01
Syntax
A program is a sequence of forms. MLisp reads the complete input before evaluation, so a syntax error anywhere prevents the whole input from running.
| Form | Meaning |
|---|---|
42 -1.5 1e3 | Double-precision numbers. |
"text" | Strings. Supports \n, \r, \t, \\ and \". |
true false | Boolean values. Only false is falsey. |
nil | The absence of a value. It is truthy. |
:get :timeout-ms | Keywords. They evaluate to themselves. |
{:a 1 "b" 2} | An immutable map with alternating keys and values. |
name buffer/find + | Symbols, resolved in the current lexical scope. |
(f a b) | A list. Evaluation calls f with a and b. |
[1 2 3] | A vector. Its items evaluate from left to right. |
'form | Shorthand for (quote form); returns the form unevaluated. |
,form | Unquote syntax, valid inside a match pattern. |
; comment | A comment running to the end of the line. |
Symbols are case-sensitive and may contain letters, digits and +-*/<>=!?_/. A slash is an ordinary character; names such as buffer/find use a convention rather than a language-level module system.
02
Lists & vectors
Lists and vectors are ordered sequences. They support the same access and transformation functions, but remain distinct types.
Creating sequences
Square brackets create a vector and evaluate each item from left to right. The vector function is the dynamic equivalent. Use list to construct a list from evaluated arguments.
[1 (+ 1 1) 3] ; [1 2 3]
(vector 1 2 3) ; [1 2 3]
(list "red" "green") ; ("red" "green")
Parentheses normally mean a function call, so literal list data must be quoted. Quoting prevents every item in the form from being evaluated.
'(ready waiting done)
(quote (ready waiting done))
| Operation | Behavior |
|---|---|
(list values...) | Create a list from evaluated arguments. |
(vector values...) | Create a vector from evaluated arguments. |
(first sequence) | Return the first item. An empty sequence is an error. |
(rest sequence) | Return everything after the first item. The result keeps the input type; an empty sequence stays empty. |
(at sequence index) | Return the item at a zero-based integer index. An out-of-range index is an error. |
(length sequence) | Return the number of items. |
(empty sequence) | Return whether the sequence has no items. |
(map function sequence) | Transform every item and preserve the collection type. |
(filter predicate sequence) | Keep items for which the predicate is not false, preserving type. |
(sort sequence less) | Stable-sort with a comparison function and preserve type. |
(at [10 20 30] 1)
; 20
(map (fn (n) (* n 2)) [1 2 3])
; [2 4 6]
(rest '(a b c))
; (b c)
Structural equality compares lists and vectors item by item, but a list never equals a vector—even when their contents are identical.
03
Keywords & maps
Keywords are case-sensitive values distinct from symbols and strings. They cannot be binding or parameter names. Map keys must be strings or keywords; values can be any MLisp value.
(def user
{:name "Ada"
:role :admin})
(:name user) ; "Ada"
(:theme user "system") ; "system"
Calling a keyword is shorthand for get. Because keywords are ordinary values, expressions such as (map :name rows) work as expected.
| Expression | Result |
|---|---|
(hash-map :a 1) | {:a 1} |
(get {:a 1} :a) | 1 |
(get {} :a 42) | 42 |
(:a {:a 1}) | 1 |
(:a {} 42) | 42 |
(contains? {:a nil} :a) | true |
(assoc {:a 1} :b 2) | {:a 1 :b 2} |
(dissoc {:a 1} :a) | {} |
(keys {:a 1 :b 2}) | [:a :b] |
(vals {:a 1 :b 2}) | [1 2] |
(length {:a 1}) | 1 |
(empty {}) | true |
Map expressions evaluate keys and values from left to right. Duplicate keys retain their final value and original insertion position. Maps compare by entries regardless of order, while display, keys and vals preserve insertion order. assoc and dissoc return new maps.
get returns an explicitly stored nil even when given a default. Use contains? when you need to distinguish a missing key from a stored nil.
04
Evaluation
Function arguments evaluate from left to right before the function is called. Special forms control their own evaluation:
ifevaluates its condition and exactly one branch.beginevaluates body forms in order and does not create a scope.quoteevaluates nothing.defevaluates its value and binds the result.
Each if branch is one expression. Wrap multiple forms in begin. Remember that only false is falsey: nil, zero, empty strings and empty collections are all truthy.
05
Pattern matching
match evaluates its subject once, tries clauses in order and evaluates the first matching body in a fresh lexical scope. The final body form is returned; no matching clause is an error.
(match response
({:status :ok :body body}
(parse body))
({:status :error :code code}
(log code)
false)
({}
false))
- A map pattern is a subset. The subject may contain keys not named by the pattern;
{}matches any map. - Strings, keywords, numbers, booleans and
nilmatch by value. - A symbol binds the matched value. Repeating it requires equality.
_is an ordinary binding, not a wildcard.- A quoted value such as
'readymatches exactly. - A comma-prefixed expression evaluates in the scope surrounding
matchand compares its result with the subject.
(let ((expected 200))
(match response
({:status ,expected :body body} body)
({} false)))
Map-pattern keys must currently be literal strings or keywords. Bare list and vector patterns are unsupported; quote them to match an exact value. Errors and side effects inside unquoted expressions behave normally.
06
Scope & binding
MLisp uses lexical scope. def creates a binding in the current scope. set updates the nearest existing binding, searching outward, and errors if the name has never been defined.
let creates a new scope and binds names sequentially, so each binding can see those before it. begin does not create a scope.
(def counter
(let ((n 0))
(fn ()
(set n (+ n 1))
n)))
(counter) ; 1
(counter) ; 2
A closure retains the scope where it was created for as long as the function remains reachable. The runtime releases unreachable scopes between evaluations.
07
Functions
fn creates a function with fixed parameters and one or more body forms. The last form supplies the return value. Calling a function with the wrong number of arguments is an error.
(def twice
(fn (f x)
"Apply f to x twice."
(f (f x))))
(twice (fn (n) (* n 2)) 5) ; 20
A string in the first position of a multi-form body is the function’s docstring. If the string is the only body form, the function simply returns it. Functions are values: they can be passed, stored in collections, returned by other functions and retained by host functions.
08
Values & equality
= compares values structurally. Numbers compare by value; strings and symbols by text; lists and vectors item by item. A list never equals a vector. Booleans and nil compare by identity, and functions are equal only to themselves.
Host handles supplied by MEdit compare equal when they refer to the same object, even if that object has since been renamed. str converts any value to its printed representation. print writes values separated by spaces and emits strings without quotes.
09
Errors & limits
Errors stop evaluation immediately. Messages identify the source—file, console or command—and the line and column of the failing form. Output, definitions and editor changes made before an error are not rolled back.
Reaching a limit is an ordinary evaluation error. These limits protect the editor’s interface thread; they are guardrails, not a security sandbox.