Design Document for a Lispy Language
Lisp is an exercise in simplicity. Let's design our own.
Tokens
These are the tokens allowed:
(
-- open parens)
-- close parens/[-_a-zA-Z0-9.:?!><7*]+/
-- unquoted literal, such ashello
,test-function
,+
,1234
,*hello*
./\"(\\.|[^"])*\"/
-- double-quoted literal, such as"hello world!\n"
,"{\"hello\": \"world\"}"
./\'(\\.|[^'])*\'/
-- single-quoted literal, such as'hello world!\n'
,'{"hello": "world"}'
.
Everything else matching /\s/
is whitespace, which is discarded by the tokenizer.
Grammar
The grammar goes as follows:
program := [ expressions ] expressions := expression [ expressions ] expression := "(" atoms ")" atoms := atom [ atoms ] atom := scalar-literal | list-literal | expression scalar-literal := unquoted-literal | double-quoted-literal | single-quoted-literal list-literal := "'(" atoms ")"
Data Types
Value types -- Every value is either a scalar or a list. A scalar is always represented as a string, so there is no dedicated data type for numbers, characters or booleans. A list is a finite sequence of values, meaning that it can contain other lists.
Null value -- There is a special value for nil
, intended for expressions that do not return a value.
Truthiness and falseness -- The only values considered to be false are: nil
, the empty string ""
and the empty list '()
. All other values are true.
Equality -- The nil
value is only equal to itself. A string is equal to another string iff they are both empty or they have the same contents (despite being unquoted, double-quoted or single-quoted). A list is equal to another list if they are both empty or have the same contents. The nil
value, a string or a list are never equal to another.
Functions and Macros
Functions are defined as follows:
(defun function-name (arg1 arg2 [...] argn) function-body)
A function may be called from an expression:
(function-name expr1 expr2 expr3 [...] exprn)
On execution, this expression will be the result of evaluating the function-body
by substituting the values of arg1
for the evaluation of the expr1
expression, arg2
for the evaluation of the expr2
expression, and so on.
Macro are similar, but each of the parameters are passed as-is, without being evaluated first. They may be evaluated within an macro by calling eval
.
(defmacro my-if (condition if-true if-false) (if (eval condition) (eval if-true) (eval if-false)))
The macro above my-if
would have an equivalent behavior to the built-in if
. If that were defined as a function, it both if-true
and if-false
would each be evaluated before being passed into the if
, despite the truth value of the condition
.