Skip to content

kiryk/mlisp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mlisp Early Documentation

Data Types

Name Reference type Index type
Nil no no
C Function no no
Number no no
Symbol yes no
String yes no
List yes yes
Weak reference * no no

* weak references are only used internally by the interpreter

The mlisp interpreter doesn't manage every object through reference count, some values are only copied, and only the reference types are cared by the garbage collector. This is intended to save memory, since non-reference types are usually of similar size as pointers.

Based on everything above, types can be put in the following hierarchy:

  • value
    • object (reference types)
      • list
      • symbol
      • string
    • c function
    • number
    • weak (weak reference)
    • nil

The terms above will be used in next section.

Built-in functions

This section describes behaviour of built-in functions. The following conventions will be used here:

Example Name Meaning
(f) → type Returns function f returns value of type type
argument... Repeatable argument may be repeated
[argument] Optional argument is optional
[argument argument] Group both arguments are optional, but have to be passed together
[argument argument]... Repeatable group like above, but the arguments can be repeated
A|B One of the argument has to be of type or name A or B

Beware, the behaviour for most of these functions is still unspecified if they are given wrong type of arguments, however, at worst it only causes forever loops and null-pointer dereference, otherwise the functions just return wrong results without any other side effects.

Base system

This sub-section describes the most basic functions in the language, without which the intepreter couldn't work correctly.

(eval value) → value

eval takes value of any type, tries to evaluate it, and returns the result.

(read) → value

read reads the standard input, parses it as mlisp data, and returns it without evaluation.

(import string) → value

import runs another mlisp script in the same context as the main script. It can be used for accessing data structures and functions defined in another file.

The return value is the value of the last expression in the imported file.

(print [value]...) → nil

print prints its arguments to the output, it doesn't dive recursively into indexed types. It returns nil after it finishes its task.

(quote value) → value

quote returns its only argument as a literal, unevaluated token.

(def symbol value) → nil

def creates new variable in the local scope with the name of the first argument, then evaluates its second argument and assigns the result to the variable.

The function returns nil.

(set value value) → nil

set evaluates it's arguments, and assigns the second one, to the first one. The first argument might be a variable name or a reference to a list or map field, for instance:

(def l (list "a" "b" "c" "d")) ; creates a new list
(print (list-get l 2))       ; prints 'c'

(set (list-get l 2) "g")     ; sets the "c" inside the list to "g"
(print (list-get l 2))       ; prints 'g'

The difference between set and def is that set will look for an existing value rather than create one, if it doesn't find it, it will look for it in the global scope. But since the interpeter treats every non-declared variable as it were containing nil, the set function -- given a symbol as its first argument -- can be actually used to declare global variables from inside functions.

The function returns nil.

(fn ([symbol]...) [value]...) → list

fn creates and returns a new lambda, its first argument is assumed to be a list of symbols to be used as argument names, the next arguments are the lambda's body.

Every lambda, if called, returns the value of its last expression.

The idiom to declare and call functions in mlisp is:

(def twice (fn (n)
  (mul 2 n)))      ; declares function 'twice'

(print (twice 9))  ; prints '18'

Since lambdas are first-class values, and actually just lists, they can be passed as arguments to other lambdas or returned by them. The user can use this property to create solutions like the following one:

(def double-op (fn (op n)
  (op n n)))

(def twice (fn (n)
  (double-op add n)))

(def squared (fn (n)
  (double-op mul n)))

(print (twice 4) (squared 4)) ; prints '8 16'

Debugging system

This section describes pars of the language that are helpful when some internal actions of the intepreter are to be figured out.

(debug) → nil

debug prints information on number of objects currently allocated in the memory, notice it doesn't include non-reference types.

The function returns nil.

(write [value]) → nil

write prints it's only argument as an s-expression, if it's not given any arguments, it will print the contents of a scope it is run in.

The function returns nil.

Map and list manipulation system

This section describes functions used to manage maps and lists.

(list [value]...) → list

list creates list of its evaluated arguments and returns it.

(list-get index number) → value

list-get returns value of nth element inside the given index.

Remember that lambdas are just lists, so they can be manipulated with some help of this function.

(map [number|string|symbol value]...) → list

The arguments for map have to come in key-value pairs, the function puts them inside a newly created map, with the values evaluated, and returns it.

The returned value is a list understanable by map family of functions, but since it's a list, it can be managed by list functions as well.

(map-get list number|string|symbol) → value

map-get returns value of the element assigned to the given key (the second argument) in the given map (the first argument).

Expression system

This section describes functions used for evaluating mathematical and comparement expressions as well as functions used for control flow.

For these functions only nil is treated and returned as a false value, all the other values, including empty strings or 0s are truths.

(len object) → number

len returns length of given objects, if it's a string or a symbol, it returns the number of its characters, if it's a list, it returns the number of values inside it.

(while value [value]...) → value

while evaluates all its arguments as long as the first one is true, it returns the last value of the last expression in its body.

(if value value [value]) → value

if evaluates its first argument and checks whether its true, if it is, it evaluates and returns its second argument, otherwise it returns the evaluated value of the third argument. If the third argument isn't given, it is assumed to be nil.

(do [value]...) → value

do evaluates every each of its arguments once, and returns the value of the last one.

This function is obviously not directly related with mathematical expressions, but it can be used to group expressions; that might be especially handy inside ifs.

(+|-|*|/|% number [number]...) → number

All of the functions above take their first argument, and then apply the appropriate mathematical operation to it -- using the next argument; if there are more arguments, the operation is repeated with the current result and the next arguments.

(> number [number]...) → number|nil

> checks whether it's arguments are sorted in decreasing order.

If so, it returns the last value on the list, otherwise it returns nil.

(< number [number]...) → number|nil

< checks whether it's arguments are sorted in rising order.

If so, it returns the last value on the list, otherwise it returns nil.

(>= number [number]...) → number|nil

>= checks whether it's arguments are sorted in non-rising order.

If so, it returns the last value on the list, otherwise it returns nil.

(<= number [number]...) → number|nil

<= checks whether it's arguments are sorted in non-decreasing order.

If so, it returns the last value on the list, otherwise it returns nil.

(= number [number]...) → number|nil

= checks whether all of its arguments are of the same numerical value.

If so, it returns the last value on the list, otherwise it returns nil.

(!= number [number]...) → number|nil

!= checks whether any neighbouring pair of its arguments is of different value.

If so, it returns the last value on the list, otherwise it returns nil.

(or value [value]...) → value|nil

or checks if any of arguments passed to it are of type other than nil, if so, it returns the first non-nil value met, otherwise it returns nil.

(and value [value]...) → value|nil

and checks if any of arguments passed to it are of type nil, if so, it returns nil, otherwise it returns the last value passed to it.

(not value) → number|nil

not checks if the only argument passed to it is nil, if so, it returns a number, otherwise it returns nil.