This manual is for SL (version 0.1), a Simple Lisp interpreter.
Copyright © 2024 8dcc.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts.
A copy of the license should be included along with the manual. If not, see https://www.gnu.org/licenses/.
This section will explain some important concepts about the Lisp syntax, and about the interpreter itself.
All expressions have a single type that determines the kind of value it is currently storing. Types are usually capitalized and, in the manual, slanted.
See also Type-checking primitives and Type conversion primitives.
Some functions accept multiple numeric types, so in order to operate on them, they need to be internally converted to a Generic Number Type. Depending on the types and the values being converted, some information might be lost in the process (e.g. when converting really big integers).
Currently, the generic number type is Float, that is, a C double
. For
example, most arithmetic function accept arguments of any numeric type,
so if the list of arguments is not homogeneous, they are converted, and
a Generic Number Type is returned.
(+ 5 2.0)
⇒ 7.0
The C functions for handling Generic Number Types are defined as inline
in the expr.h header.
These variables are defined by default in the global environment.
These symbols are bound to a constant value, and can’t be redefined.
- Variable: `nil’
- <<nil>>
Represents the empty list, but is also used to denote the logical value false.
nil ⇒ nil '() ⇒ nil () ; Special case, not treated as function call ⇒ nil (eval nil) ⇒ nil (cons 1 (cons 2 nil)) ⇒ (1 2)
This symbol is very special, since it represents the empty list
()
even when quoted. The following four expressions evaluate to the same value: The quoted symbol'nil
, the symbolnil
, the quoted empty list'()
, and the empty list()
.(type-of nil) ⇒ List (type-of 'nil) ⇒ Symbol (null? 'nil) ⇒ tru (equal? 'nil nil) ⇒ tru
Note that this exception only applies to the symbol
nil
, it doesn’t happen with other symbols bound to the empty list.(define var nil) ⇒ nil (type-of 'var) ⇒ Symbol (null? 'var) ⇒ nil (null? var) ⇒ tru
- Variable: `tru’
- <<tru>>
Symbol that evaluates to itself, used for representing explicit truth in procedures returning predicates (see Logical primitives). There is no need for this symbol, since any non-nil expression represents truth, but it’s convenient.
tru ⇒ tru (eval tru) ⇒ tru
These variables are used by the interpreter itself for debugging purposes.
- Variable: `*debug-trace*’
- <<*debug-trace*>>
List of expressions that are traced when called. It’s not advised to change the value of
*debug-trace*
directly withdefine
, but instead use the standard library functiontrace
(see =trace=).(defun fact (n) (if (= n 0) 1 (* n (fact (- n 1))))) (define *debug-trace* (list fact)) (fact 3) 0: (fact 3) 1: (fact 2) 2: (fact 1) 3: (fact 0) 3: 1 2: 1 1: 2 0: 6 ⇒ 6
In the previous example, notice how the function itself is added to the list, not the symbol
fact
. This allows debugging anonymous functions and macros, as long as they match with theequal?
primitive (see =equal?=).(defun identity (e) e) ⇒ <lambda> > (equal? identity (lambda (e) e)) ⇒ tru (trace (lambda (e) e)) ⇒ "Trace enabled." (identity 5) 0: (identity 5) 0: 5 ⇒ 5
This section explains the different primitive procedures in SL. Primitive procedures are implemented in C.
These primitives are special forms, that is, special procedures whose arguments are not evaluated before the call. This way the procedures can operate on the un-evaluated expressions, before evaluating them manually if needed. The C primitives for this section are defined in prim_special.c.
A list is interpreted as a special form call when its first element is a special form symbol. Special form symbols are specified by an environment flag that can’t be currently set by the user. Special form symbols are also constant, so they can’t be redefined.
(defun special-form-symbol? (sym)
;; TODO: Check the symbol's flags in the environment.
...)
(defun special-form? (e)
;; Non-empty list whose first element is a special symbol.
(and (list? e)
(not (null? e))
(symbol? (car e))
(special-form-symbol? (car e))))
For more information on special forms, see Section 4.1.1 of /Structure and Interpretation of Computer Programs/[fn:: https://web.mit.edu/6.001/6.037/sicp.pdf#subsection.4.1.1].
- Special Form: quote expr
- <<quote>>
Return the argument, effectively delaying its evaluation.
(quote x) ⇒ x 'x ⇒ x (quote (+ 1 2)) ⇒ (+ 1 2)
Note that
'expr
is just syntactic sugar for(quote expr)
. This is handled in parser.c. - Special Form: backquote expr
- <<backquote>>
Return the argument, while allowing selective evaluation. Without using special unquote arguments, described below, it’s behavior is identical to
quote
. Note that multiple symbols are bound to the C primitive in the global environment:backquote
and =`=[fn::That is, the grave accent character (ASCII code 96).].As mentioned, the backquote is pretty special because it lets the user evaluate parts of the argument expression. There are two symbols that can be used as a procedure call for specifying which parts should be evaluated. The
,
symbol[fn::That is, the comma character (ASCII code 44).] is used for unquoting and the,@
symbol[fn::That is, the comma character (ASCII code 44) followed by the at sign (ASCII code 64).] is used for splicing.If an expression is unquoted (e.g
,expr
or(, expr)
), it will be evaluated bybackquote
. If an expression is spliced (e.g,@expr
or(,@ expr)
), it will be evaluated just like when unquoting, but instead of returning the list itself, the contents of the resulting list will be appended to an outer list. Therefore, you can only splice an expression if it evaluates to a list, and if the splice call was made within another list.The
,
and,@
symbols are bound in the global environment to note that they are reserved, but they cannot be used outside of abackquote
argument.Again, just like with
quote
, note that`expr
is just syntactic sugar for(` expr)
, and,expr
is syntactic sugar for(, expr)
. They are all handled in parser.c.`sym ⇒ sym ;; For showing how the parser expands them. (quote `(a ,b c d)) ⇒ (` (a (, b) c d)) (define var 123) `(a ,var b c) ⇒ (a 123 b c) `(a (b ,var) c ,var) ⇒ (a (b 123) c 123) (define my-list '(1 2 3)) `(a b ,@my-list c d) ⇒ (a b 1 2 3 c d) `(a b ,@(list 'X 'Y 'Z) c) ⇒ (a b X Y Z c)
Since the backquote evaluates each unquoted expression normally, you can nest backquotes without any special syntax:
`(hi ; "hi" quoted by the outer backquote. ,(if (< var 30) ; "if" Evaluated by the outer backquote. (+ 100 var) ; "+" evaluated depending on the "if". `(abc ; "abc" quoted by the inner backquote. ,var ; "var" evaluated by the inner backquote. xyz)) ; "xyz" quoted by the inner backquote. bye) ; "bye" quoted by the outer backquote.
In the previous example, if
var
was7
, the backquote would return(hi 107 bye)
, but ifvar
was35
, it would return(hi (abc 35 xyz) bye)
.Also note that none of this unquote functionality is available inside
quote
arguments, justbackquote
:'(,a b (c ,d) e) ⇒ ((, a) b (c (, d)) e) (define var 123) ⇒ 123 (define my-backquote-call '`,var) ⇒ (` (, var)) (eval my-backquote-call) ⇒ 123
- Special Form: define symbol expr
- <<define>>
Bind symbol to a value in the current environment.
Evaluates the second argument, and binds it to the first one. Returns the evaluated expression.
n ⇒ Unbound symbol: `n'. (define n 123) ⇒ 123 n ⇒ 123
As mentioned, it only operates on the current environment.
(define n 123) ⇒ 123 (define f (lambda () (define n 999) (list "Finished:" n))) (f) ⇒ ("Finished:" 999) n ⇒ 123
It is a special form because the first argument is not evaluated. This way, it doesn’t have to be quoted by the caller.
- Special Form: define-global symbol expr
- <<define-global>>
Bind symbol to a value in the top-most environment. For more information, see =define=.
(define n 123) ⇒ 123 (define f (lambda () (define-global n 999) (list "Finished:" n))) (f) ⇒ ("Finished:" 999) n ⇒ 999
- Special Form: lambda formals body…
- <<lambda>>
Return a new anonymous procedure.
The
lambda
primitive expects a list of formal arguments (which must be symbols) and one or more expressions (of any type) for the body.Expressions of type Lambda evaluate to themselves. When calling a lambda, each argument is evaluated and bound to its formal symbol, and each expression in the body of the function is evaluated in order, returning the last one.
(lambda (x) (* x 3)) ⇒ <lambda> ((lambda (x) (* x 3)) 5) ⇒ 15 (define f (lambda (x) (+ x 5))) ⇒ <lambda> (f 3) ⇒ 8
A keyword symbol
&rest
followed by a single symbol S, can be used in the formal argument list to indicate that the caller can provide extra non-mandatory arguments, and they will be stored in a list bound to the symbol S when making the call. If no extra arguments are provided when making the call, S is bound to the empty listnil
.(define f (lambda (a b &rest other) (list a b other))) ⇒ <lambda> (f 1 2 3 4 5) ⇒ (1 2 (3 4 5))
- Special Form: macro formals body…
- <<macro>>
Return a new anonymous macro.
The
macro
primitive expects a list of formal arguments (which must be symbols) and one or more expressions (of any type) for the body.Expressions of type Macro evaluate to themselves. Macros are generally similar to lambdas, but there are some key differences:
- When a macro is called, the arguments are not evaluated before applying it, so the macro can operate on the un-evaluated expressions directly, instead of on the values they compute. The first step of a macro call is binding the un-evaluated arguments to the formals.
- Macros don’t directly compute values, they instead build Lisp expressions that will be used to compute the actual values. The second step of a macro call is the macro expansion (see =macroexpand=). In this step, the macro is called just like a lambda, returning a Lisp expression.
- The last step of a macro call is evaluating the expanded expression, which will be used to compute the actual value returned by the macro.
In other words the general process when calling a lambda is:
Evaluate arguments -> Bind arguments -> Evaluate body `-----------------------------´ (Apply)
While the call process of a macro is:
Bind arguments -> Evaluate body -> Evaluate expansion `-----------------------------´ (Expand)
While the process of calling a macro is:
(macro (name) (list 'define name 123)) ⇒ <macro> (define my-macro (macro (name) (list 'define name 123))) ⇒ <macro> (my-macro some-name) ⇒ 123 (macroexpand '(my-macro some-name)) ⇒ (define some-name 123) some-name ⇒ 123
In the previous example, notice how we don’t have to quote
some-name
when callingmy-macro
. This is because, since macro arguments are not evaluated, the symbolsome-name
is passed to the macro, not the value bound to it. The macro is expanded to the list(define some-name 123)
, and then it’s evaluated.The special form
backquote
can be really useful in macros. See =backquote=.;; Without using backquote (defmacro my-macro (x y) (list 'if x (list 'func (list 'quote 'abc)) (list '+ '1 '2 y))) ;; Using backquote (defmacro my-macro (x y) `(if ,x (func 'abc) (+ 1 2 ,y)))
Just like lambdas, macros support the use of the
&rest
keyword in the formal argument list.For more information on how macros behave in this Lisp, see the Emacs Lisp manual.
- Special Form: begin &rest exprs
- <<begin>>
Evaluate each argument in order, and return the last result.
This primitive is a special form for various reasons. When making a normal procedure call, the arguments are not required to be evaluated in order, when calling
begin
, they are. The fact that it has to evaluate the expressions is helpful when combined with something likeapply
and a quoted expression (see =apply=).;; Arguments not evaluated because it's a special form. (begin (define n 123) (+ 1 2)) ⇒ 3 n ⇒ 123 ;; Arguments not evaluated because the list is quoted. (apply begin '((define n 456) (+ 1 2))) ⇒ 3 n ⇒ 456
Furthermore, it could be defined as a macro using
lambda
, with some limitations. For example, in the following macro version, calls todefine
would bind the variables in thelambda
environment, which does not happen in the special form version.(defmacro my-begin (&rest exprs) `((lambda () ,@exprs))) ⇒ <macro> (my-begin (define my-var 123) ; Only defined in body 'ignored-sym (+ 1 2 3)) ⇒ 6 my-var ⇒ Unbound symbol: `my-var'.
- Special Form: if predicate consequent alternative
- <<if>>
Return evaluated consequent or alternative depending on whether or not predicate evaluated to non-nil or not, respectively. See also =nil= and =tru=.
(if tru 'abc 'xyz) ⇒ abc (if nil 'abc 'xyz) ⇒ xyz (if (> 5 3) (+ 10 20) (- 60 50)) ⇒ 30
Note that the predicate is always evaluated, but only the consequent or the alternative is evaluated afterwards. This is a good example on why special forms are necessary, since a normal function call would have to evaluate the 3 arguments before applying
if
to them. - Special Form: or &rest exprs
- <<or>>
Evaluates each argument expression in order, and once it finds a non-nil result, it stops evaluating and returns it. Returns
nil
if all of them evaluated tonil
, or when called with no arguments.(or (> 1 2) (> 3 4) (> 5 6)) ⇒ nil (or (> 1 2) (> 3 4) 'hello) ⇒ hello (or) ⇒ nil
Note that this primitive does not need to be a special form, since it can be built with a macro and
if
.(defmacro my-or (&rest exprs) (if (null? exprs) nil ;; TODO: Don't overwrite "result", generate unique symbol. ;; NOTE: We could also use the `let' macro. `((lambda (result) (if result result (my-or ,@(cdr exprs)))) ,(car exprs))))
- Special Form: and &rest exprs
- <<and>>
Evaluates each argument expression in order, and if it finds a
nil
result, it stops evaluating and returnsnil
. If all arguments evaluated to non-nil, returns the last result. Returnstru
when called with no arguments.(and (> 1 2) (> 3 4) (> 5 6)) ⇒ nil (and (> 4 3) (> 2 1) 'hello) ⇒ hello (and) ⇒ tru
Just like with
or
, this primitive does not need to be a special form:(defmacro my-and (&rest exprs) (if (null? exprs) tru ;; TODO: Don't overwrite "result", generate unique symbol. ;; NOTE: We could also use the `let' macro. `((lambda (result) (if result ,(if (null? (cdr exprs)) 'result `(my-and ,@(cdr exprs))) nil)) ,(car exprs))))
If you have trouble understanding the nested backquotes, see =backquote=.
These primitives don’t fit into other categories. They are defined in prim_general.c.
- Function: eval expr
- <<eval>>
Evaluate the specified expression.
Different expression types have different evaluation rules:
- The empty list (
nil
) evaluates to itself. - Non-empty lists are evaluated as procedure calls.
- If the (un-evaluated)
car
of the list is a special form symbol (see *Special Forms), it passes the un-evaluatedcdr
to the corresponding special form primitive. - If the (evaluated)
car
of the list is a macro, the macro is called with the un-evaluatedcdr
of the list. - Otherwise, the arguments are evaluated and the procedure is called. If one argument fails to evaluate, evaluation stops.
- If the (un-evaluated)
- Symbols evaluate to their bound values in the current environment[fn::See also Section 3.2 of SICP.].
- Other expression types (numbers, strings, functions, etc.) evaluate to themselves.
Keep in mind that, since
eval
is a normal procedure, its arguments will be evaluated before the actual function call is made, so the user might need to use thequote
special form.(define var 123) ;; We are evaluating 123, which evaluates to itself. (eval var) ;; We are evaluating the symbol "var", which evaluates to 123. (eval (quote var))
The C primitive is called
prim_eval
, but the actual evaluation process is performed by the C functioneval
, defined in eval.c. - The empty list (
- Function: apply function arg-list
- <<apply>>
Apply a function to a list of arguments.
The first argument must be an applicable expression, that is, a Primitive, Lambda or Macro; and the second argument must be a list.
Again,
apply
is a normal procedure, so its arguments will be evaluated before the call. However, even thought the user might need to quote the argument list, the first argument must be a procedure, not a symbol.(apply '+ '(1 2 3)) ⇒ Error: Expected a procedure as the first argument, got 'Symbol'. (apply + '(1 2 3)) ⇒ 6
Just like with
eval
, the C primitive is calledprim_apply
, but it’s just a wrapper for the C functionapply
, defined in eval.c. It checks the type of thefunction
expression, and dispatches the call to the appropriate function for performing the actual application process. For more information, see =lambda= and =macro=. - Function: macroexpand quoted-expr
- <<macroexpand>>
Expand
quoted-expr
, a list representing a macro call. The evaluatedcar
of the list must be an expression of type Macro. The expansion of a macro is the expression returned by that macro before being evaluated. The expansion step of a macro call is the same as a normal lambda call, but the arguments are not evaluated before calling it.(defmacro inc (sym) (list 'define sym (list '+ sym 1))) ⇒ <macro> ;; Alternative, using backquote (defmacro inc (sym) `(define ,sym (+ ,sym 1))) ⇒ <macro> (define my-var 5) ⇒ 5 (macroexpand '(inc my-var)) ⇒ (define my-var (+ my-var 1))
Notice how the macro body just returns a list. That is the macro expansion. Calling a macro simply means evaluating the expanded expression. See also =macro=.
- Function: random limit
- <<random>>
Return a random number between zero and
limit
. The argument type must be numeric, and the returned number will share the same type.(random 5) ⇒ 4 (random 5.0) ⇒ 2.261398 (type-of (random 1)) ⇒ Integer (type-of (random 1.0)) ⇒ Float
- Function: set-random-seed seed
- <<set-random-seed>>
Set the random seed to the specified integer argument. Returns
tru
.(set-random-seed 1337) ⇒ tru (random 1000) ⇒ 136 (set-random-seed 1337) ⇒ tru (random 1000) ⇒ 136
These primitives are used to check for logical truth. They usually
return a predicate, that is, an expression whose value is meant to be
interpreted as either true or false. In SL, the empty list nil
is used
to denote false, and other values denote true implicitly (see
=nil=). Usually, these functions return either nil
or the explicit truth
symbol tru
.
- Function: equal? a b &rest rest
- <<equal?>>
Return
tru
if the structure of all arguments is equal,nil
otherwise. In other words, if they are isomorphic. As a rule of thumb, two expressions are isomorphic ifwrite-to-str
returns the same string for both of them (see =write-to-str=). Isomorphism for different types will be expanded below.The primitive doesn’t require arguments of the same type, but the equality will usually fail if they don’t share a common one.
Important exceptions:
- The symbol
nil
and the empty list()
are interchangeable, and therefore equal. This is an exception, and is explained in more detail in =nil=.
Equality for different types:
- Two non-empty lists are equal if they have the same number of
elements, and if each expression in the first list is equal to the
corresponding expression in the second list, according to this
function
equal?
. - Two numbers are equal according to this function if they share the same type, and if they have the same value. General numeric equality can be checked with ===.
- Two strings are equal if they have the same length, and if all of their characters match.
- Symbols are handled just like strings, but comparing the two types
will always returns
nil
. - Two expressions of type Primitive are equal if they point to the same C function in memory.
- Two lambda functions are equal if they have the same number of
formals, their formals have the same names, and all of the
expressions in their body match according to this function
equal?
. - Macros are handled just like lambdas, but, just like symbols and strings, they are not equal according to this function because they don’t share the same expression type.
Some examples:
(equal? 123 123) ⇒ tru (equal? 5 5.0) ⇒ nil (equal? 'abc "abc") ⇒ nil (defun foo (x) x) ⇒ <lambda> (equal? foo (lambda (x) x)) ⇒ tru (equal? foo (lambda (y) y)) ⇒ nil (defmacro bar (x) x) ⇒ <macro> (equal? foo bar) ⇒ nil
- The symbol
- Function: = a b &rest rest
- <<=>>
Returns
tru
if the value of all numeric arguments is equal,nil
otherwise. The value of two numeric expressions is equal, according to this function, if their values are the same after being converted to a Generic Number Type. See Generic Number Type.Some examples:
(= 1 1) ⇒ tru (= 1 1.0) ⇒ tru (= 1 1.0 2) ⇒ nil
- Function: < a b &rest rest
- <<lt>>
Return
tru
if all arguments are monotonically increasing, that is,$a<b<…<n$ ;nil
otherwise. Predicates are therefore transitive, that is,$a<c$ .Just like with equality, two expressions will increase or decrease depending on their type. These are the different conditions required for two expressions to be increasing or decreasing:
- Two numbers are increasing or decreasing if the value of second is greater or smaller than the value of the first, respectively. Numbers can be compared if they don’t share the same type, but will be converted to a Generic Number Type, just like with ===.
- Two strings are increasing or decreasing if the first differing
character in the strings is greater or smaller on the second string
than on the first[fn::This is checked using the C function
strcmp
.], respectively. - Symbols are handled just like strings, but comparing the two types
will always returns
nil
. - Other expression types can’t be compared using this function.
Some examples:
(< 1 2) ; tru (< 10 20 30) ; tru (< 10 20 5) ; nil
- Function: > a b &rest rest
- <<gt>>
Return
tru
if all arguments are monotonically decreasing, that is,$a>b>…>n$ ;nil
otherwise. Predicates are therefore transitive, that is,$a>c$ . For more information on this function, see =<=.Some examples:
(> 2 1) ; tru (> 30 20 10) ; tru (> 30 20 40) ; nil
These primitives are used for checking the type of an expression. Note
that most of these type?
functions don’t need to be primitives, since we
could check the symbol returned by type-of
. The primitives in this
section are defined in prim_type.c.
See also Types.
- Function: type-of expr
- <<type-of>>
Return a symbol representing the type of the specified expression.
(type-of 1) ⇒ Integer (type-of 1.0) ⇒ Float (type-of 'foo) ⇒ Symbol (type-of "Bar") ⇒ String (type-of '(a b c)) ⇒ List (type-of +) ⇒ Primitive (type-of (lambda (x) x)) ⇒ Lambda (type-of (macro (x) x)) ⇒ Macro
- Function: int? expr
- <<int?>>
Returns
tru
if the argument is an Integer number,nil
otherwise.(int? 1) ⇒ tru (int? 1.0) ⇒ nil
- Function: flt? expr
- <<flt?>>
Returns
tru
if the argument is a Float number,nil
otherwise.(flt? 1.0) ⇒ tru (flt? 1) ⇒ nil
- Function: symbol? expr
- <<symbol?>>
Returns
tru
if the argument is a Symbol,nil
otherwise. Note that, even though the symbolnil
and the empty list()
are interchangeable, only the former is a symbol according to this function. See =nil=.(define foo 123) ⇒ 123 (symbol? 'foo) ⇒ tru (symbol? foo) ; 123 is checked ⇒ nil (symbol? "Bar") ⇒ nil (symbol? 'nil) ⇒ tru (symbol? nil) ; NOTE: This might change in the future ⇒ nil
- Function: string? expr
- <<string?>>
Returns
tru
if the argument is a String,nil
otherwise.(string? "Foo") ⇒ tru (string? 'bar) ⇒ nil
- Function: list? expr
- <<list?>>
Returns
tru
if the argument is a List,nil
otherwise. For more details on how thenil
symbol is handled, see =symbol?=.(list? '(a b c)) ⇒ tru (list? (+ 1 2)) ; 3 is checked ⇒ nil (list? nil) ⇒ tru (list? 'nil) ; NOTE: This might change in the future ⇒ nil
- Function: primitive? expr
- <<primitive?>>
Returns
tru
if the argument is a C Primitive,nil
otherwise.(primitive? +) ⇒ tru (defun foo (x) x) ⇒ <lambda> (primitive? foo) ⇒ nil
- Function: lambda? expr
- <<lambda?>>
Returns
tru
if the argument is a Lambda function,nil
otherwise.(defun foo (x) x) ⇒ <lambda> (defmacro bar (x) x) ⇒ <macro> (lambda? foo) ⇒ tru (lambda? bar) ⇒ nil (lambda? +) ⇒ nil
- Function: macro? expr
- <<macro?>>
Returns
tru
if the argument is a Macro function,nil
otherwise.(defun foo (x) x) ⇒ <lambda> (defmacro bar (x) x) ⇒ <macro> (lambda? foo) ⇒ nil (lambda? bar) ⇒ tru (lambda? +) ⇒ nil
These primitives are used for converting between expression types. The primitives in this section are defined in prim_type.c.
- Function: int->flt expr
- <<int-to-flt>>
Converts the specified Integer into a Float.
(int->flt 1) ⇒ 1.000000
- Function: flt->int expr
- <<flt-to-int>>
Converts the specified Float into an Integer.
(flt->int 1.0) ⇒ 1
- Function: int->str expr
- <<int-to-str>>
Converts the specified Integer into a String. See also =write-to-str=.
(int->str 1) ⇒ "1"
- Function: flt->str expr
- <<flt-to-str>>
Converts the specified Float into a String.
(flt->str 1.0) ⇒ "1.000000"
- Function: str->int expr
- <<str-to-int>>
Converts the specified String into an Integer.
(str->int "1") ⇒ 1 (str->int "1abc") ⇒ 1 (str->int "abc1") ; Invalid input ⇒ 0
- Function: str->flt expr
- <<str-to-flt>>
Converts the specified String into a Float.
(str->flt "1.0") ⇒ 1.000000 (str->flt "1.0abc") ⇒ 1.000000 (str->flt "1") ⇒ 1.000000 (str->flt "1abc") ⇒ 1.000000 (str->flt "abc1") ; Invalid input ⇒ 0.000000
These primitives are related to the construction, modification and information of lists. The primitives in this section are defined in prim_list.c.
- Function: list &rest exprs
- <<list>>
Construct a list from the specified arguments. All elements remain in the top level, even if they are other lists.
(list 1 2 3) ⇒ (1 2 3) (list 'a '(b c) 'd) ⇒ (a (b c) d) (list 'a 'b '() nil) ⇒ (a b nil nil)
- Function: cons expr lst
- <<cons>>
Prepend
expr
to the beginning of the listlst=[fn::For more information on the history of =cons
, see John McCarthy (1979) /History of Lisp/].Note that lists are currently not implemented as multiple
cons
pairs, but as a simple linked list instead. I think this is more memory-efficient, but this will probably change in the future. Therefore, thecons
implementation is a bit different that most other Lisps.(cons 'a '(b c d)) ⇒ (a b c d) (cons '(a b) '(c d)) ⇒ ((a b) c d) (cons 'a nil) ⇒ (a) (cons 'a 'b) ; NOTE: Not yet implemented ⇒ Error: Expected expression of type 'List', got 'Symbol'.
- Function: car pair
- <<car>>
Return the first element of the specified /cons pair/[fn::For historical reasons,
car
stands for “Contents of the Address (part) of Register”.]. Therefore, since(car (cons a b))
is alwaysa
, thecar
of a list is its first element.The
car
ofnil
is alwaysnil
, even though it represents a list with no elements.(car '(a b c)) ⇒ a (car '((a b) c d)) ⇒ (a b) (car nil) ; Special case ⇒ nil
- Function: cdr pair
- <<cdr>>
Return the second element of the specified /cons pair/[fn::For historical reasons,
cdr
stands for “Contents of the Decrement (part) of Register”.]. Therefore, since(cdr (cons a b))
is alwaysb
, thecdr
of a list is the part of the list that follows the first element (thecar
).The
cdr
ofnil
is alwaysnil
, even though it represents a list with no elements.(cdr '(a b c)) ⇒ (b c) (cdr '((a b) c d)) ⇒ (c d) (cdr '(a (b c) d)) ⇒ ((b c) d) (cdr nil) ; Special case ⇒ nil
- Function: length sequence
- <<length>>
Return the number of elements in a sequence, that is, a List or String.
(length '(a b c)) ⇒ 3 (length "abc") ⇒ 3 (length nil) ⇒ 0 (length "") ⇒ 0
- Function: append &rest sequences
- <<append>>
Attach one sequence to another, that is, a List or String. Note that all arguments must share the same type, so you can’t append a list to a string.
(append '(1 2 3) '(a b c) '(4 5 6)) ⇒ (1 2 3 a b c 4 5 6) (append '(a b c)) ⇒ (a b c) (append "foo" "bar") ⇒ "foobar"
When called with no arguments,
append
returnsnil
.(append) ⇒ nil
These primitives are related to the construction, modification and information of strings. The primitives in this section are defined in prim_string.c.
Note that some functions in List-related primitives operate on sequences in general, not just lists, so they can be used with strings.
- Function: write-to-str expr
- <<write-to-str>>
Returns a string that represents the specified expression. The format of the returned string must contain enough information to be parsed into the original expression using =read=.
See also =write=.
(write-to-str 1) ⇒ "1" (write-to-str 'hello) ⇒ "hello" (write-to-str (lambda (x) (* x 2))) ⇒ "(lambda (x) (* x 2))" (write-to-str "Hello, world\n") ⇒ "\"Hello, world\\n\""
It might be a bit hard to understand what is really escaped, and what is only escaped “visually”. First, note that the user input is “un-escaped” by the lexer, so the interpreter always works with the real string (i.e. the interpreter would write
0xA
to the internal string, not[0x5C, 0x6E]
). Then, sincewrite-to-str
must return a valid string forread
, it manually escapes it, normally resulting in what the user typed in the first place. However, note that the print step of the REPL also escapes strings before printing them (that’s what I meant by “only escaped visually”). To view the “raw” output ofwrite-to-str
, it’s best to use something likeprint-str
(See =print-str=).(begin (print-str (write-to-str "Hello, world\n")) (print-str "\n") (print-str "\"Hello, world\\n\"") ; Returned (print-str "\n") 'done) → "Hello, world\n" → "Hello, world\n" ⇒ done
- Function: format format-string &rest exprs
- <<format>>
Returns a string with the specified format. This function is similar to C’s
sprintf(3)
.The
format
function produces a string from theformat-string
, copying all characters literally, except the percent sign%
, which is used to indicate the start of a format specifier. Format specifiers are used to indicate how its corresponding expression (obtained from theexprs
list) should be converted and appended to the final string.This function expects the number of
exprs
to match the format specifiers in theformat-string
; the function will fail if the user didn’t supply enough arguments, but will not check if the user supplied more. Furthermore, the function will make sure that each supplied argument matches the type required by the format specifier.These are the currently supported format specifiers:
s
- Format an expression of type String. Each character is printed
literally, nothing is escaped, similar to
print-str
. d
- Format an expression of type Integer.
u
- Format an expression of type Integer as unsigned.
x
- Format an expression of type Integer as unsigned, in
hexadecimal format with a
0x
prefix. f
- Format an expression of type Float.
%
- Used to represent the literal percent sign
%
. This format specifier does not need a matching expression in theexprs
list.
The function will fail if the user supplied an unknown format specifier.
(format "%s, %s!" "Hello" "world") ⇒ "Hello, world!" (format "%d / %d = %d (%f)" 5 2 (quotient 5 2) (/ 5 2)) ⇒ "5 / 2 = 2 (2.500000)"
- Function: substring string &optional from to
- <<substring>>
Return a new string whose contents are a substring of
string
. Paraphrasing the Emacs Lisp manual:The returned string consists of the characters between index
from
(inclusive) and indexto
(exclusive) ofstring
. Thefrom
andto
arguments are zero-indexed: 0 means the first character ofstring
.Negative values are counted from the end of
string
, so -1 represents the last character in the string.If
from
is nil, the substring starts at index 0; and ifto
is nil, the substring runs to the end ofstring
.Some examples:
(substring "abcdef") ⇒ "abcdef" (substring "abcdef" 0 2) ⇒ "ab" (substring "abcdef" 1 nil) ⇒ "bcdef" (substring "abcdef" -1 nil) ⇒ "f" (substring "abcdef" 1 -1) ⇒ "bcde" (substring "abcdef" -3 -1) ⇒ "de"
- Function: re-match-groups regexp string &optional ignore-case
- <<re-match-groups>>
Try to match every group in
regexp
againststring
, and return a list with the matches. The first match in the returned list corresponds to the wholeregexp
, and the remaining elements correspond to each parenthesized group, if any. If theregexp
didn’t matchstring
, the function returnsnil
.Each match in the returned list is a list with the form
(START END)
, whereSTART
andEND
are integers that indicate the start and end index of the match insidestring
, respectively.By default, the search is case-sensitive, but this can be overwritten by specifying a non-nil argument for the optional parameter
ignore-case=[fn::When non-nil, the C function =regcomp
is called with theREG_ICASE
flag. For more information, see the manual page for =regcomp=.].The function uses POSIX regular expression syntax, more specifically Extended Regular Expression (ERE) syntax[fn::For more information, see IEEE Std 1003.1, Section 9, /Regular Expressions/; and the
sed
manual, /Overview of extended regular expression syntax/ as well as /Character Classes and Bracket Expressions/.].Some examples:
(define str "abc XYZ 123") (re-match-groups "abc" str) ⇒ ((0 3)) (re-match-groups "xyz" str) ⇒ nil (re-match-groups "xyz" str tru) ⇒ ((4 7)) (re-match-groups "^(abc) ([A-Z]+) ([[:digit:]]+)$" str) ⇒ ((0 11) (0 3) (4 7) (8 11))
Note that this function only returns information about the first match of
regexp
instring
, not about all the possible matches:;; Not ((0 1) (1 2) (2 3)) (re-match-groups "a" "aaa") ⇒ ((0 1))
Also note that, since any non-nil argument can be used for the
ignore-case
parameter, sometimes it might be a good idea to use a more descriptive value:(re-match-groups "abc" "ABC" 'ignore-case) ⇒ ((0 3))
These primitives are used for performing arithmetical operations on numbers. The primitives in this section are defined in prim_arith.c. See also Bit-wise primitives.
- Function: + &rest numbers
- <<+>>
Add the specified numbers. If the arguments don’t share a common type, they are converted to a common type (see Generic Number Type). Returns 0 when called with no arguments.
(+) ⇒ 0 (+ 1 2 3) ⇒ 6 (+ 1 2.0 3) ⇒ 6.000000
- Function: - &rest numbers
- <<->>
Subtract the specified numbers in order. If the arguments don’t share a common type, they are converted to a common type (see Generic Number Type). When called with just one argument, it’s negated. Returns 0 when called with no arguments.
(-) ⇒ 0 (- 5) ⇒ -5 (- 5 2 1) ⇒ 2 (- 5 2.0 1) ⇒ 2.000000
- Function: * &rest numbers
- <<*>>
Multiply the specified numbers. If the arguments don’t share a common type, they are converted to a Generic Number Type. Returns 1 when called with no arguments.
(*) ⇒ 1 (* 1 2 3) ⇒ 6 (* 1 2.0 3) ⇒ 6.000000
- Function: / dividend &rest divisors
- <</>>
Divide the
dividend
by each divisor in order. The arguments are always converted to a common type, even if the arguments share a common type (see Generic Number Type). For integer division, see =quotient=. Trying to divide by zero results in an error.(/ 10) ⇒ 10.000000 (/ 10 2) ⇒ 5.000000 (/ 10 0) ⇒ Error: Trying to divide by zero. (/ 10 3) ⇒ 3.333333 (/ 10 2 2) ⇒ 2.500000
- Function: mod dividend &rest divisors
- <<mod>>
Return the modulus of
dividend
by each divisor in order. Just like/
, this function converts all arguments to a common type before operating on them (see Generic Number Type). This function allows floating-point and negative inputs[fn::For more details on a possible implementation of a floating-pointmod
, see the article on my blog.]. Trying to divide by zero results in an error.Similarly to how the Emacs Lisp manual describes
mod
, the following expression should be equal to thedividend
:(+ (mod dividend divisor) (* (floor (/ dividend divisor)) divisor))
Note that, although the behavior of the
mod
function is the same in SL and in Emacs Lisp, the behavior of thefloor
and/
functions is not. See =floor=.Some examples of
mod
:(mod 10) ⇒ 10.000000 (mod 10 2) ⇒ 0.000000 (mod 10 3) ⇒ 1.000000
- Function: quotient dividend &rest divisors
- <<quotient>>
Divide the
dividend
by each divisor in order. Unlike/
, this function only operates with integers. Trying to divide by zero results in an error.(quotient 10) ⇒ 10 (quotient 10 2) ⇒ 5 (quotient 10 0) ⇒ Error: Trying to divide by zero. (quotient 10 3) ⇒ 3
The behavior is identical to integer division in C, that is, the result is always truncated towards zero; in other words, rounded towards the smallest absolute value. Dividing using
quotient
is not the same as usingfloor
on a floating point/
division:(floor (/ -5 2)) ⇒ -3.000000 (quotient -5 2) ⇒ -2
- Function: remainder dividend &rest divisors
- <<remainder>>
Return the remainder of
dividend
by each divisor in order. Unlikemod
, this function only operates with integers. Trying to divide by zero results in an error.The
remainder
function in SL works like theremainder
function in Scheme. The following expression should be equal to thedividend
:(+ (remainder dividend divisor) (* (quotient dividend divisor) divisor))
Again, note the difference between
(floor (/ ...))
and(quotient ...)
. See =quotient= and =floor=.Some examples of
remainder
:(remainder 10) ⇒ 10 (remainder 10 2) ⇒ 0 (remainder 10 3) ⇒ 1
The behavior of this function is identical to the
%
operator in C. Note that, in SL, the remainder and the modulo of two numbers is not the same:(remainder -5 2) ⇒ -1 (mod -5 2) ⇒ 1.000000
- Function: round number
- <<round>>
Round
number
to nearest integer. Halfway cases are rounded away from zero. The type of the returned value always matches the type of the input.(round 5) ⇒ 5 (round 5.3) ⇒ 5.000000 (round 5.5) ⇒ 6.000000 (round 5.6) ⇒ 6.000000 (round -5.3) ⇒ -5.000000 (round -5.5) ⇒ -6.000000 (round -5.6) ⇒ -6.000000
- Function: floor number
- <<floor>>
Return the largest integral value not greater than
number
. In other words, round the specifiednumber
towards negative infinity. The type of the returned value always matches the type of the input.(floor 5) ⇒ 5 (floor 5.0) ⇒ 5.000000 (floor 5.7) ⇒ 5.000000 (floor -5.0) ⇒ -5.000000 (floor -5.7) ⇒ -6.000000
Note how
floor
does not round towards zero for negative values. See also =truncate=. - Function: ceiling number
- <<ceiling>>
Return the smallest integral value not less than
number
. In other words, round the specifiednumber
towards positive infinity. The type of the returned value always matches the type of the input.(ceiling 5) ⇒ 5 (ceiling 5.0) ⇒ 5.000000 (ceiling 5.3) ⇒ 6.000000 (ceiling -5.0) ⇒ -5.000000 (ceiling -5.3) ⇒ -5.000000
- Function: truncate number
- <<truncate>>
Round the specified
number
to an integer, towards zero. In other words, return thenumber
with the fractional part set to zero. The type of the returned value always matches the type of the input.(truncate 5) ⇒ 5 (truncate 5.3) ⇒ 5.000000 (truncate 5.6) ⇒ 5.000000 (truncate -5.3) ⇒ -5.000000 (truncate -5.6) ⇒ -5.000000
These primitives are related to the manipulation of bits. The primitives in this section are defined in prim_bitwise.c.
- Function: bit-and integer &rest rest
- <<bit-and>>
Perform a bit-wise and operation with each integer argument. That is, each bit in the result is set if that bit was set in all of the arguments.
0b11110000 0xF0 240 0b11001100 0xCC 204 ---------- ---- --- (AND) 0b11000000 0xC0 192
Some examples:
(bit-and 0xF0 0xCC) ⇒ 192 (bit-and 0xF0 0xCC 0x03) ⇒ 0
- Function: bit-or integer &rest rest
- <<bit-or>>
Perform a bit-wise or operation with each integer argument. That is, each bit in the result is set if that bit was set in at least one of the arguments.
0b11110000 0xF0 240 0b11001100 0xCC 204 ---------- ---- --- (OR) 0b11111100 0xFC 252
Some examples:
(bit-or 0xF0 0xCC) ⇒ 252 (bit-or 0xF0 0xCC 0x03) ⇒ 255
- Function: bit-xor integer &rest rest
- <<bit-xor>>
Perform a bit-wise xor operation with each integer argument. That is, each bit in the result is set if that bit was set in an odd number of arguments.
0b11110000 0xF0 240 0b11001100 0xCC 204 ---------- ---- --- (XOR) 0b00111100 0x3C 60
Some examples:
(bit-xor 0xF0 0xCC) ⇒ 60 (bit-xor 0xF0 0xCC 0x03) ⇒ 63
- Function: bit-not integer
- <<bit-not>>
Perform a bit-wise not operation on
integer
. That is, if a bit was set (1) in the input, it becomes unset; and if the bit was unset (0), it becomes set.0b11001100 0xCC 204 ---------- ---- --- (NOT) 0b00110011 0x33 51
Some examples:
(bit-not 0xCC) ⇒ -205 ; Shown as signed (format "%x" (bit-not 0xCC)) ⇒ "0xffffffffffffff33" (bit-not 0) ⇒ -1 (format "%x" (bit-not 0)) ⇒ "0xffffffffffffffff"
- Function: shr integer n-bits
- <<shr>>
Shift the specified
integer
n bits to the right. Sets the high-order bits to zero. It is equivalent to the>>
C operator, and to theshr
x86
assembly instruction[fn::It is not equivalent to theror
instruction.].Some examples:
(shr 0xF0 4) ⇒ 15 (format "%x" (shr 0xF0 4)) ⇒ "0xf" (shr 0xF0 6) ⇒ 3 (format "%x" (shr 0xF0 6)) ⇒ "0x3"
- Function: shl integer n-bits
- <<shl>>
Shift the specified
integer
n bits to the left. Sets the low-order bits to zero. It is equivalent to the<<
C operator, and to theshl
x86
assembly instruction[fn::It is not equivalent to therol
instruction.].Some examples:
(shl 0x0F 4) ⇒ 240 (format "%x" (shl 0x0F 4)) ⇒ "0xf0" (shl 0x0F 6) ⇒ 960 (format "%x" (shl 0x0F 6)) ⇒ "0x3c0"
These primitives are related to reading and writing data to the outside world. They are defined in prim_io.c.
- Function: read
- <<read>>
Read a single expression from the standard input, parse it, and return it as a Lisp expression. It’s the first step in the REPL, which consists of reading a string from the standard input, tokenizing it, and parsing it into a Lisp expression.
In the following example, note that the inputs are shown literally, so in the input
"Hello\nWorld\n"
, the user typed the quotes,\
andn
.(read) ;; Input: foo bar ⇒ foo (type-of (read)) ;; Input: foo ⇒ Symbol (read) ;; Input: "Hello\nWorld\n" ⇒ "Hello\nWorld\n" (print-str (read)) ;; Input: "Hello\nWorld\n" → Hello → World (eval (read)) ;; Input: (+ 1 2) ⇒ 3
Note the difference with =scan-str=.
- Function: write expr
- <<write>>
Write an expression in such a way that it can be parsed into the original expression using =read=. This function returns
tru
on success. For more information, see =write-to-str=.(write 123) → 123 ⇒ tru (write 'sym) → sym ⇒ tru (write "foo\nbar") → "foo\nbar" ⇒ tru (write "foo bar baz") → "foo\nbar\nbaz" ⇒ tru
- Function: scan-str &optional delimiters
- <<scan-str>>
Read user input into a string. This function reads from the standard input until one of the following is found:
- End-of-file (
EOF
). - Null character (
'\0'
). - A character in the
delimiters
string.
By default, the
delimiters
string is"\n"
, so the function stops reading as soon as the received character is a newline. Note that the final delimiter is not included in the returned string.Unlike
read
, this function doesn’t read or parse a single Lisp expression. See =read=.In the following example,
<RET>
and<TAB>
are used to indicate that the user pressed the return and tab keys, respectively.(scan-str) ;; Input: foo<RET> ⇒ "foo" (type-of (scan-str)) ;; Input: foo<RET> ⇒ String (scan-str) ;; Input: Hello<TAB>World<RET> ⇒ "Hello\tWorld" (scan-str "._-\n") ;; Input: Hello. World.<RET> ⇒ "Hello"
- End-of-file (
- Function: print-str string
- <<print-str>>
Print the specified string literally to standard output. Returns its argument.
(print-str "Hello, world.\n") → Hello, world. ⇒ "Hello, world.\n" (print-str "I am \"escaping\" the quotes...\n") → I am "escaping" the quotes... ⇒ "I am \"escaping\" the quotes...\n"
Unlike
write
, it only operates on strings, does not print the double-quotes, and doesn’t escape anything implicitly.(print-str "123 \"abc\" 456\n") → 123 "abc" 456 ⇒ "123 \"abc\" 456\n" (write "123 \"abc\" 456\n") → "123 \"abc\" 456\n" ⇒ tru
Note that
write
is doing the escaping before printing;print-str
doesn’t “un-escape” anything, the user input is converted by the lexer. See =write-to-str=. - Function: error string
- <<error>>
TODO
TODO
TODO
- Function: trace function
- <<trace>>
TODO