Skip to content

Latest commit

 

History

History
2189 lines (1592 loc) · 55.3 KB

sl-manual.org

File metadata and controls

2189 lines (1592 loc) · 55.3 KB

SL Manual

Copying

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/.

General concepts

This section will explain some important concepts about the Lisp syntax, and about the interpreter itself.

Types

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.

Generic Number Type

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.

Variables

These variables are defined by default in the global environment.

Constants

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.

nilnil

'()
  ⇒ nil

()  ; Special case, not treated as function callnil

(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 symbol nil, 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
    

Debugging variables

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 with define, but instead use the standard library function trace (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: 66
    

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 the equal? 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: 55
    

Primitive Procedures

This section explains the different primitive procedures in SL. Primitive procedures are implemented in C.

Special Forms

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 by backquote. 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 a backquote 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 was 7, the backquote would return (hi 107 bye), but if var was 35, it would return (hi (abc 35 xyz) bye).

Also note that none of this unquote functionality is available inside quote arguments, just backquote:

'(,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 list nil.

(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 calling my-macro. This is because, since macro arguments are not evaluated, the symbol some-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 like apply 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 to define would bind the variables in the lambda 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 to nil, 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 returns nil. If all arguments evaluated to non-nil, returns the last result. Returns tru 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=.

General Primitives

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:

  1. The empty list (nil) evaluates to itself.
  2. 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-evaluated cdr to the corresponding special form primitive.
    • If the (evaluated) car of the list is a macro, the macro is called with the un-evaluated cdr of the list.
    • Otherwise, the arguments are evaluated and the procedure is called. If one argument fails to evaluate, evaluation stops.
  3. Symbols evaluate to their bound values in the current environment[fn::See also Section 3.2 of SICP.].
  4. 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 the quote 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 function eval, defined in eval.c.

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 called prim_apply, but it’s just a wrapper for the C function apply, defined in eval.c. It checks the type of the function 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 evaluated car 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
    

Logical primitives

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 if write-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
    
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&lt;b&lt;…&lt;n$; nil otherwise. Predicates are therefore transitive, that is, $a&lt;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&gt;b&gt;…&gt;n$; nil otherwise. Predicates are therefore transitive, that is, $a&gt;c$. For more information on this function, see =<=.

Some examples:

(> 2 1)      ; tru
(> 30 20 10) ; tru
(> 30 20 40) ; nil
    

Type-checking primitives

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 symbol nil 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 checkednil

(symbol? "Bar")
  ⇒ nil

(symbol? 'nil)
  ⇒ tru

(symbol? nil) ; NOTE: This might change in the futurenil
    
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 the nil symbol is handled, see =symbol?=.

(list? '(a b c))
  ⇒ tru

(list? (+ 1 2)) ; 3 is checkednil

(list? nil)
  ⇒ tru

(list? 'nil) ; NOTE: This might change in the futurenil
    
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
    

Type conversion primitives

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 input0
    
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 input0.000000
    

List-related primitives

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 list lst=[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, the cons 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 implementedError: 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 always a, the car of a list is its first element.

The car of nil is always nil, even though it represents a list with no elements.

(car '(a b c))
  ⇒ a

(car '((a b) c d))
  ⇒ (a b)

(car nil) ; Special casenil
    
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 always b, the cdr of a list is the part of the list that follows the first element (the car).

The cdr of nil is always nil, 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 casenil
    
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 returns nil.

(append)
  ⇒ nil
    

String primitives

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, since write-to-str must return a valid string for read, 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 of write-to-str, it’s best to use something like print-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 the format-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 the exprs list) should be converted and appended to the final string.

This function expects the number of exprs to match the format specifiers in the format-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 the exprs 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 index to (exclusive) of string. The from and to arguments are zero-indexed: 0 means the first character of string.

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 if to is nil, the substring runs to the end of string.

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 against string, and return a list with the matches. The first match in the returned list corresponds to the whole regexp, and the remaining elements correspond to each parenthesized group, if any. If the regexp didn’t match string, the function returns nil.

Each match in the returned list is a list with the form (START END), where START and END are integers that indicate the start and end index of the match inside string, 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 the REG_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 in string, 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))
    

Arithmetic primitives

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-point mod, 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 the dividend:

(+ (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 the floor 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 using floor 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. Unlike mod, this function only operates with integers. Trying to divide by zero results in an error.

The remainder function in SL works like the remainder function in Scheme. The following expression should be equal to the dividend:

(+ (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 specified number 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 specified number 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 the number 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
    

Bit-wise primitives

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 the shr x86 assembly instruction[fn::It is not equivalent to the ror 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 the shl x86 assembly instruction[fn::It is not equivalent to the rol instruction.].

Some examples:

(shl 0x0F 4)
  ⇒ 240

(format "%x" (shl 0x0F 4))
  ⇒ "0xf0"

(shl 0x0F 6)
  ⇒ 960

(format "%x" (shl 0x0F 6))
  ⇒ "0x3c0"
    

Input/Output primitives

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, \ and n.

(read)
  ;; Input: foo bar
  ⇒ foo

(type-of (read))
  ;; Input: fooSymbol

(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"
    
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

Standard library

TODO

Debugging

TODO

Function: trace function
<<trace>>

TODO