Mana is a toy programming language written in Go. It is a dynamically typed, interpreted language with a C-like syntax.
Note: The language is still in development and is not yet usable. The documentation below is a work in progress and is subject to change.
Implementation | Status | Specification | Example | Tests |
---|---|---|---|---|
LetStatement |
✔️ | Let Statements are used to declare variables | let x = 5; |
✔️ |
ReturnStatement |
✔️ | Return Statements are used to return values from functions | return 5; |
✔️ |
ExpressionStatement |
✔️ | Expression Statements are used to evaluate expressions | 5 + 5; |
✔️ |
IdentifierExpression |
✔️ | Identifier Expressions are used to reference variables | x |
✔️ |
IntegerLiteralExpression |
✔️ | Integer Literal Expressions are used to represent integer values | 5 |
✔️ |
PrefixExpression |
✔️ | Prefix Expressions are used to represent prefix operators | !true |
✔️ |
InfixExpression |
✔️ | Infix Expressions are used to represent infix operators | 5 + 5 |
✔️ |
BooleanLiteralExpression |
✔️ | Boolean Literal Expressions are used to represent boolean values | true |
✔️ |
IfExpression |
✔️ | If Expressions are used to represent conditional statements | if (true) { return 5; } |
✔️ |
BlockStatement |
✔️ | Block Statements are used to represent blocks of code | { let x = 5; return x; } |
✔️ |
FunctionLiteralExpression |
✔️ | Function Literal Expressions are used to represent function definitions | fn(x) { return x; } |
✔️ |
CallExpression |
✔️ | Call Expressions are used to call functions | add(5, 5) |
✔️ |
StringLiteralExpression |
✔️ | String Literal Expressions are used to represent string values | "Hello, World!" |
✔️ |
BuiltInFunctions |
✔️ | Built-in Functions are functions that are built into the language | len("Hello, World!") |
✔️ |
ArrayLiteralExpression |
✔️ | Array Literal Expressions are used to represent array values | [1, 2, 3] |
✔️ |
IndexExpression |
✔️ | Index Expressions are used to index into arrays | myArray[0] |
✔️ |
HashLiteralExpression |
✔️ | Hash Literal Expressions are used to represent hash values | {"key": "value"} |
✔️ |
*NYI = Not Yet Implemented
Mana ships with a Read-Eval-Print-Loop (REPL) that can be used to evaluate Mana code. The REPL can be started by running the mana
execultable. Note that you will need to build the project before you can run the REPL.
/path/to/mana
Mana will then start the REPL and you can start typing Mana code. The REPL will evaluate the code and print the result.
Hello <username>! Welcome to Mana REPL!
███╗░░░███╗░█████╗░███╗░░██╗░█████╗░
████╗░████║██╔══██╗████╗░██║██╔══██╗
██╔████╔██║███████║██╔██╗██║███████║
██║╚██╔╝██║██╔══██║██║╚████║██╔══██║
██║░╚═╝░██║██║░░██║██║░╚███║██║░░██║
╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═╝░░╚══╝╚═╝░░╚═╝
>>> let x = 5;
Mana has a C-like syntax. The following is an example of a simple program written in Mana:
let x = 5; // declare a variable named x and assign it the value 5
let y = 10; // declare a variable named y and assign it the value 10
// this is a function that adds two numbers together
let add = fn(x, y) {
return x + y;
};
// this will either add x and y if x is less than y, or return the difference between x and y
let result = if (x < y) {
add(x, y)
} else {
subtract(x, y)
}; // result = 15
puts(result); // prints the value of result to the console
Mana is a dynamically typed language. This means that the type of a variable is determined at runtime. The following are the types that Mana supports:
Type | Description | Example |
---|---|---|
Integer |
A 64-bit signed integer | 5 |
Boolean |
A boolean value | true |
String |
A string value | "foo" |
Mana supports the following operators:
Operator | Description | Example |
---|---|---|
+ |
Addition | 5 + 5 |
- |
Subtraction | 5 - 5 |
* |
Multiplication | 5 * 5 |
/ |
Division | 5 / 5 |
! |
Logical NOT | !true |
< |
Less Than | 5 < 5 |
> |
Greater Than | 5 > 5 |
== |
Equal To | 5 == 5 |
!= |
Not Equal To | 5 != 5 |
Variables are declared using the let
keyword. The variable name is followed by an equals sign and an expression. The expression is evaluated and the result is assigned to the variable.
let x = 5;
Mana supports If-Else conditionals. An IfExpression
in Mana is composed of two parts: the condition and the consequence. The condition is an expression that evaluates to a boolean value. The consequence is a BlockStatement
that is executed if the condition evaluates to true
. The consequence is optional. If the condition evaluates to false
and there is no consequence, then the IfExpression
evaluates to null
. If there is a consequence, then the IfExpression
evaluates to the value of the last statement in the consequence.
if (x < y) {
x + y;
} else {
x - y;
}
Mana supports first-class functions. This means that functions can be passed as arguments to other functions, returned from functions, and assigned to variables. The following is an example of a function definition in Mana.
Functions are defined using the fn
keyword. The function name is followed by a list of parameters in parentheses. The function body is enclosed in curly braces. The function body is a BlockStatement
, which means that it is a list of statements enclosed in curly braces. The last statement in the function body is the return
statement, which is used to return a value from the function. Functions, themselves, are ExpressionStatements
, which means that they evaluate to a value. The value that a function evaluates to is the value that is returned from the function.
fn add(x, y) {
return x + y;
}
Mana has a number of built-in functions that are available to the programmer. These functions are built into the language and can be used without having to define them. The following is a list of built-in functions that are available in Mana:
Function | Description | Parameters | Example | Output |
---|---|---|---|---|
len |
Returns the length of a string or an array | string |
len("Hello, World!") |
13 |
push |
Appends an element to the end of an array | array , expression |
push([1, 2, 3], 4) |
[1, 2, 3, 4] |
first |
Returns the first element of an array | array |
first([1, 2, 3]) |
1 |
last |
Returns the last element of an array | array |
last([1, 2, 3]) |
3 |
rest |
Returns all but the first element of an array | array |
rest([1, 2, 3]) |
[2, 3] |
puts |
Prints a value to the console | expression |
puts("Hello, World!") |
Hello, World! |
Strings in Mana are enclosed in double quotes. Strings are immutable, which means that once a string is created, it cannot be changed. Strings can be concatenated using the +
operator.
let greeting = "Hello, ";
let name = "World!";
let message = greeting + name; // message = "Hello, World!"
Arrays in Mana are ordered collections of values. Arrays are created using square brackets. Arrays can contain values of any type, including other arrays. Arrays are indexed using square brackets. The index is an integer value that represents the position of the element in the array. Arrays are zero-indexed, which means that the first element in the array is at index 0.
let numbers = [1, 2, 3, 4, 5];
let first = numbers[0]; // first = 1
let last = numbers[len(numbers) - 1]; // last = 5
let mixed = [1, "two", true, fn(x) { x * x; }];
let c = 69;
let f = mixed[3](5); // f = 25
let u = ["one", "two", 3][5 - 4]; // u = "two"
let k = mixed[c - 67]; // k = true
Hashes in Mana are unordered collections of key-value pairs. Hashes are created using curly braces. Hashes can contain keys of supported types
(Boolean, Integers, and Strings - Objects which implement the Hashable
interface). Hashes can contain values of any type, including other hashes. Hashes are indexed using square brackets. The index is a key that represents the key of the element in the hash.
let person = {
"name": "Alice",
"age": 30,
"isStudent": false,
"address": {
"street": "123 Main St",
"city": "Anytown"
}
};
let name = person["name"]; // name = "Alice"
let city = person["address"]["city"]; // city = "Anytown"
Map
let map = fn(arr, f) {
let iter = fn(arr, accumulated) {
if (len(arr) == 0) {
accumulated
} else {
iter(rest(arr), push(accumulated, f(first(arr))));
}
};
iter(arr, []);
};
let a = [1, 2, 3, 4, 5];
let double = fn(x) { x * 2; };
let doubled = map(a, double); // doubled = [2, 4, 6, 8, 10]
Reduce
let reduce = fn(arr, initial, f) {
let iter = fn(arr, result) {
if (len(arr) == 0) {
result
} else {
iter(rest(arr), f(result, first(arr)));
}
};
iter(arr, initial);
};
let a = [1, 2, 3, 4, 5];
let sum = fn(arr) {
reduce(arr, 0, fn(initial, el) { initial + el; });
};
sum(a); // sum = 15
Filter
let filter = fn(arr, f) {
let iter = fn(arr, filtered) {
if (len(arr) == 0) {
filtered
} else {
let x = first(arr);
if (f(x)) {
iter(rest(arr), push(filtered, x));
} else {
iter(rest(arr), filtered);
}
}
};
iter(arr, []);
};
let a = [1, 2, 3, 4, 5];
let two = fn(x) { x == 2; };
let filtered = filter(a, two); // filtered = [2]
To build the project, you will need to have Go installed on your machine. You can download Go from the official website. Once you have Go installed, you can build the project by running the following command:
go build
This will create an executable file named mana
in the root of the project directory.
To run the tests, you can use the go test
command. This will run all of the tests in the project.
go test ./...
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.