A JavaScript implementation of the Egg programming language, as described by Marijn Haverbeke in his book.
You can check everything is installed correctly by running the following commands:
$ node --version
v8.9.4
$ npm --version
5.6.0
First, make sure that you have all the dependencies listed in the previous section. Then, clone the project repository and enter the root directory:
$ git clone https://github.com/nventuro/egglang.git
$ cd egglang
Next, build the project dependencies:
$ npm install
To make sure everything is set up correctly, it would be a good idea to run all tests at this point and verify that they finish successfully:
$ npm test
Run egg.sh
to get a REPL, or call it with a .egg
file to run an Egg program. Sample programs are provided in the samples directory.
$ ./egg.sh samples/modules.egg
40
Egg is an expression-oriented language, with each program being a single expression, and uses prefix notation (also called Polish notation). Because of this, it looks somewhat different from other popular programming languages.
Egg supports integers (both positive and negative), boolean values, and literal strings (escape characters are not allowed though).
All of the basic arithmetic and comparison operands are supported, and can be used by calling a function with their name. These operations can be chained.
> +(1, 2)
3
> /(12, -3)
-4
> +(*(4, 2), -(5, 3))
10
> <(5, 10)
true
Egg is a dynamically typed language, and as such type declarations are not required when creating new variables. This is done using the :=
operator.
> :=(a, 2)
2
> a
2
> +(a, 3)
5
Like everything else in Egg, :=
is an expression, and evaluates to the assigned value.
=
is used to update the value of a :=
'd variable, and also evaluates to the assigned value.
> :=(a, 2)
2
> :=(a, 3)
ReferenceError: Attempting to re-define local variable
> a
2
> =(a, 3)
3
> a
3
Functions are created with the fun
keyword: its first n-1
arguments are the function's arguments, with the remaining argument being the function body. A function is evaluated to (returns) its body. Functions can be passed as arguments and returned from them, and closures can be created.
> :=(f, fun(
2
))
> f()
2
> :=(g, fun(a,
a
))
> g(3)
3
> :=(h, fun(
:=(i, 2)
))
> h()
2
> i
ReferenceError: Undefined variable: i
> :=(adder, fun(a,
fun(b,
+(a, b)
)
))
> :=(add_5, adder(5))
> add_5(3)
8
A program composed of a single expression is quite limiting, but this becomes a non-issue by using the do
keyword. It evaluates each of its arguments in order, and is evaluated to the value of the last one. This makes it useful for function bodies and flow control, as is shown below.
> do(
:=(a, 3),
:=(b, 4),
+(a, b)
)
7
If a particular value wants to be 'returned' from a do
expression (such as at the end of a function), that value can simply be evaluated last.
> :=(f, fun(do(
:=(a, 2),
:=(b, 3),
+(a, b),
a
)))
> f()
2
Both fun
and do
create a local scope, in which new variables can be :=
'd without having them be created in the outer scope. Outer variables can still be accessed and modified using =
.
The standard if
keyword is supported by Egg, but its meaning is slightly different. Since if
is also an expression, it's actually closer to C's ternary operator (?:
), and like in C, both the taken and not-taken branches are required. if
evaluates to the value of the branch that ends up being evaluated.
> if(true,
2,
3
)
2
> :=(a, 2)
> if(==(a, 3),
:=(a, 4),
:=(a, 5)
)
5
> a
5
The while
keyword provides the only flow control mechanism, evaluating its body until the condition is false. do
can be used with while
to allow more than one expression to be evaluated inside its body.
> :=(i, 0)
> while(<(i, 10),
=(i, +(i, 1))
)
> i
10
> :=(i, 0)
> :=(pow, 1)
> while(<(i, 10), do(
=(pow, *(pow, 2)),
=(i, +(i, 1))
))
> pow
1024
while
is also an expression, and always evaluates to false.
Egg programs can print to the console by calling print
, which evaluates to its argument.
There are two kinds of collections in Egg: arrays and dictionaries. Their interface is similar, but usage differs slightly. Both collections can store any kind of object.
Arrays are created by calling array
with the values to be stored in the array (or none for an empty array). These values can then be retrieved by calling get
with a zero-based index, and new values can be added at the end of the array by calling push
. The length of the array is returned by length
.
> :=(arr, array(1, 2, 3))
[1, 2, 3]
> arr.get(0)
1
> arr.push("4")
[1, 2, 3, "4"]
> arr
[1, 2, 3, "4"]
> length(arr)
4
Dictionaries are created by calling dict
with an even number of arguments (none for an empty dictionary): even arguments will be keys, and odd arguments will be values. These values can then be retrieved by calling get
with an appropiate key, and new key-value pairs can be added by calling push
. The number of key-value pairs is returned by length
.
> :=(di, dict(1, 2, 3, "4"))
{1: 2, 3: "4"}
> arr.get(1)
2
> di.push("abc", 123)
{1: 2, 3: "4", "abc": 123}
> di
{1: 2, 3: "4", "abc": 123}
> length(di)
3
Egg supports modules in a similar way as Node.js's require
works. The import
keyword loads an Egg program (an expression), evaluates it, and returns that value. Therefore, modules typically consist of single functions or dictionaries, which are then stored by the user in a variable. Standard Egg modules are provided in the modules directory.
# is_even.egg
fun(x,
==(%(x, 2), 0)
)
> :=(is_even, import("is_even.egg"))
> is_even(2)
true
# parity.egg
dict(
"is_even", fun(x,
==(%(x, 2), 0)
),
"is_idd", fun(x,
!=(%(x, 2), 0)
)
)
> :=(parity, import("parity.egg"))
> get(parity, "is_odd")(5)
true