diff --git a/Makefile b/Makefile index 7a604a6..e3c9626 100644 --- a/Makefile +++ b/Makefile @@ -12,8 +12,6 @@ keithlisp: $(OBJS) $(OBJ_DIR)/%.o: %.c gcc -c -o $@ $^ $(CFLAGS) -$(OBJ_DIR): - mkdir $(OBJ_DIR) .PHONY = clean clean: diff --git a/doc/main.md b/doc/main.md new file mode 100644 index 0000000..a527f63 --- /dev/null +++ b/doc/main.md @@ -0,0 +1,24 @@ +# The Keithlisp Programmer's Reference +Keithlisp is a tiny, stripped-down version of Lisp written in C. It's +vaguely reminiscent of Common Lisp; however, **Keithlisp does NOT fully +adhere to the Common Lisp standard!** Many Common Lisp functions are not +present or behave differently in Keithlisp. + +## General Advice +- Keithlisp assumes that you, the programmer, know what you are doing. + **Many Keithlisp functions will segfault or otherwise misbehave if you + do not provide them with the data they expect.** +- Keithlisp doesn't properly parse tokens longer than 64 bytes. This + includes string literals. +- Strings in Keithlisp are sequences of *bytes*. A zero byte does not + necessarily correspond to the end of a string. +- You can comment out `#define LISP_USE_ATOMS_ALIST` in main.h to remove + the atoms_alist. This significantly reduces Keithlisp's memory + footprint, but makes it impossible to retrieve the name of an atom. +- Keithlisp doesn't automatically garbage-collect return values at the + moment, but this may change in the future. If you know whether a + function will return a cons/string referenced elsewhere or not, you + should insert a garbage collector annotation in your code. + +## Contents +- [The Keithlisp Standard Library](stdlib.md) diff --git a/doc/stdlib.md b/doc/stdlib.md new file mode 100644 index 0000000..b268d97 --- /dev/null +++ b/doc/stdlib.md @@ -0,0 +1,9 @@ +# The Keithlisp Standard Library +Keithlisp's built-in functions are grouped into the following +categories: + +- [Special Forms](stdlib_forms.md) +- [Arithmetic](stdlib_arithmetic.md) +- [Boolean Logic](stdlib_boolean.md) +- [Comparison](stdlib_comparison.md) +- [Miscellaneous Functions](stdlib_misc.md) diff --git a/doc/stdlib_arithmetic.md b/doc/stdlib_arithmetic.md new file mode 100644 index 0000000..9827ab9 --- /dev/null +++ b/doc/stdlib_arithmetic.md @@ -0,0 +1,41 @@ +# Arithmetic + +## `+` +``` +(+ &rest numbers) +``` +`+` returns the sum of its arguments, skipping any inputs that are not +ints or floats. If no arguments are given, it returns 0. + +## `-` +``` +(- number) +(- number &rest more-numbers) +``` +If called with one argument, `-` returns the negation of that argument. +If called with more than one argument, `-` subtracts `more-numbers` from +`number`, skipping any inputs that are not ints or floats. +If `number` is not an int or float, `-` returns nil. + +## `*` +``` +(* &rest numbers) +``` +`*` returns the product of its arguments, skipping any inputs that are +not ints or floats. If no arguments are given, it returns 1. + +## `/` +``` +(/ number &rest more-numbers) +``` +`/` divides `number` by `more-numbers`, skipping any inputs that are not +ints or floats. It always returns a float. +If `number` is not an int or float, `/` returns nil. + +## `int/` +``` +(int/ number &rest more-numbers) +``` +`int/` performs integer division, dividing `number` by `more-numbers`, +skipping any inputs that are not ints or floats, and returning an int. +If `number` is not an int or float, `int/`returns nil. diff --git a/doc/stdlib_boolean.md b/doc/stdlib_boolean.md new file mode 100644 index 0000000..8219cf1 --- /dev/null +++ b/doc/stdlib_boolean.md @@ -0,0 +1,22 @@ +# Boolean Logic + +## `not` +``` +(not value) +``` +`not` returns nil if `value` is non-nil, otherwise, it returns `t`. + +## `or` +``` +(or &rest values) +``` + +`or` returns its first non-nil argument, or nil if all arguments are +nil. + +## `and` +``` +(and &rest values) +``` +`and` returns its last argument if all arguments are non-nil, or nil if +it encounters a nil argument. diff --git a/doc/stdlib_comparison.md b/doc/stdlib_comparison.md new file mode 100644 index 0000000..861e526 --- /dev/null +++ b/doc/stdlib_comparison.md @@ -0,0 +1,55 @@ +# Comparison + +## `eq` +``` +(eq value &rest values) +``` +`eq` returns t if all `values` are equal to `value`, or nil if any are +not. + +For two values to be equal according to `eq`, they must have the same +type. In all cases **except strings**, `eq` compares the immediate value +as returned by [`addr-of`](stdlib_misc.md#addr-of). However, `eq` will +return t for strings with identical contents, **regardless of their +addresses**. To check if two strings reference the same location in +memory, compare their `addr-of`. + +## `=` +``` +(= value &rest values) +``` +`=` compares its arguments numerically. If they are all equal, it +returns `value`, otherwise it returns nil. If any argument is not an int +or float, `=` returns nil. + +## `<` +``` +(< value &rest values) +``` +`<` compares its arguments numerically. If the sequence is monotonically +increasing, it returns the last argument, otherwise it returns nil. If +any argument is not an int or float, `<` returns nil. + +## `>` +``` +(> value &rest values) +``` +`>` compares its arguments numerically. If the sequence is monotonically +decreasing, it returns the last argument, otherwise it returns nil. If +any argument is not an int or float, `>` returns nil. + +## `<=` +``` +(<= value &rest values) +``` +`<=` compares its arguments numerically. If the sequence is +monotonically nondecreasing, it returns the last argument, otherwise it +returns nil. If any argument is not an int or float, `<=` returns nil. + +## `>=` +``` +(>= value &rest values) +``` +`>=` compares its arguments numerically. If the sequence is +monotonically nonincreasing, it returns the last argument, otherwise it +returns nil. If any argument is not an int or float, `>=` returns nil. diff --git a/doc/stdlib_forms.md b/doc/stdlib_forms.md new file mode 100644 index 0000000..a60d315 --- /dev/null +++ b/doc/stdlib_forms.md @@ -0,0 +1,22 @@ +# Special Forms +Special forms are constructs that Keithlisp evaluates differently from +other S-expressions. + +## `quote` +``` +quote form +``` +`quote` returns `form` as-is, without performing any evaluation on it. + +## `cond` +``` +cond {(test {body-form}*)}* +``` +`cond` is Keithlisp's basic conditional operator. It consists of zero or +more clauses (a `test`, followed by zero or more `body-form`s). + +`cond` evaluates the `test` of each clause in order. If the result is +non-nil, `cond` skips all remaining clauses, evaluates each `body-form`, +and returns the result of the last one (or the result of `test` if the +clause has no `body-form`s). Otherwise, if all `test`s evaluate to nil, +or no clauses were given, `cond` returns nil. diff --git a/doc/stdlib_misc.md b/doc/stdlib_misc.md new file mode 100644 index 0000000..b4b631a --- /dev/null +++ b/doc/stdlib_misc.md @@ -0,0 +1,31 @@ +# Miscellaneous Functions + +## `type-of` +``` +(type-of value) +``` +`type-of` returns an atom indicating the type of `value`, or nil if +`value` is nil. + +## `addr-of` +``` +(addr-of value) +``` +`addr-of` returns the raw immediate value of its argument as an int. For +conses, strings, and funptrs, this is their memory address (hence the +name). For ints, floats, and atoms, this returns their raw bytes +interpreted as a signed 2's-complement integer. + +## `set` +``` +(set atom value) +``` +`set` changes the value associated with `atom` in syms-alist, or creates +the association if it does not already exist. It returns `value`. + +## `fun` +``` +(fun atom) +``` +`fun` returns the function associated with `atom` in funs-alist, or nil +if no such function exists. diff --git a/main.c b/main.c index 3dde255..0b914d7 100644 --- a/main.c +++ b/main.c @@ -235,7 +235,7 @@ void lisp_evaluate(lisp_cons* cons, lisp_value* value) { return; } if (cons->car.value.atom == atom_cond) { - lisp_macro_cond(cons->cdr.value.cons, value); + lisp_sform_cond(cons->cdr.value.cons, value); return; } lisp_cons* fun_cons = lisp_alist_get(funs_alist, cons->car); @@ -279,7 +279,7 @@ void lisp_evaluate(lisp_cons* cons, lisp_value* value) { } } lisp_atom atom_cond; -void lisp_macro_cond(lisp_cons* cons, lisp_value* value) { +void lisp_sform_cond(lisp_cons* cons, lisp_value* value) { while (cons != NULL) { lisp_cons* fork = cons->car.value.cons; lisp_evaluate_value(fork->car, value); @@ -298,15 +298,32 @@ void lisp_macro_cond(lisp_cons* cons, lisp_value* value) { } void lisp_set(lisp_cons* cons, lisp_value* value) { + lisp_value key = cons->car; + *value = cons->cdr.value.cons->car; + lisp_cons** pairptr = lisp_alist_getptr(syms_alist, key); + if (pairptr != NULL) { + (*pairptr)->cdr = *value; + return; + } + lisp_cons* new_alist = dbg_malloc(sizeof(lisp_cons)); lisp_cons* pair = dbg_malloc(sizeof(lisp_cons)); - pair->car = cons->car; - pair->cdr = cons->cdr.value.cons->car; - lisp_alist_put(&syms_alist, pair); - *value = pair->cdr; + pair->car = key; + pair->cdr = *value; + new_alist->car.type = LISP_T_CONS; + new_alist->car.value.cons = pair; + new_alist->cdr.type = LISP_T_CONS; + new_alist->cdr.value.cons = syms_alist; + syms_alist = new_alist; + return; } void lisp_fun(lisp_cons* cons, lisp_value* value) { lisp_cons* pair = lisp_alist_get(funs_alist, cons->car); + if (pair == NULL) { + value->type = LISP_T_CONS; + value->value.cons = NULL; + return; + } *value = pair->cdr; } diff --git a/main.h b/main.h index f514525..7d43580 100644 --- a/main.h +++ b/main.h @@ -58,6 +58,6 @@ lisp_atom lisp_defun_native(lisp_string* lstr, lisp_native_fun funptr); void lisp_evaluate_value(lisp_value input, lisp_value* result); void lisp_evaluate(lisp_cons* cons, lisp_value* value); extern lisp_atom atom_cond; -void lisp_macro_cond(lisp_cons* cons, lisp_value* value); +void lisp_sform_cond(lisp_cons* cons, lisp_value* value); #endif diff --git a/native_funs.c b/native_funs.c index e6dfaa5..d4e41a7 100644 --- a/native_funs.c +++ b/native_funs.c @@ -202,7 +202,8 @@ void lisp_eq(lisp_cons* cons, lisp_value* value) { } cons = cons->cdr.value.cons; } - *value = cmp_value; + value->type = LISP_T_ATOM; + value->value.atom = atom_t; } void lisp_num_eq(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car;