Syntax

The syntax is really easy; almost everything is an expression except two things: variable and function definitions. Before we dive into the expression part let’s look at the general structure of variable and function definitions.

Variables, functions and constants

Variables

Variable definitions are structured like this

let <variable-name> = <definition>
Constants

Constants are like variables, the only difference is that they can’t be reassigned

# declare a constant
const MY_CONST = 1

# this will throw an error
MY_CONST = 2

Functions

Function definitions follow (almost) the exact same format

define <function-name>(<parameters>) = <definition>

Examples

Let’s look at some examples of variable, function and constant definitions

let n = 2
const ONE = 1
define f(x) = x + 1
let x = 2

define xplusone() = x + 1

Note that functions can have as many parameters as you wish

define f(a, b, c, d, e, f, g) = a + b + c + d + e + f + g

You can even build complex things like a function which calculates the factorial of a number (n!)

define factorial(n) = if(n <= 1, 1, n * factorial(n - 1))

External functions

You might’ve noticed that we were using a function called "if" in the last example, "if" is one of the default external functions which is defined in native rust code. Here are all default external functions

# Prints out the first argument
print(output)

# Prints out the first argument PLUS a new line
println(output)

# Prints a new line
newline()

# Prints a space (' ')
empty()

# Returns the second argument if the first argument is 1 otherwise the third argument
if(condition, true, false)

# Reads a number from stdin (console)
input()

# Waits millis milliseconds
wait(millis)

In the near future you’ll be able to implement your own external functions.

Expressions

As mentioned before, almost everything in this language is an expression. All available expressions are

# Direct number value
0

# Retrieve value of variable x
x

# Negate x
-x

# Add x and y together
x + y

# Subtract y from x
x - y

# Multiply x by y
x * y

# Divide x by y
x / y

# x to the power of y
x ^ y

# Check if x and y are equal (1 if both are equal, 0 if not)
x == y

# Check if x and y aren't equal (0 if both are equal, 1 if not)
x =! y

# Check if x is bigger than y or equals (1 if x is equal or bigger, 0 if not)
x >= y

# Check if x is bigger than y (1 if x is bigger, 0 if not)
x > y

# Check if x is smaller than y or equals (1 if x is smaller or equals, 0 if not)
x <= y

# Check if x is smaller than y (1 if x is smaller, 0 if not)
x < y

# Assign n to x (gives back n)
x = n

Expressions can be embraced with parenthesis to run the expression in them and ignore precedence

(expression)

An example of this is

(1 + 2) * 3

Where part in a variable definition

This feature is currently unavailable

After the definition of a variable there can be a "where part", the general structure of a where part looks like this

where <var-name> = <definition>, ...

where parts are for when you don’t want to repeat yourself in a variable definition. For example

define f(x) = ...
let a = ...

let n = (f(a) + 3) * (f(a) + 2) + f(a)

this looks really messy because of all the f(x) invocations, using the where part we can make it look much more clean

define f(x) = ...
let a = ...

let n = (r + 3) * (r + 2) + r where r = f(a)

these variables that are only available in that specific variable definition where we define them in the where part are called "part variables". We can even have multiple part variables in one where part

define f(x) = ...
define g(x) = ...
let a = ...

let n = (rf + 3) * (rg + 2) + rf where rf = f(a), rg = g(a)

New lines and the pipe (|) operator

This feature is still under development and can cause crashes

If you want to continue an expression in a new line you can use the pipe (|) operator

let a = |
		2 + |
	2

You can write as many pipe operators as you wish on one line (they each count for a new line)

let a = ||||



	0

is the same as

let a = 0

or

let a = |
0

Caching

Making a function cached (the result will be stored and automatically retrieved when the arguments match) can be done by annotating it with the "cache" keyword

define cache f(...) = ...

This can be useful when doing things like factorial; but you should not cache every function! If you only ever call the function once or do simple things such as adding two numbers together then you should not make it cached. Caching can sometimes increase interpretion time!

As mentioned before it can be extremely useful when doing e.g. factorial calculation. Let’s look at an example

define factorial(n) = if(n <= 1, 1, n * factorial(n - 1))

define f(n, to) = if(n >= to, println(factorial(n)), println(factorial(n)) + f(n + 1, to))

f(1, 50)

This code will print out the factorials of 1 to 50, normally the interpretion time would take around ~190ms. But if we now add the cache keyword to it

define cache factorial(n) = ...

...

it will only take about ~60ms.

Comments

Commenting code can be done by putting a # at the start of a line

# This is a comment

Examples

Function to calculate factorial of number

define factorial(n) = if(n <= 1, 1, n * factorial(n - 1))

Function to calculate nth fibonacci number

define fibonacci(n) = if(n <= 0, 0, if(n == 1, 1, fibonacci(n - 1) + fibonacci(n - 2))

Factorial of user input

define factorial(n) = if(n <= 1, 1, n * factorial(n - 1))

println(factorial(input()))

Naming convention

Item Convention

Variables

camelCase

Functions

camelCase

Constants

UPPER_SNAKE_CASE

Special thanks to

  • joeyjoejoe#7320 for helping me fix the lexer issue

  • the entire together java server for helping me develop the syntax and other stuff

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details