Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ examples/*.o
test/tmp.asm
test/*.test
test/*.out
test/*.s
test/*.asm
test/*.o
*.o
*.asm
examples/bf/*.out
examples/bf/*.o
examples/bf/*.asm
43 changes: 29 additions & 14 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ Special forms are things that are built into the compiler core, and handled spec

* `alias!`
* Remap functions. See [INTRODUCTION.md](INTRODUCTION.md) for details.
* `cond`
* An efficient switch implementation.
* `defconst`
* Define an immutable global variable.
* `defmacro`
* Define a macro.
* `defun`
* Define a function - The function named `main` is both mandatory, and the entry-point to user-scripts.
* `defvar`
Expand All @@ -62,23 +62,28 @@ Special forms are things that are built into the compiler core, and handled spec
* Creates a lambda function.
* `let`
* Create a new scope, with locally bound variables.
* `list`
* Create a list.
* `require`
* Load a new package, inline. See [INTRODUCTION.md](INTRODUCTION.md) for details.
* `set!`
* Set the value of a variable.
* `unless`
* Run an unlimited number of expressions when the given condition is false
* `(unless x (expression1) (expression2) ..)` is the same as `(if x nil (do (expression1) (expression2) ..))`
* `when`
* Run an unlimited number of expressions when the given condition is true.
* `(when x (expression1) (expression2) ..)` is the same as `(if x (do (expression1) (expression2) ..))`
* `while`
* Run the given body for as long as the specified condition is non-nil.



## Quoting

We have support for the _standard_ quote things, required to implement macros. However our macros are very limited and are more akin to template-expansion.

* `'x`, or `quote`.
* ```x `` or `quasiquote`.
* `,y` or `unquote`.
* `,@y` or `unquote-splicing`.

Our lisp-interpreter, inception, has the same support for quoting and unquoting.



## Core Primitives

Core primitives are implemented in assembly language, and can be found within the file [compiler/template.tmpl](compiler/template.tmpl)
Expand Down Expand Up @@ -221,8 +226,6 @@ The implementation of these primitives can be found in the file [stdlib.slisp](s
* Add the given key/value to an alist.
* `alist:values`
* Return all known values from the given alist.
* `and`
* Test if every item in a list is true.
* `append`
* Append the given value to the specified list. If the list is empty just return the specified item.
* `atoi`
Expand Down Expand Up @@ -282,8 +285,6 @@ The implementation of these primitives can be found in the file [stdlib.slisp](s
* Return 1 if the given number is odd, nil otherwise.
* `one?`
* Return true if the number is one.
* `or`
* Is any value in the given list non-nil?
* `plist:new`
* Create a new property-list
* `plist:get`
Expand Down Expand Up @@ -333,6 +334,20 @@ The implementation of these primitives can be found in the file [stdlib.slisp](s



## Macros

We've implemented several primitives as macros, within our standard library [stdlib.slisp](stdlib.slisp):

* `and`
* `cond`
* `or`
* `unless`
* `when`

Our lisp-interpreter, inception, has the same support for macros.



## See Also

* [README.md](README.md)
Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ You can find bigger examples beneath [examples/](examples/), and our [test/](tes
* [test/sort3.lisp](test/sort3.lisp) - A mergesort implementation.
* [test/vararg.lisp](test/vararg.lisp) - Demonstration of a function accepting a variable number of arguments.

It should be noted that we prepend a standard library of functions to all user programs unless `-stdlib=false` is added to the command line. That library itself is a useful reference/demonstration of functionality:
It should be noted that we prepend a standard library of functions to all user programs unless `-stdlib=false` is added to the compiler command line. That library itself is a useful reference/demonstration of functionality:

* [stdlib.slisp](stdlib.slisp) - Our standard library, written in `slisp` itself.
* Has a good `print` definition which handles known types appropriately.
Expand All @@ -77,31 +77,32 @@ It should be noted that we prepend a standard library of functions to all user p
* `=`, `<`, `<=`, `>=`, `>`, and `!` to invert a result.
* Special forms (only some of which are valid at the top-level, those are marked with `*`):
* `(alias! ..)` - `*` - Alias/overwrite a function.
* `(cond ..)`
* `(defmacro ..)` - `*` - declare a macro.
* `(defun ..)` - `*` - declare a function.
* `(defconst ..)` - `*` - declare a global constant.
* `(defvar ..)`- `*` - declare a global variable.
* `(do ..)`
* `(if ..)`
* `(lambda ..)`
* `(let ..)`
* `(list ..)`
* `(require ..)` - `*` - Include other source files.
* `(set! ..)`
* `(unless ..)`
* `(when ..)`
* `(while ..)`
* Support for _simple_ macros.
* For example our standard functions `and`, `cond`, `list`, `or`, `unless`, and `when` are implemented as macros.

You can see a complete list of our primitives, and their details in [PRIMITIVES.md](PRIMITIVES.md) - documenting both the built-in special-forms, and the parts of the standard library which are implemented in assembly, or `slisp` itself.
You can see a complete list of our primitives, and their details in [PRIMITIVES.md](PRIMITIVES.md) - this documents the built-in special-forms, the parts of the standard library which are implemented in assembly, those things which are written in `slisp` itself, as well as our predefined macros.

Anti-features:

* No macros.
* It wouldn't be impossible to add them, but without `quote`, `quasiquote`, etc, it's a lot of work.
* No `quote`
* Only really useful if you can call `eval` and as a compiler? That's not going to happen easily.
* We don't have "symbols" exposed to the language, but if you prefix a variable with "`:`" it will become visually distinct, and this is useful when working with alists, or plists.
* Internally that is actually translated to a stringified version of the variable name (So `(print :name)` becomes `(print "name")` - that might seem weird but it works for alist/plist usage, etc.)
* Macros (`defmacro`) are a deliberately restricted, non-hygienic, compile-time expansion mechanism.
* A macro body may use bound parameters, literals, `quote`/`quasiquote` templates, a compile-time `if`, and `car`/`cdr`/`nil?` (for recursing over a variadic parameter) to construct its expansion - but not arbitrary compile-time computation (e.g. calling `+` directly against a parameter)
* A macro can't substitute into "raw name" slots - the target of `set!`, `let`-binding names, or `lambda`/`defun` parameter names - since those are parsed as literal tokens, not expressions.
* So you cannot write a decent `dolist` macro that inserts a named variable in the callee scope, however you can use an anaphoric approach.
* We don't have "symbols" exposed to the language.
* You may prefix a variable with "`:`" to make it visually distinct.
* Quoting a bare symbol, e.g. `'foo`, produces the same kind of string.
* So both `:foo` and `'foo` are treated as the string `"foo"`.



Expand Down Expand Up @@ -253,7 +254,7 @@ Solution 1 (1 5 8 6 3 7 2 4):

> Here you'll see we added `--main` which automatically runs the `(main)` function our examples define.

So what are the differences between our _compiler_ and our _interpreter_? Well in some ways the interpreter is more advanced as it has support for `(quote)`, it has a symbol-type, and you can get references to functions using them. The lambdas/defuns are real standalone objects which are treated largely interchangeably and which you can also print.
So what are the differences between our _compiler_ and our _interpreter_? Well in some ways the interpreter is more advanced as it has a real symbol-type, and you can get references to functions using them. The lambdas/defuns are real standalone objects which are treated largely interchangeably and which you can also print.

The `alias!` function works for user-defined functions, but sadly doesn't allow you to override or change built-in functions, as they are in a different namespace. This works:

Expand All @@ -267,8 +268,6 @@ But this doesn't work, if it did we'd have a recursion problem too of course:
(alias! string x)
(string "steve")

Inception loads the standard library `stdlib.slisp` on startup, to ensure that programs executed by it have the same supporting-functions available as when compiled. The embedded packages contained within `packages/` are also available at run-time with `(require NAME)` as you would expect.

The interpreter is obviously much slower than our compiled binaries, due to the overhead of interpreting everything manually. Sometimes this slowdown is minor, other times it is signification, it really depends upon the nature of the program:

* `time ./example` -> 0.006s
Expand Down
Loading
Loading