+
← Back to demos

anglais

anglais is a small programming language with a bytecode VM. (see below)
It in your browser with the magic of WebAssembly

What is this?

anglais was my first time learning how programming languages that are evaluated using virtual machines are built and how they work.

I started working on this project after the summmer of 2024. The previous 8 months i had been working on and off on a different programming language, and it was the first time i dipped my toes into that sort of meta-work. My first language, radion, was evaluated using a tree-walking interpreter. The project was loosely based on Robert Nystrom's Crafting Interpreters, but while it was an amazing guide and resource, what i both preferred and found most interesting was experimenting and trying out my own ideas.

When i began writing the code for anglais, i had only a rough idea of what i wanted. Most of the project was made up along the way. At the beginning, i had decided to have a full parser as the step before compiling, as it allowed for more complex grammar, and more flexibility. The "algorithm" i used for the parser was recursive descent. I wanted to try something new, like an LR-parser, but the lack of good learning resources made me decide to stick with something i already knew how worked.
> The abstract structure of the program.
anglais runs considerably slower in the browser than on a computer. Running in my browser, the basel-problem-pi program with 100 000 terms takes on average ~110 ms, while running it on my own computer gives an average of 19.5 ms ± 1.4 ms. With 1 000 000 terms, the browser (WASM) version takes ~850 ms, while running it on my computer takes 165.5 ms ± 2.7 ms.

What's the syntax?

anglais is loosely inspired by several languages, namely go (which it was implemented in), and strikingly so. Some go code should work as anglais code. The main difference is that go is structured with functions on the top level, while anglais is a scripting language like Python and JavaScript. For example syntax and (working) example programs in the language, see above.

Strings ("Hello world"), numbers (1, 3.141), and booleans (true, false) are written identical to almost every other language. In anglais, the null/nil value is nil. Functions are called using parentheses, with arguments seperated by commas, for example sum(1, 2). There is also support for lists. They look identical to lists in python and JavaScript: with square brackets opening and closing. (e.g. [1, nil, 2, "Hello", true]). Lists can have any size, and any value can be in a list; lists are very dynamic.

The "print" function in anglais is write. It takes a single argument, and prints it to the standard output. The following is a simple hello-world program in anglais.
write("Hello world!")
The output from this program would be
Hello world!

Assigning and declaring

In anglais, assigning and declaring are two different things. When you want to update the value of a variable that already exists, you assign it a new value. When you want to make a new variable with an initial value, you declare it. Syntax-wise, they are pretty similar, but with one important difference: declaring uses := instead of = (exactly like in go).
a := 1

b := 2
a = b + 2
You can redeclare a variable in a new scope (inside braces/blocks, e.g. functions, loops):
a := 10
write(a)

{
	# the variable is redeclared
	a := 7
	write(a)

	# and updated
	a = 4
	write(a)
}

# and the original value is resumed
write(a)
The previous code would produce the output:
10
7
4
10

Loops

The only loop currently implemented is the while-loop. It executes as long as the condition is true:
while <condition> {
	...
}
Using this, a for-like-loop can be made by also declaring a counter i which is incremented each iteration:
i := 0
while i < 100 {
	...

	i = i + 1
}

Functions

Functions are declared with the func keyword. This is for both inline functions and function declarations. A function declaration has the same effect as assigning an inline function to a variable:
func sum(a, b) {
	return a + b
}

# or

sum := func(a, b) {
	return a + b
}
Functions are first-class citizens. This means that you can pass them as function parameters, but also assign them to variables, since they are treated as values. If you (for some reason) want to rename the write function to print, you could to the following:
print := write
The new print function is the same as write, meaning that it takes the same parameters, yields the same value, and has the same body.
print := write

# Both of these do the same thing
print("Hello world!")
write("Hello world!")

Importing

Importing is done with an import statement, which has the following form:
import "<file>"
Where the name of the file is inside a string. Importing has the same effect as taking the source of the library of program and replacing the import statement with it. It is very simple. This means that there is no way of just importing one or two functions; importing always imports all available functions. When importing all statements that are not functions are also included.

Standard library

The two functions for outputting are write and print. write appends a newline behind the output, while print only outputs the string version of its argument.

Here, in this web-demo, other examples can be imported using the id shown in the dropdown to import them. The small standard math library is available, and i would recommend going through the source if you're looking for documentation. For example, if you would like to import the math library:
import "math"


Some values have properties which are standard functions. Currently, only lists and strings have these. Lists have list.append(n), which allows you to add an element to a list. The function returns a new list with the previous elements first, and the appended element after ([1, 2].append(3) == [1, 2, 3]). This means that you have to reassign the list when you append a value:
list := [1, 2]
write(list)

# append a value
list = list.append(3)
write(list)
The previous code would produce the following output:
[1, 2]
[1, 2, 3]
Lists also have a list.length() function, which simply returns the length of the list (the amount of elements). For example:
write([1, 2, 3].length())
write([nil, true, "Hello world!", nil].length())
would produce the output:
3
5
Lists also have a list.map(fn) and a list.reduce(fn, s) function. These functions take in a function as their first parameter, and list.reduce(fn, s) takes a starting value as a second parameter. The list.map(fn) function applies the function on each element in the list, and returns a new list, same length, but with the return value from the function instead of each element. For example:
list := [1, 3, 2]
write(list)

squares := list.map(func(x) {
	return x*x
})

write(squares)
would produce the output:
[1, 3, 2]
[1, 9, 4]
© Neemek 2026