Share your Programming Language Jam submissions here!

← Back to all posts
Facilis - A Low Level Functional Language
h
CSharpIsGud (646)

Facilis is a strongly typed functional language that aims to be low level. Facilis uses a hand-written recursive descent parser made with love.

Factorial Example

impure foreign putchar(c of u8) -> void
impure foreign tostr(num of void*) -> string

impure write(str of string, start of i32) -> void {
	putchar(str[start])

	if start != str.length() {
		write(str, start + 1)
	}
}

pure fact(i of i32) -> i32 {
	if i == 1 {
		return i
	}

	return i * fact(i - 1)
}

impure main() -> i32 {
    write(tostr(fact(6)), 0)
}

Demo: https://repl.it/@Facilislang/FacilisDemo#README.md
^ Has a user/password login program loaded by default. Read the README!

Source: https://repl.it/@Facilislang/Facilis
^ 2072 lines of C++!

@CSharpIsGud @Subarashi @xxpertHacker

Commentshotnewtop
AmazingMech2418 (941)

Are there supposed to be errors with the linker?

fuzzyastrocat (1297)

@CSharpIsGud @xxpertHacker I too get this error, undefined symbol main. From Sea I know that this means the main function hasn't been defined, so maybe the compiler is not generating an entry point?

CSharpIsGud (646)

@fuzzyastrocat It happens randomly, usually if you keep on trying the same thing it eventually works. We don't really know why yet.

fuzzyastrocat (1297)

@CSharpIsGud Weird! It's odd when things like that happen even though there's no randomness in the code.

CSharpIsGud (646)

@fuzzyastrocat We assume it has something to do with memory alloc/dealloc problems.

AmazingMech2418 (941)

@CSharpIsGud Memory allocation won't cause an issue with the linker...

CSharpIsGud (646)

@AmazingMech2418 the linker isn't the problem, the error happens because the compiler has a bug that makes it generate an empty file.
the linker errors when it cant find the main function.

AmazingMech2418 (941)

@CSharpIsGud Try changing it to a C++ repl instead of a Bash repl.

fuzzyastrocat (1297)

@CSharpIsGud Interesting, okay. Memory management can be pretty finicky sometimes, so that would probably explain it.

AmazingMech2418 (941)

@fuzzyastrocat It only happens in the demo repl or a fork of it. It works in the main repl.

AmazingMech2418 (941)

@fuzzyastrocat I think it might be that it is a Bash repl...

CSharpIsGud (646)

@AmazingMech2418 it happens on the main repl too.

AmazingMech2418 (941)

@CSharpIsGud Not for me. Maybe it's the program being compiled?

xxpertHacker (555)

@AmazingMech2418 It seems to be non-deterministic, I'd estimate it occurs about 50% of the time, for now just keep compiling until it works.
I had theorized that it occurs due to early deallocation, but we don't have any hard proof, or we would've fixed it.
And yes, it's on all of the Repls, Bash Repls are just easier to work with.

xxpertHacker (555)

@fuzzyastrocat Yeah, it definitely could be memory, considering how much we allocated memory, because we started off by using plenty of std::strings instead of something like static std::string_views, meaning a good amount of memory is allocated, which may be problematic for large programs.
But if we ever wanted to work on this, we would have to do a full rewrite.

(Also, there is a whole memory leak we didn't do anything about...)

fuzzyastrocat (1297)

@xxpertHacker Oh no, memory leaks are the worst... I envy low-level programmers who are able to avoid them lol

xxpertHacker (555)

@fuzzyastrocat It's literally as easy as saying delete x; in C++, lol. But apparently we didn't clear any of the memory we allocated... so, yeah, there's that. :)

fuzzyastrocat (1297)

@xxpertHacker Oh I meant for C, I'm not a big fan of malloc and free
But yeah always good to clear that memory :D

Jakman (458)

If you want a truely functional language then every function should return a value. void should not exist

xxpertHacker (555)

@Jakman Yeah, look closely and it has more than one problem preventing it from really being an actual FP language.

Jakman (458)

@xxpertHacker yep. That impure keyword

xxpertHacker (555)

@Jakman The language is 100% useless without the impure keyword, although if you wanted a CPU warming program, of course, Facilis is just for you! Even Haskell has some impurity.

Now seriously, there are other problems, the if statement is useless, as there is no way to use it without creating impure code, unless it's followed by an else, in which case it's better, but the only genuine use I could imagine would be an if else in which both statements return a value, besides that there's little to no use.

void only has use in impure code, a void function is automatically impure. There's plenty of other things I can't even begin to think of at the moment. We almost had mutable variables too!

Jakman (458)

@xxpertHacker I have created a paradigm that allows OOP and FP. When you call a method on an object it returns a new object with some modification. This technically follows Fp guidelines and allows you to still use OOP efficiently. for example Instead of

struct Car {tire_count:u32}
impl Car {
   fn lose_tire(&mut self) {
       self.tire_count -= 1;
   }

You could have this

struct Car {tire_count:u32}
impl Car {
    fn lose_tire(&self) -> Self {
        return Car{tire_count:self.tire_count-1};
    }
}
Subarashi (3)

@Jakman well, the language was never intended to be a pure functional lang.

fuzzyastrocat (1297)

@Jakman That works nicely in theory, but it's absolutely terrible for low-level efficiency. Consider: every time you need to lose_tire, you're allocating a new Car object. And garbage-collecting becomes a must, since calling lose_tire multiple times quickly leaks memory.
So, I'm not saying it's impossible or a bad idea — you could write an efficient compiler to overwrite the old car with the new car, but now you're basically just modifying the original car while wasting time overwriting the non-changed fields.

TL;DR: In theory, that works nicely. In practice, to make it efficient it becomes the same as just modifying the original object.

CSharpIsGud (646)

@fuzzyastrocat Later we could probably secretly modify the original object behind the scenes.

fuzzyastrocat (1297)

@CSharpIsGud True. But now what happens if other functions refer to the original object? You could end up with an impurity leak in pure functions. To do it correctly, you'd pretty much have to create a new object copy every time, but that leads to obvious problems.

So... extremely well-made garbage collector I guess?

xxpertHacker (555)

@Jakman OOP isn't entirely independent of FP, they're not incompatible for any particular reason, it's just how OOP is normally used that affects it. Also, usually OOP is incredibly inefficient.

I was thinking about allowing 100% immutable structs to start with later, possibly developing into some twisted OOP, but who knows.

As of right now, I was preparing to focus on static arrays, destructuring, compile-time JavaScript-like syntax array spreading, and other array-related stuff, etc.

DynamicSquid (4398)

@Jakman

Scratch is not a real programming language.

What has society come to in order for you to say that?

Jakman (458)

@DynamicSquid Some tours through Vietnam

sugarfi (587)

@Jakman that's just fp lmao
haskell internally does that iirc

Camto (8)

@Jakman That's not a new paradigm, those are called lenses! :)

xxpertHacker (555)

@fuzzyastrocat No, I am not CSharpIsGud's alternate account, I've never used C#.
I also couldn't tell which post you commented on either since you deleted it, so I presumed it was here?

fuzzyastrocat (1297)

@xxpertHacker Oh no, it was a reply to "OOP isn't entirely independent of FP...". Since you talked about implementing stuff I assumed you were a CSharpIsGud alt, but then I realized you were listed as a teammate.

xxpertHacker (555)

@fuzzyastrocat Yeah, I helped a bit.
(I was probably more harmful than helpful, but whatever.)
Thankfully, we're separate people :).

DynamicSquid (4398)

why are we using all caps in an enum with a non-capitalized enum name?

I like that question xD

xxpertHacker (555)

@DynamicSquid Wait what, you aren't supposed to actually check the source code! Lol, no one else at Repl.it does at least. Good luck understanding that stuff.

DynamicSquid (4398)

@xxpertHacker lol. I just find it really interesting since it's in C++ and I could something. And it's not that messy

DynamicSquid (4398)

@xxpertHacker quick question, so when you compiled your language, you translated your language's syntax into C++ code in the out.cpp file, and compiled that with bash?

xxpertHacker (555)

@DynamicSquid We actually accept command line arguments, we just passed "out.cpp" as the output file, but yeah, we used a bit of Bash, we could've changed it to pure C++, but it's not easy to query the system and execute other code in a portable simple fashion. In a way we just took the easy way out, also, you can hand tune the output or check it for bugs yourself, since our implementation is still pretty buggy.

DynamicSquid (4398)

@xxpertHacker oh okay. I'v never done any compiled language stuff before so all this is really interesting. so basically a compiled language turns your language's syntax into lower level code?

xxpertHacker (555)

@DynamicSquid It's not an actual compiler, it's merely a transpiler, but if we win and are asked to update it for the next 3 months, we might end up using LLVM, or just fixing all of the bugs and making it more real.

It could very well become its own language independent from C/C++, once we create a proper module system, a foreign function interface (FFI), scope checking, types, type checking, new operations, etc.

But for the time being, it's actually just a transpiler.

DynamicSquid (4398)

@xxpertHacker I see... but I heard that a compiler translates source code to a lower level, so in this case, would it have to translate to Assembly for it to be an actual compiler?

fuzzyastrocat (1297)

@DynamicSquid That is the general consensus, yes. Some people consider compiling to C (or a C-like language such as C++, C--) a "true" compiler as well, but others don't. (I do, since if you compile to C the C just compiles to x86, so you're basically compiling to x86 with two steps. If you don't consider this a true compiler, then Haskell is not compiled.)

DynamicSquid (4398)

@fuzzyastrocat wait did you just say C minus minus?????? oh okay. for your language "Sea", you compiled to Assembly?

DynamicSquid (4398)

@fuzzyastrocat so you had to learn assembly?

fuzzyastrocat (1297)

@DynamicSquid :D

It takes dedication, but it's not impossible.

DynamicSquid (4398)

@fuzzyastrocat yuck. how do you run it? the file extension is .s, right?

fuzzyastrocat (1297)

@DynamicSquid Running it depends on the assembly "flavor". There are two main flavors of assembly:

  • Intel: This is the most common one. It is the thing you'll see a bunch on StackOverflow. (For some examples of what it looks like, so that I don't have to explain it, look here).
  • AT&T: This one is far less common, but I like it better. It's also used by the gcc C compiler by default. Some examples of what AT&T looks like
    can be found here.

To run AT&T, use gcc [filename].s -o ./out. That will make an executable, ./out, which can be run through the shell by simply typing ./out.

To run Intel, you have to get something like nasm. I won't go over that here, you can probably find it online by a google search (I'm not super experienced with nasm either so google will probably explain it better).

fuzzyastrocat (1297)

@DynamicSquid I may also stand corrected — I'm attempting to run Intel with gcc right now, so I'll get back to you if it's possible.

fuzzyastrocat (1297)

@DynamicSquid I can't seem to find a way to do it but it may be possible in different environments. (The internet claims that adding the -masm=intel option to GCC works, but it didn't in my test.) So for all intents and purposes
Intel does require something like nasm, and also uses the file extension .asm.

For more about the differences, Wikipedia has a good comparison. There's also this which gives a shorter overview.

fuzzyastrocat (1297)

@DynamicSquid One last thing: In my opinion a compiler might not be the best way to learn assembly — first it might be best to learn some basics (using an external tutorial), then try making the compiler. The thing about assembly is that it's not super hard to learn the syntax (so learning the basics should be easy), but finding the right commands (and semantics) can be hard (the command set is fairly large and documentation can be hard to find for some commands).

DynamicSquid (4398)

@fuzzyastrocat yeah, that was what I was planning to do, but I think for now I'm going to stick to transpiled languages first

fuzzyastrocat (1297)

@DynamicSquid Transpiling is a great way to learn the how of a compiler without the nitty-gritty what. So yeah, if you do a transpiler and then learn assembly it should all kinda "come together"*.

* "coming together" not guaranteed. You know what they say about theory and practice... :D
fuzzyastrocat (1297)

@DynamicSquid The only true way to lol and have a smiley is to lol with smileys :D

    :D         :D :D :D   :D
    :D         :D    :D   :D
    :D :D :D   :D :D :D   :D :D :D
xxpertHacker (555)

@DynamicSquid Often times directly to binary. Either way, it just has to compiled from every high-level, abstract language, into instructions that show how the machine works, oftentimes those instructions should be executable.
Ideally, Facilis is without external dependencies, unlike what we have done here, compilation should be one single step from the user's point of view, but I guess we can just document that somewhere.