Eme is a low-level, strongly typed, and non-managed programming language with a compiler written in C. It's both compiled and interpreted, and is designed to support 'high-level' features such as advanced polymorphism, iterators, and operator overloading, without run-time costs.
'Eme' () means 'language' in Sumerian, the earliest known written language.
Here's a simple code example which recursively computes the factorial of an integer x.
fac :: fn (x: int) -> int {
if(1 < x) return x*fac(x-1);
else return 1;
}
And the iterative equivalent:
fac :: fn (x: int) -> int {
if(x == 0 || x == 1) return 1;
r := 1;
each(range(2, x+1)) r = r * it;
return r;
}
A longer code sample which constructs the 'array' data structure from the bottom up can be found below, and I've also open-sourced a demo of the language which uses SDL2 to make a simple shooter game.
Currently, in order to simplify development, Eme only supports x64 Windows. Look in releases for an x64 Windows executable, or compile the compiler yourself by linking and including LLVM-C. The downloaded executable can either interpret or compile your program, adjustable through command line options.
To interpret a program, run:
eme source_file -i
This will run the program and output the integer value that the main function returns.
To compile a program, run:
eme source_file -o obj_file [asm_file] [llvm_ir_file]
This will output an object file, along with optionally an x64 assembly file and an LLVM bytecode file. Currently, Eme relies on the C standard library for interfacing with the operating system, so the recommended way to compile it is by linking it to a short C program. Note that these file paths must be not be relative.
For example, I compile my Eme programs with this file:
int main();
The out.obj
file generated by the Eme compiler will fill in the definition of main
. I use the following MSVC compiler command to compile the above file with the compiler's outputted object file:
cl.exe support.c /nologo /Fe"out.exe" /link "out.obj"
This produces an output executable (out.exe
), which is your compiled Eme program!
There is an extra compilation mode:
eme source_file -b
This compilation mode is only used for benchmarking and only generates bytecode and then exits. It functionally does nothing but it's useful for testing the performance of the front end.
The following is a ground-up implementation of an array data type in Eme, along with a small example of its use. The implementation includes a subscript operator overload and an iterator implementation, so that the data structure is easily used in user programs.
// We're using the foreign function malloc to allocate data.
malloc :: foreign fn(int) -> int
// Allocates a block of memory of size x * size_of(T).
allocn :: fn #inline ($T, x: int) -> ^T {
return bit_cast(^T, malloc(size_of(T)*x));
}
// This is a polymorphic struct which can represent an array of any type T.
array :: struct(T: type) {
length: int;
data: ^T;
}
// The array subscript operator, which returns a pointer to the nth element of
// a given array a. The #operator tag means you can call this function
// subscript(arr, n) with the syntax arr[n].
subscript :: fn #inline #operator (a: array($T), n: int) -> ^T {
return bit_cast(^T, size_of(T)*n + bit_cast(int, a.data));
}
// The array allocation/initialization function. Other code can
// override the identifier 'alloc' with a different first argument, so you can
// specialize the 'alloc' function to support any data structure you define.
alloc :: fn #inline (array($T), length: int) -> array(T) {
r: array(T);
r.length = length;
r.data = allocn(T, length);
return r;
}
// This is the entry point of our program. Right now, it just calculates some
// squares to demo the array data structure.
main :: fn () -> int {
// Allocate an array
arr := alloc(array(int), 10);
// Use a loop to fill its contents, by calculating squares from 0-9.
// This uses an iterator, implemented for the array datatype below this function.
each(arr) it = it_index*it_index;
// Return the sixth element (which should be 25)
return arr[5];
}
// This is the struct representing the iterator type.
array_iter :: struct(T: type) {
arr: array(T);
index: int;
}
// Creates a new iterator from an array.
iterator_make :: fn #inline #operator (a: array($T)) -> array_iter(T) {
r: array_iter(T);
r.arr = a;
r.index = 0;
return r;
}
// Gets element the iterator is currently at.
iterator_element :: fn #inline #operator (a: array_iter($T)) -> ^T {
return a.arr^[a.index]; // This operator returns the pointer to the element in
// the array, rather than the element itself. This is
// automatically defined with the subscript operator.
}
// Gets the index of the element the iterator is currently at.
iterator_index :: fn #inline #operator (a: array_iter($T)) -> int {
return a.index;
}
// Gets whether the iterator is finished going through the whole array.
iterator_done :: fn #inline #operator (a: array_iter($T)) -> bool {
return a.index >= a.arr.length;
}
// Moves the iterator to the next element.
iterator_next :: fn #inline #operator (a: ^array_iter($T)) {
a.index = a.index + 1;
}