Skip to content

Marmoset: A compiled programming language for ARM

License

Notifications You must be signed in to change notification settings

danjovich/marmoset

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Marmoset: A compiled programming language for ARM

This is a simple programming language inspired by the one implemented in the book Writing A Compiler In Go, written by Thorsten Ball.

Since the language implemented in the book is called "monkey," the name of this language comes from the fact that marmoset is a type of monkey, and the language is compiled for ARM (mARMoset).

How to use it

To compile the compiler, simply run make. You need to have a Go compiler installed, version >=1.20. To install the Go compiler, just follow the instructions on the official website.

Currently, the Marmoset compiler only prints the assembly generated from the source code. To generate an ELF executable, you will need to use some recipes in the Makefile.

Before generating the binaries, the necessary binutils must be installed. If you are on an amd64 machine with APT as the package manager, you need to run:

sudo apt install binutils-arm-linux-gnueabihf binutils-arm-linux-gnueabihf-dbg

Additionally, on an amd64 machine, you need to install qemu for AArch32 in user mode, which can be done with:

sudo apt install qemu-user qemu-user-static

Then, to compile a Marmoset program, simply place the .marm file in the examples folder and run:

make examples/bin/<program_name>.out

To run the program, simply execute:

./examples/bin/<program_name>.out

For example, for the provided fibonacci.marm program, which prints the seventh, then the first, and then the 15th number in the Fibonacci sequence, calculated through a recursive function, the output should be:

13
1
610

It is also possible to run a program (with a .marm extension and inside the examples folder) directly with a single command make run-<program_name>, but make will delete the generated assembly and binary afterward (if they were not previously generated). For example, to run fibonacci.marm this way, simply run:

make run-fibonacci

The language

Variables declaration and assignment

Variables declaration must be done using the reserved keyword let. Every variable must be initialized with a value; it is not possible to simply declare them (e.g., let a; is not a valid expression in Marmoset). Assignment also occurs with let, and shadowing is allowed — identical expressions can act as both declaration and assignment or just assignment. Note that the semicolon (;) at the end of each expression is optional.

let a = 1; 
let b = 2;
let a = b;
let c = true

Comments

Comments can be written in C++ style:

// comment

Functions

Function declaration starts with the reserved keyword fn, and function calls are similar to C. There is no need to declare return types or parameter types; everything is treated as either an integer or a boolean. Functions that do not explicitly return any value actually return null (equivalent to 0). Function returns do not need to be explicit: if the last expression to be executed is not stored in a variable or used in any way, its value will be returned. Empty returns (return;) are not allowed.

fn identity(x) { x; }; 
identity(7); // returns 7

fn add(a, b) { a + b }; 
add(1, 2); // returns 3

fn sub(a, b) { a - b }; 
sub(1, 2); // returns -1

Conditionals

Conditionals in Marmoset are expressions, so they return values. If the code inside an if (or else) ends with an expression without assignment, the value of that expression will be returned; otherwise, the conditional will return null (0).

if (x < y) { 
    z 
} else { 
    w 
}
	
let a = if (true) {5;} // a receives 5

let b = if (true) {
  if (false) {
    10;
  } else {
    20;
  }
} // b receives 20

Operator Precedence

Operator precedence is the same as in most languages, in increasing order:

  • Equality (==) or inequality (!=);
  • Less than (<) or greater than (>);
  • Addition and subtraction;
  • Multiplication, division, and modulus (%);
  • Negation (!x) or sign inversion (-x);
  • Function calls.

In these examples, you can see how the compiler handles precedences (the comments show the result that the parser will generate when analyzing precedence):

3 + 4 * 5 == 3 * 1 + 4 * 5 
// ((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))

add(a + b + c * d / f + g % h * i)
// add((((a + b) + ((c * d) / f)) + ((g % h) * i)))

Builtins

The following functions have been implemented:

  • put: Uses the Linux write system call to print a character to the screen (i.e., to stdout). Since there are no chars in Marmoset, it must receive an integer and will print the ASCII character equivalent to that integer.

  • get: Uses the Linux read system call to read a character from the screen (i.e., from stdin). Since there are no chars in Marmoset, it returns an integer equivalent to the ASCII character entered.

  • putint: To simplify printing integers on the screen (since passing an integer to the put function will print the equivalent ASCII character, not the integer itself), this function is provided. It was implemented in Marmoset and compiled, so when it is used by a program, the generated assembly routine is included in the new program's assembly. Its implementation in Marmoset is as follows (note that 45 is the value of the ASCII character "-" and 48 of "0"):

    fn putint(i) {
      if (i < 0) {
        put(45);
        let i = -i;
      }
    
      if (i/10) {
        putint(i/10);
      }
    
      put((i % 10) + 48);
    }
    
  • putintln: Since the putint function does not insert a newline (\n) after printing the integer, and it is often desired to do so, this builtin function was also implemented as follows (10 is \n in ASCII):

    fn putintln(i) {
      putint(i);
      put(10);
    }
    

About

Marmoset: A compiled programming language for ARM

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published