Mylalang.

Mylalang

Mylalang is a LISP implemented in Rust.

TODO: Document head and tail

Content

Table of contents generated with markdown-toc

Usage

Firstly, clone the repository to a local directory

1git clone git@github.com:APiercey/mylalang.git
2cd mylalang

Mylalang comes with two binaries: an interactive REPL and a interpreter.

Interactive REPL

The REPL can be started by running:

1cargo run --bin imy

Interpreter

The REPL can be started by running:

1cargo run --bin imy

Types

Only primitive types exist in Mylalang. Constructing types does not exist.

Arithmetic Operators

Addition

1(+ 1 2)
2=> 3

Subtraction

1(- 7 2)
2=> 5

Multiplication

1(* 10 2)
2=> 20

Division

1(/ 2 10)
2=> 20

List Processing

All operators work against their arguments as lists, reducing to a result:

1(+ 1 2 3 4 5)
2=> 15
3
4(- 1 2 3 4 5)
5=> -13

Relational Operators

Equality

1(= 1 1)
2=> true
3
4(= 2 1)
5=> false

Greater than

1(> nil 1)
2=> false
3
4(> 1 nil)
5=> true

Less than

1(< 1 nil)
2=> false
3
4(< nil 1)
5=> true

Greater than or equal to

1(>= 1 1)
2=> true
3
4(>= 2 1)
5=> true
6
7(>= 1 2)
8=> false

Less then or equal to

1(<= 1 1)
2=> true
3
4(<= 1 2)
5=> true
6
7(<= 2 1)
8=> false

Comments

Comments start with ;

 1; This is a comment!
 2
 3; This code does not execute
 4; (def a "123")
 5
 6a
 7
 8thread 'main' panicked at '"a" does not exist within this scope
 9
10; oops :)

Binding values

The def keyword allows bind a value (and functions but more on that later) to a name:

1(def a 23)
2=> <#def "a">
3
4(def b 2)
5
6(+ a b)
7=> 23

Functions

In Mylalang, functions are a primitive type. They can be bound to a name and used as a value. Through this, the language supports both anonymous and named functions.

Named Functions

The def keyword can be used to bind a function to a name

1(def double (fn [a] a * 2))
2=> <#def "double">
3
4(double 2)
5=> 4

Functions can take multiple parameters

1(def greet (fn [firstname lastname] (+ "Hello " firstname " " lastname)))
2=> <#def "greet">
3
4(greet "John" "Travolta")
5=> Hello John Travolta

Anonymous Functions

Anonymous functions are called without binding it to a name. An example makes this easier to explain

The function below doubles a given number:

1(fn [a] (* 2))
2=> <#anonfunc [[<#def "a">]]>

The return value is a function. Like this, it’s a bit useless but it provides the foundation of using functions as values. They can be called immediately or passed as a function:

Executing an anonymous function

1((fn [fruit] (+ "My favorite fruit is " fruit)) "Banana")
2=> My favorite fruit is Banana

Comparing to its named counterpart

1(def favfruit (fn [fruit] (+ "My favorite fruit is " fruit)))
2=> <#def "favfruit">
3
4(favfruit "Banana")
5=> My favorite fruit is Banana

Passing functions as values

1(def double (fn [a] (* a 2)))
2=> <#def "double">
3
4(def applyfunc (fn [f a] (+ "The value of the applied function is: " (f a)))
5=> <#def "applyfunc">
6
7(applyfunc double 21)
8=> The value of the applied function is: 42

Local Binding

It is possible to bind local scoped variables to a function using the let keyword. This keyword functions the same as def but can only be used within a function.

let is a function which takes multiple arguments. The first is list of name and value pairs and binds the name in the first position to a value in the second position of each pair in the list.

The remaining arguments can be function calls with the last function to return a value. This is used when you want to execute side effects or inspect a result.

1# This example needs to be compressed into a single line to execute correctly in the REPL.
2
3(def shifter (fn [i]
4  (let [x (* i i)
5        y (+ i i)
6        z (if (> x 50) (- x y) (+ x y))]
7    (+ "Final value is " z))))
8    
9=> <#def "shifter">

In the example above, x equals the value if i multiplied by i.

1(shifter 23)
2=> Final value is 483

Function Overloading

It’s possible to bind multiple functions to the same name if they accept different parameters.

 1(def greeter (fn [name] 
 2  (let [greeting (+ "Hello " name)]
 3    (inspect greeting))))
 4=> <#def "greeter">
 5  
 6(def greeter (fn [first_name last_name]
 7  (let [greeting (+ "Hello " first_name " " last_name)]
 8    (inspect greeting))))
 9=> <#def "greeter">
10  
11(def greeter (fn [salutation first_name last_name]
12  (let [greeting (+ "Hello " salutation ". " first_name " " last_name)]
13    (inspect greeting))))
14=> <#def "greeter">
15
16(greeter "Alan")
17=> Hello Alan
18
19(greeter "Alan" "Turing")
20=> Hello Alan Turing
21
22(greeter "Dr" "Alan" "Turing")
23=> Hello Dr. Alan Turing

This allows to build boundaries in a very convenient way, particularly with recursion.

Aliasing

Because all types - including functions - are just values, it is possible to bind a name to another name using def.

 1(def hey (fn [] (inspect "Hey")))
 2=> <#def "hey">
 3
 4(def sayhey hey)
 5=> <#def "sayhey">
 6
 7(sayhey)
 8Hey
 9=> Hey
10
11(def no_i_made_this "A very fancy string")
12=> <#def "no_i_made_this">
13
14(inspect no_i_made_this)
15=> "A very fancy string"

Inspecting

Mylalang lets you inspect a value and pass it through to calling functions.

Inspecting simple results

1(inspect "This is an inspection!")
2
3This is an inspection!
4=> This is an inspection!

The output in a REPL shows a printed result and then shows the value of the executed statement.

Using inspect inside a function

1(def suspiciousfunction (fn [a]
2  (let [r (* a a)]
3    (inspect (+ "Result is strange... " r))
4    r)))

The do Function

The do function lets you execute multiple functions without the need of a let function. It’s used when you want to execute side effects but have no need for let local bindings.

 1(def saygarbage (fn [a]
 2  (do (inspect "Hello world")
 3      (inspect "foo bar")
 4      a)))
 5
 6=> <#def "saygarbage">
 7(saygarbage "123")
 8Hello world
 9foo bar
10=> 123

Conditionals

There are two conditional statements:

1(if (< 1 10) "One is lower than ten" "Something is fishy..")
2
3=> One is lower than ten
1(unless (= 8 8) "We've broken math" "8 always equals 8")
2
3=> 8 always equals 8

Files

Reading Files

The readfile function will read the contents of a file. The value returned is a string.

1; echo "Hello world" > file.txt
2
3(readfile "file.txt")
4
5=> Hello world

Importing

Importing named valued (bound through def) in another file is possible using the import function, which binds the imported functions to named in the local scope.

 1# math.my
 2(def double (fn [a] (* a 2)))
 3
 4# main.my
 5(import "math.my")
 6
 7=> <#def "double"> 
 8
 9(double 99)
10
11=> 198

Recursion and Loops

Recursion is used for looping in Mylang.

Example of a function using recursion create a loop with an exit condition

 1(def start 0)
 2
 3(def inc (fn [i] 
 4  (+ i 1)))
 5
 6(def loop (fn [i] 
 7  (if (<= i 10) 
 8    (loop (inc (inspect i)))
 9    (inspect "finished"))))
10
11(loop start)

The cons Function

The cons operator is function which constructs a list from another value. An example of its usage is to append a value to a list.

1(cons "This" ["is" "my" "list"])
2=> [[This, is, my, list]]

The list Function

The () syntax is literally a list which executes a context. The context being the first item in the list.

For this reason, it is difficult for Mylalang to understand if you want to construct a list or execute a some context, such as a bound function. The list function solves this issue by creating a list for you.

1(list 1 2 3 5 7 11 13)
2=> ([1, 2, 3, 5, 7, 11, 13])

Lists can be operated on like any other primitive type.

1(+
2  (list 1 2 3)
3  (list 4 5 6))
4  
5=> ([1, 2, 3, 4, 5, 6])

The real power of lists comes out when used with recursion where they are applied as a list of arguments. See the apply function.

: Operator

The short form of cons is the : operator. It is used the same as above.

1(: 1 [2 3])
2=> [[1, 2, 3]]

The & Operator and the apply Function

The apply function is a powerful function that allows us to expand a list as arguments for a function. It accepts the function as the first argument and a list of values-as-parameters as the second argument.

An example using the + operator.

1; The operation we want to perform
2(+ 1 2 3)
3=> 6
4
5(apply + (list 1 2 3))
6=> 6

In most cases, we want to operate on value individually. The & (capture) operator allows us to capture all remaining unnamed arguments as a list.

 1(def cap_example (fn [first & rest]
 2  (do (inspect "First arg")
 3      (inspect first)
 4      (inspect "The rest of the args")
 5      (inspect rest))))
 6
 7=> <#def "cap_example">
 8
 9(cap_example 1 2 3 4 5) ; Calling the function
10First arg               ; inspecting first argument
111
12The rest of the args    ; inspecting the remaining arguments
13([2, 3, 4, 5])          
14
15=> ([2, 3, 4, 5])       ; Final return result of the function

With recursion, it becomes even more powerful. The implementation of max shows how recursion, cons, apply, and & can be used together.

1(def max (fn [a] a))
2(def max (fn [a & rest]
3  (let [b (apply max rest)] 
4    (if (>= a b) a b))))

Evaluating Code

The eval function accepts a string and executes it as code. The return result is always nil but any functions or values bound to a name will be bound to the local scope.

A simple example using inspect:

1(eval "(def value 1)")
2=> <#def "value">
3
4(inspect value)
51
6=> 1

Complex Example

A more complex example when you would want to eval code, is when the code is in another file or serialized into a string. The import function is implemented in Mylalang itself using only eval and readfile.

1(def import (fn [file_name] 
2  (let [contents (readfile file_name)]
3    (eval contents))))

Native functions

Most functions covered are implemented Mylalang itself. While there is no standard library, there are additional functions which come with Mylalang:

min

Returns the smallest value from a list.

1(min 23 77 99)
2
3=> 23

max

Returns the largest value from a list.

1(min 23 77 99)
2
3=> 99

double

Doubles a value! Originally implemented to provide a function to test with.

1(double 21)
2
3=> 42

map

The map function maps a function over a list. Accepts a function as its first argument and a list for the remaining arguments.

1(map double 1 2 3 4 5)
2
3=> ([2, 4, 6, 8, 10])

reduce

Reduces a list of values to the right. Accepts a function in it’s first position, accumulator in it’s second. The rest of the arguments are captured.

1(reduce + 0 1 2 3 4)
2
3=> 10

More functions exist in the language which can be found by checking out the native implementation files.