Crate mechylang

Source
Expand description

A dynamically typed, interpreted programming language. This is the documentation for the mechylang crate.

§What is mechylang?

mechylang is a programming language that was created for the purpose of learning how to create a programming language.

It is a dynamically typed, interpreted language that is heavily inspired by Rust.

It supports the following features:

  • Variables
  • Functions (with support for closures, recursion, and higher order functions)
  • Control flow (if/else, while, for)
  • Comments (single line and multi line)
  • Arithmetic operations
  • Comparison operations
  • Logical operations
  • String operations (concatenation)
  • Printing to the console (or using a custom function passed to the interpreter)
  • Iterators and some of their methods:
    • map
    • filter
    • fold
    • step_by
    • sum
    • take

Special thanks to Thorsten Ball for his book Writing An Interpreter In Go, it was a great read and a lot of help.

§How do I use mechylang?

Your currently looking at the documentation for the mechylang library crate. There is also a binary crate that can be used to run mechylang programs. It supports running a file, or opening a repl.

If you want to use the library crate, you can add it to your Cargo.toml file like so:

[dependencies]
mechylang = "0.1.0"

Then, you can use it in your code like so:

use mechylang::{Evaluator, Environment, EvalConfig, Object};

let mut env = Environment::new();

let code = "let x = 5; x + 5";

let result = Evaluator::eval(code, &mut env, EvalConfig::default());
assert_eq!(result, Ok(Object::Integer(10)));

§Mechylang Syntax

§Hello World

No programming language is complete without a hello world example.

// This is a comment
// With the print function you can print to stdout
print("Hello World!")
§Note about code examples:

All code examples in this documentation are automatically tested using the test_eval_ok function.

mechylang::test_utils::test_eval_ok(r#"
assert_eq(2 + 3, 5)
"#);

Only the mechylang executed code is shown in the examples, not the rust code that runs the tests.

§Variables

Variables can be declared using the let keyword.

let x = 5
let y = 10
assert_eq(x, 5)
assert_eq(y, 10)

Variables can be reassigned using the = operator.

let x = 5
x = 10
assert_eq(x, 10)

Valid variable names are any combination of alphabetic characters, numbers, and underscores, as long as they don’t start with a number.

let x = 5
let Y = 10
let _ = 15
let _z = 15
let _Z_1 = 35
let Οι_χαρακτήρες_που_δεν_είναι_ASCII_λειτουργούν_επίσης =
"non-ASCII characters also work"

§Types

mechylang has the following types:

  • Integer
  • Float
  • Boolean
  • String
  • Array
  • Function
  • Unit

§Arithmetic Operations

mechylang supports the following arithmetic operations:

  • Addition (+)
  • Subtraction (-)
  • Multiplication (*)
  • Division (/)
  • Remainder (%)
  • Negation (-)
  • Bitwise Or (|)
  • Bitwise And (&)
  • Bitwise Xor (^)
  • Bitwise Not (~)
  • Bitwise Left Shift (<<)
  • Bitwise Right Shift (>>)
let a = 5
let b = 10
assert_eq(a + b, 15)
assert_eq(a - b, -5)
assert_eq(a * b, 50)
assert_eq(a / b, 0)
assert_eq(a % b, 5)
assert_eq(-a, -5)
assert_eq(10 | 3, 11)
assert_eq(10 & 3, 2)
assert_eq(10 ^ 3, 9)
assert_eq(~10, -11)
assert_eq(10 << 1, 20)
assert_eq(10 >> 1, 5)

§Comparison Operations

mechylang supports the following comparison operations:

  • Equal (==)
  • Not Equal (!=)
  • Less Than (<)
  • Less Than Or Equal (<=)
  • Greater Than (>)
  • Greater Than Or Equal (>=)
  • Logical And (&&)
  • Logical Or (||)
  • Logical Not (!)
assert_eq(5 == 5, true)
assert_eq(5 != 5, false)
assert_eq(5 < 10, true)
assert_eq(5 <= 10, true)
assert_eq(5 > 10, false)
assert_eq(5 >= 10, false)
assert_eq(true && false, false)
assert_eq(false || true, true)
assert_eq(!true, false)
§Unit ()

mechylang has a special type () called Unit. It is the bottom type, and is used to represent the absence of a value. It is similar to void in C, or () in Rust.

Unit is the return type of a function that doesn’t return anything.

fn foo() {
   // This function returns unit
}

assert_eq(foo(), ());

§Functions

In mechylang, functions are first class citizens. This means that they can be passed as arguments to other functions, and returned from other functions.

Functions can be declared in 2 ways:

  • As a function declaration using the fn <name>(<args>) { <body> } syntax
  • As an anonymous function using the fn(<args>) { <body> } syntax, which can be assigned to a variable

A function declaration looks like this:

fn add(a, b) {
    a + b
}

assert_eq(add(5, 10), 15)

An anonymous function looks like this:

fn(a, b) {
   a + b
}

// You can also assign an anonymous function to a variable
let add = fn(a, b) {
   a + b
}

assert_eq(add(5, 10), 15)

The return keyword can be used to return early from a function.

let add = fn(a, b) {
  return a + b
  // This line is never reached
  a - b
}

assert_eq(add(5, 10), 15)

The last expression in a function is implicitly returned.

fn is_even(x) {
  if x % 2 == 0 {
    return true
  }
  false
}
assert_eq(is_even(10), true)
assert_eq(is_even(11), false)

Functions can be passed as arguments to other functions.

fn apply(f, a, b) {
 f(a, b)
}

let add = fn(a, b) {
 a + b
}

assert_eq(apply(add, 5, 10), 15)
// You can also use an anonymous function without binding it to a variable
assert_eq(apply(fn(a, b) { a - b }, 5, 10), -5)

Functions can be returned from other functions.

fn make_adder(a) {
    return fn(b) {
        a + b
    }
}

let add_five = make_adder(5)
let remove_five = make_adder(-5)

assert_eq(add_five(10), 15)
assert_eq(remove_five(10), 5)

§Function declarations are hoisted

Functions declared with the fn <name>(<args>) { <body> } syntax are hoisted.

This means that you can call a function before it is declared.

assert_eq(add(5, 10), 15)
fn add(a, b) {
   a + b
}

Functions declared with the let <name> = fn(<args>) { <body> } syntax are not hoisted.

assert_eq(add(5, 10), 15) // Results in `Identifier not found: add`
let add = fn(a, b) {
  a + b
}

§Built-in functions:

mechylang has a few built-in functions, to learn more about them, check out the builtins module.

Built-in functions can be called like any other function. And even passed as arguments to other functions.

let apply = fn(f, x) {
    f(x)
}

assert_eq(apply(len, "Hello World!"), 12)

§Statements and Expressions

In mechylang, almost all statements are expressions. This means that they return a value.

§Blocks

Blocks are expressions and return the value of the last expression in the block.

let x = {
   let y = 5
   y * 2
}

assert_eq(x, 10)
§If/Else

The if statement is an expression and returns a value. The value of the if expression is the value of the last expression in the block that is executed.

let x = if (true) { 5 } else { 10 }
assert_eq(x, 5)
let x = if (false) { 5 } else { 10 }
assert_eq(x, 10)

If none of the branches are executed, the value of the if expression is unit.

let x = if (false) { 5 } // A false condition without an else branch returns unit
assert_eq(x, ())
§Loops

The while and for loops are also expressions and return a value. The value of the loop expression is the value of the last iteration.

let x = 0
let y = while (x < 5) {
    x = x + 1
    x * 2
}
assert_eq(y, 10)
let x = for i in 0..5 {
   i * 4
   // loops return the value of the last iteration
}

assert_eq(x, 16)
§Breaking with a value

You can break out of a loop with a value using the break keyword.

let x = for i in 0..5 {
  if i == 3 {
    break i
  }
  i * 4
}

assert_eq(x, 3)

Statements (like let) that do not produce a value return unit which is represented by ().

let x = {
  let y = 5
}
assert_eq(x, ())

§Blocks

Blocks are a list of expressions that are evaluated sequentially. Blocks themselves are expressions, and the last expression in a block is returned.

let x = {
   let a = 5
   let b = 10
   a + b
}

assert_eq(x, 15)

In blocks you can also use the return keyword to return early.

let x = {
  let a = 5
  let b = 10
  return a + b
  // This line is never reached
  a - b
};

assert_eq(x, 15)

Returning from a bloct within a function will return from the function.

let add = fn(a, b) {
    {
        return a + b
        // This line is never reached
        a - b
    } // Since blocks are expressions and we want to evaluate this block as a statement
    // we need to add a semicolon at the end.

    // This line is never reached
    2 * (a + b)
}

assert_eq(add(5, 10), 15)

§Arrays

Arrays are declared using the [] syntax. Arrays can contain any type of object, including other arrays and functions.

let a = [1, 2, 3]
let b = [1, 2, 3, [4, 5, 6]]
let c = [1, 2, 3, fn(a, b) { a + b }]

Arrays can be accessed using the [] operator.

assert_eq([1, 2, 3][0], 1)
assert_eq([1, 2, 3][1], 2)
assert_eq([1, 2, 3][2], 3)

Array values can be reassigned using the [] operator. This will replace the value at the given index with the new value. If the index is out of bounds, an error will be returned. The index must be an integer.

let a = [1, 2, 3]
a[0] = 10
assert_eq(a, [10, 2, 3])
a[1] = 20
assert_eq(a, [10, 20, 3])
a[2] = 30
assert_eq(a, [10, 20, 30])

To push an item to the end of an array, use the push method.

let a = [1, 2, 3]
a.push(4)
assert_eq(a, [1, 2, 3, 4])

Or to remove an item from the end of an array, use the pop method.

let a = [1, 2, 3]
assert_eq(a.pop(), 3)
assert_eq(a, [1, 2])
assert_eq(a.pop(), 2)
assert_eq(a, [1])

To learn more about array methods, check out the array module

§Strings

Strings are declared using the "" syntax. Strings can contain any unicode character, including emojis.

let a = "Hello World!"
let b = "👋🌎"
let c = a + b
assert_eq(c, "Hello World!👋🌎")

§Examples

To see some examples, check out examples

Re-exports§

pub use lexer::Lexer;
pub use lexer::Token;
pub use lexer::TokenKind;
pub use lexer::Span;
pub use parser::Parser;
pub use parser::Expression;
pub use parser::Statement;
pub use parser::Program;
pub use evaluator::EvalConfig;
pub use evaluator::Evaluator;
pub use evaluator::Object;
pub use evaluator::Environment;

Modules§

docs
A module containing docs specific to the Mechylang language itself.
evaluator
lexer
parser
pretty_errors
test_utils

Macros§

trace

Enums§

Error

Type Aliases§

Result