summaryrefslogtreecommitdiff
path: root/liblali.c
diff options
context:
space:
mode:
Diffstat (limited to 'liblali.c')
-rw-r--r--liblali.c1404
1 files changed, 1404 insertions, 0 deletions
diff --git a/liblali.c b/liblali.c
new file mode 100644
index 0000000..e2239dd
--- /dev/null
+++ b/liblali.c
@@ -0,0 +1,1404 @@
+/*
+ * lali (Lali Another Lisp Implementation)
+ *
+ * Author: Daniel Cerqueira (dan.git@lispclub.com)
+ * Maintainer: Daniel Cerqueira (dan.git@lispclub.com)
+ * Version: 0.0
+ * Keywords: lali, lisp, implementation, interpreter, lisp1.5,
+ * computer programming language
+ * Homepage: https://gitlab.com/alexandre1985/lali
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Copyright (C) 2025 Daniel Cerqueira
+ *
+ * This file is part of lali.
+ *
+ * lali is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <https://www.gnu.org/licenses/>.
+ *
+ *
+ * lali software is based on tiny-lisp <https://github.com/matp/tiny-lisp/>
+ * version from 2016, written by Matthias Pirstitz, which is released to public
+ * domain under Unlicense <https://unlicense.org/>.
+ */
+
+
+#include "liblali.h"
+
+#define MEMORY_SIZE 3000000UL
+
+static Memory *memory = &(Memory){ MEMORY_SIZE };
+
+Object *symbols = NULL;
+Object *nil = &(Object){ TYPE_SYMBOL, .string = "()" };
+Object *n = &(Object){ TYPE_SYMBOL, .string = "n" };
+Object *t = &(Object){ TYPE_SYMBOL, .string = "t" };
+Object *f = &(Object){ TYPE_SYMBOL, .string = "f" };
+
+static unsigned int seed = 1;
+
+jmp_buf exceptionEnv;
+
+
+// EXCEPTION HANDLING /////////////////////////////////////////////////////////
+
+void exceptionWithObject(Object *object, char *format, ...) {
+ fputs("error: ", stderr);
+
+ if (object) {
+ writeObject(object, true, stderr);
+ fputc(' ', stderr);
+ }
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fputc('\n', stderr);
+
+ longjmp(exceptionEnv, 1);
+}
+
+// GARBAGE COLLECTION /////////////////////////////////////////////////////////
+
+/* This implements Cheney's copying garbage collector, with which memory is
+ * divided into two equal halves (semispaces): from- and to-space. From-space
+ * is where new objects are allocated, whereas to-space is used during garbage
+ * collection.
+ *
+ * When garbage collection is performed, objects that are still in use (live)
+ * are copied from from-space to to-space. To-space then becomes the new
+ * from-space and vice versa, thereby discarding all objects that have not
+ * been copied.
+ *
+ * Our garbage collector takes as input a list of root objects. Objects that
+ * can be reached by recursively traversing this list are considered live and
+ * will be moved to to-space. When we move an object, we must also update its
+ * pointer within the list to point to the objects new location in memory.
+ *
+ * However, this implies that our interpreter cannot use raw pointers to
+ * objects in any function that might trigger garbage collection (or risk
+ * causing a SEGV when accessing an object that has been moved). Instead,
+ * objects must be added to the list and then only accessed through the
+ * pointer inside the list.
+ *
+ * Thus, whenever we would have used a raw pointer to an object, we use a
+ * pointer to the pointer inside the list instead:
+ *
+ * function: pointer to pointer inside list (Object **)
+ * |
+ * v
+ * list of root objects: pointer to object (Object *)
+ * |
+ * v
+ * semispace: object in memory
+ *
+ * GC_ROOTS and GC_PARAM are used to pass the list from function to function.
+ *
+ * GC_TRACE adds an object to the list and declares a variable which points to
+ * the objects pointer inside the list.
+ *
+ * GC_TRACE(gcX, X): add object X to the list and declare Object **gcX
+ * to point to the pointer to X inside the list.
+ */
+
+Object *gcMoveObject(Object *object) {
+ // skip object if it is not within from-space (i.e. on the stack)
+ if (object < (Object *)memory->fromSpace
+ || object >= (Object *)((char *)memory->fromSpace + memory->fromOffset))
+ return object;
+
+ // if the object has already been moved, return its new location
+ if (object->type == (Type)-1)
+ return object->forward;
+
+ // copy object to to-space
+ Object *forward = (Object *)((char *)memory->toSpace + memory->toOffset);
+ memcpy(forward, object, object->size);
+ memory->toOffset += object->size;
+
+ // mark object as moved and set forwarding pointer
+ object->type = (Type)-1;
+ object->forward = forward;
+
+ return object->forward;
+}
+
+void gc(GC_PARAM) {
+ memory->toOffset = 0;
+
+ // move symbols and root objects
+ symbols = gcMoveObject(symbols);
+
+ for (Object *object = GC_ROOTS; object != nil; object = object->cdr)
+ object->car = gcMoveObject(object->car);
+
+ // iterate over objects in to-space and move all objects they reference
+ for (Object *object = memory->toSpace;
+ object < (Object *)((char *)memory->toSpace + memory->toOffset);
+ object = (Object *)((char *)object + object->size)) {
+
+ switch (object->type) {
+ case TYPE_NUMBER:
+ case TYPE_STRING:
+ case TYPE_SYMBOL:
+ case TYPE_PRIMITIVE:
+ break;
+ case TYPE_CONS:
+ object->car = gcMoveObject(object->car);
+ object->cdr = gcMoveObject(object->cdr);
+ break;
+ case TYPE_LAMBDA:
+ case TYPE_MACRO:
+ object->params = gcMoveObject(object->params);
+ object->body = gcMoveObject(object->body);
+ object->env = gcMoveObject(object->env);
+ break;
+ case TYPE_ENV:
+ object->parent = gcMoveObject(object->parent);
+ object->vars = gcMoveObject(object->vars);
+ object->vals = gcMoveObject(object->vals);
+ break;
+ }
+ }
+
+ // swap from- and to-space
+ void *swap = memory->fromSpace;
+ memory->fromSpace = memory->toSpace;
+ memory->toSpace = swap;
+ memory->fromOffset = memory->toOffset;
+}
+
+// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
+
+size_t memoryAlign(size_t size, size_t alignment) {
+ return (size + alignment - 1) & ~(alignment - 1);
+}
+
+Object *memoryAllocObject(Type type, size_t size, GC_PARAM) {
+ size = memoryAlign(size, sizeof (void *));
+
+ // allocate from- and to-space
+ if (!memory->fromSpace) {
+ if (!(memory->fromSpace = mmap(NULL, memory->capacity * 2,
+ PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)))
+ exception("mmap() failed, %s", strerror(errno));
+
+ memory->toSpace = (char *)memory->fromSpace + memory->capacity;
+ }
+
+ // run garbage collection if capacity exceeded
+ if (memory->fromOffset + size >= memory->capacity)
+ gc(GC_ROOTS);
+ if (memory->fromOffset + size >= memory->capacity)
+ exception("out of memory, %lu bytes", (unsigned long)size);
+
+ // allocate object in from-space
+ Object *object = (Object *)((char *)memory->fromSpace + memory->fromOffset);
+ object->type = type;
+ object->size = size;
+ memory->fromOffset += size;
+
+ return object;
+}
+
+// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
+
+Object *newObject(Type type, GC_PARAM) {
+ return memoryAllocObject(type, sizeof (Object), GC_ROOTS);
+}
+
+Object *newObjectFrom(Object **from, GC_PARAM) {
+ Object *object = memoryAllocObject((*from)->type, (*from)->size, GC_ROOTS);
+ memcpy(object, *from, (*from)->size);
+ return object;
+}
+
+Object *newNumber(double number, GC_PARAM) {
+ Object *object = newObject(TYPE_NUMBER, GC_ROOTS);
+ object->number = number;
+ return object;
+}
+
+Object *newObjectWithString(Type type, size_t size, GC_PARAM) {
+ size = (size > sizeof (((Object *)NULL)->string))
+ ? size - sizeof (((Object *)NULL)->string)
+ : 0;
+ return memoryAllocObject(type, sizeof (Object) + size, GC_ROOTS);
+}
+
+Object *newStringWithLength(char *string, size_t length, GC_PARAM) {
+ int nEscapes = 0;
+
+ for (int i = 1; i < length; ++i)
+ if (string[i - 1] == '\\' && strchr("\\\"trn", string[i]))
+ ++i, ++nEscapes;
+
+ Object *object = newObjectWithString(TYPE_STRING,
+ length - nEscapes + 1, GC_ROOTS);
+
+ for (int r = 1, w = 0; r <= length; ++r) {
+ if (string[r - 1] == '\\' && r < length) {
+ switch (string[r]) {
+ case '\\': object->string[w++] = '\\'; r++; break;
+ case '"': object->string[w++] = '"'; r++; break;
+ case 't': object->string[w++] = '\t'; r++; break;
+ case 'r': object->string[w++] = '\r'; r++; break;
+ case 'b': object->string[w++] = '\b'; r++; break;
+ case 'n': object->string[w++] = '\n'; r++; break;
+ default: object->string[w++] = '\\'; break;
+ }
+ } else
+ object->string[w++] = string[r - 1];
+ }
+
+ object->string[length - nEscapes] = '\0';
+ return object;
+}
+
+Object *newString(char *string, GC_PARAM) {
+ return newStringWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newCons(Object **car, Object **cdr, GC_PARAM) {
+ Object *object = newObject(TYPE_CONS, GC_ROOTS);
+ object->car = *car;
+ object->cdr = *cdr;
+ return object;
+}
+
+Object *newSymbolWithLength(char *string, size_t length, GC_PARAM) {
+ for (Object *object = symbols; object != nil; object = object->cdr)
+ if (memcmp(object->car->string, string, length) == 0
+ && object->car->string[length] == '\0')
+ return object->car;
+
+ GC_TRACE(gcObject, newObjectWithString(TYPE_SYMBOL, length + 1, GC_ROOTS));
+ memcpy((*gcObject)->string, string, length);
+ (*gcObject)->string[length] = '\0';
+
+ symbols = newCons(gcObject, &symbols, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *newSymbol(char *string, GC_PARAM) {
+ return newSymbolWithLength(string, strlen(string), GC_ROOTS);
+}
+
+Object *newObjectWithClosure(Type type, Object **params, Object **body, Object **env, GC_PARAM) {
+ Object *list;
+
+ for (list = *params; list->type == TYPE_CONS; list = list->cdr) {
+ if (list->car->type != TYPE_SYMBOL)
+ exceptionWithObject(list->car, "is not a symbol");
+ if (list->car == nil || list->car == n || list->car == t || list->car == f)
+ exceptionWithObject(list->car, "cannot be used as a parameter");
+ }
+
+ if (list != nil && list->type != TYPE_SYMBOL)
+ exceptionWithObject(list, "is not a symbol");
+
+ Object *object = newObject(type, GC_ROOTS);
+
+ object->params = *params;
+ object->body = *body;
+ object->env = *env;
+ return object;
+}
+
+Object *newLambda(Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_LAMBDA, params, body, env, GC_ROOTS);
+}
+
+Object *newMacro(Object **params, Object **body, Object **env, GC_PARAM) {
+ return newObjectWithClosure(TYPE_MACRO, params, body, env, GC_ROOTS);
+}
+
+Object *newPrimitive(int primitive, char *name, GC_PARAM) {
+ Object *object = newObject(TYPE_PRIMITIVE, GC_ROOTS);
+ object->primitive = primitive;
+ object->name = name;
+ return object;
+}
+
+Object *newEnv(Object **func, Object **vals, GC_PARAM) {
+ Object *object = newObject(TYPE_ENV, GC_ROOTS);
+
+ if ((*func) == nil)
+ object->parent = object->vars = object->vals = nil;
+ else {
+ Object *param = (*func)->params, *val = *vals;
+
+ for (int nArgs = 0;; param = param->cdr, val = val->cdr, ++nArgs) {
+ if (param == nil && val == nil)
+ break;
+ else if (param != nil && param->type == TYPE_SYMBOL)
+ break;
+ else if (val != nil && val->type != TYPE_CONS)
+ exceptionWithObject(val, "is not a list");
+ else if (param == nil && val != nil)
+ exceptionWithObject(*func, "expects at most %d arguments", nArgs);
+ else if (param != nil && val == nil) {
+ for (; param->type == TYPE_CONS; param = param->cdr, ++nArgs);
+ exceptionWithObject(*func, "expects at least %d arguments", nArgs);
+ }
+ }
+
+ object->parent = (*func)->env;
+ object->vars = (*func)->params;
+ object->vals = *vals;
+ }
+
+ return object;
+}
+
+// STREAM INPUT ///////////////////////////////////////////////////////////////
+
+/* The purpose of the stream functions is to provide an abstraction over file
+ * and string inputs. In order to accommodate the REPL, we need to be able to
+ * process character special files (such as stdin) character by character and
+ * evaluate expressions as they are being entered.
+ */
+
+int streamGetc(Stream *stream) {
+ if (stream->offset >= stream->length) {
+ switch (stream->type) {
+ case STREAM_TYPE_STRING:
+ // set length if a string was given but its length has not been set
+ if (!stream->length && stream->buffer && *stream->buffer) {
+ stream->length = strlen(stream->buffer);
+ return streamGetc(stream);
+ }
+
+ return EOF;
+
+ case STREAM_TYPE_FILE:
+ // if this is the first read, try to find the size of the file
+ if (!stream->buffer) {
+ struct stat st;
+
+ if (fstat(stream->fd, &st) == -1)
+ exception("fstat() failed, %s", strerror(errno));
+
+ if (S_ISREG(st.st_mode)) {
+ stream->size = st.st_size;
+
+ if (!(stream->buffer = malloc(stream->size)))
+ exception("out of memory, %ld bytes", (long)stream->size);
+
+ stream->capacity = stream->size;
+ } else
+ stream->size = -1;
+ }
+
+ // resize buffer to nearest multiple of BUFSIZ if capacity exceeded
+ if (stream->offset >= stream->capacity) {
+ char *buffer;
+ size_t capacity = stream->offset
+ ? (stream->offset / BUFSIZ + 1) * BUFSIZ
+ : BUFSIZ;
+
+ if (!(buffer = realloc(stream->buffer, capacity)))
+ exception("out of memory, %ld bytes", (long)capacity);
+
+ stream->buffer = buffer;
+ stream->capacity = capacity;
+ }
+
+ // read until offset reached
+ while (stream->length <= stream->offset) {
+ ssize_t nbytes = read(stream->fd, stream->buffer + stream->length,
+ stream->capacity - stream->length);
+
+ if (nbytes > 0)
+ stream->length += nbytes;
+ else if (nbytes < 0 && errno != EINTR)
+ exception("read() failed, %s", strerror(errno));
+
+ if (nbytes == 0 || stream->length == stream->size) {
+ stream->type = STREAM_TYPE_STRING;
+ return streamGetc(stream);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return (unsigned char)stream->buffer[stream->offset++];
+}
+
+Stream *streamSeek(Stream *stream, int offset) {
+ if (offset < 0 && -offset >= stream->offset)
+ stream->offset = 0;
+ else
+ stream->offset += offset;
+ return stream;
+}
+
+int streamPeek(Stream *stream) {
+ int ch = streamGetc(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+// READING S-EXPRESSIONS //////////////////////////////////////////////////////
+
+int readNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int initialReadNext(Stream *stream) {
+ for (;;) {
+ int ch = streamGetc(stream);
+ if (ch == ';' || ch == '#')
+ while ((ch = streamGetc(stream)) != EOF && ch != '\n');
+ if (isspace(ch))
+ continue;
+ return ch;
+ }
+}
+
+int peekForward(Stream *stream, bool shebang) {
+ int ch = (shebang) ? initialReadNext(stream) : readNext(stream);
+ if (ch != EOF)
+ streamSeek(stream, -1);
+ return ch;
+}
+
+int peekNext(Stream *stream) {
+ return peekForward(stream, false);
+}
+
+int readWhile(Stream *stream, int (*predicate)(int ch)) {
+ for (;;) {
+ int ch = streamPeek(stream);
+ if (!predicate(ch))
+ return ch;
+ streamGetc(stream);
+ }
+}
+
+Object *readUnary(Stream *stream, char *symbol, GC_PARAM) {
+ if (peekNext(stream) == EOF)
+ exception("unexpected end of stream in %s", symbol);
+
+ GC_TRACE(gcSymbol, newSymbol(symbol, GC_ROOTS));
+ GC_TRACE(gcObject, readExpr(stream, GC_ROOTS));
+
+ *gcObject = newCons(gcObject, &nil, GC_ROOTS);
+ *gcObject = newCons(gcSymbol, gcObject, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *readString(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+
+ for (bool isEscaped = false;;) {
+ int ch = streamGetc(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in string literal \"%.*s\"",
+ (int)(stream->offset - offset), stream->buffer + offset);
+ if (ch == '"' && !isEscaped)
+ return newStringWithLength(stream->buffer + offset,
+ stream->offset - offset - 1, GC_ROOTS);
+
+ isEscaped = (ch == '\\' && !isEscaped);
+ }
+}
+
+int isSymbolChar(int ch) {
+ static const char *valid = "!#$%&*+-./:<=>?@^_~";
+ return isalnum(ch) || strchr(valid, ch);
+}
+
+Object *readNumberOrSymbol(Stream *stream, GC_PARAM) {
+ size_t offset = stream->offset;
+ int ch = streamPeek(stream);
+
+ // skip optional leading sign
+ if (ch == '+' || ch == '-') {
+ streamGetc(stream);
+ ch = streamPeek(stream);
+ }
+
+ // try to read a number in integer or decimal format
+ if (ch == '.' || isdigit(ch)) {
+ if (isdigit(ch))
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtol(stream->buffer + offset, NULL, 10), GC_ROOTS);
+ if (ch == '.') {
+ ch = streamGetc(stream);
+ if (isdigit(streamPeek(stream))) {
+ ch = readWhile(stream, isdigit);
+ if (!isSymbolChar(ch))
+ return newNumber(strtod(stream->buffer + offset, NULL), GC_ROOTS);
+ }
+ }
+ }
+
+ // non-numeric character encountered, read a symbol
+ readWhile(stream, isSymbolChar);
+ return newSymbolWithLength(stream->buffer + offset,
+ stream->offset - offset, GC_ROOTS);
+}
+
+Object *reverseList(Object *list) {
+ Object *object = nil;
+
+ while (list != nil) {
+ Object *swap = list;
+ list = list->cdr;
+ swap->cdr = object;
+ object = swap;
+ }
+
+ return object;
+}
+
+Object *readList(Stream *stream, GC_PARAM) {
+ GC_TRACE(gcList, nil);
+ GC_TRACE(gcLast, nil);
+
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ exception("unexpected end of stream in list");
+ else if (ch == ')')
+ return reverseList(*gcList);
+ else if (ch == '.' && !isSymbolChar(streamPeek(stream))) {
+ if (*gcLast == nil)
+ exception("unexpected dot at start of list");
+ if ((ch = peekNext(stream)) == ')')
+ exception("expected object at end of dotted list");
+ if (!(*gcLast = readExpr(stream, GC_ROOTS)))
+ exception("unexpected end of stream in dotted list");
+ if ((ch = peekNext(stream)) != ')')
+ exception("unexpected object at end of dotted list");
+
+ readNext(stream);
+ Object *list = reverseList(*gcList);
+ (*gcList)->cdr = *gcLast;
+
+ return list;
+ } else {
+ *gcLast = readExpr(streamSeek(stream, -1), GC_ROOTS);
+ *gcList = newCons(gcLast, gcList, GC_ROOTS);
+ }
+ }
+}
+
+Object *readExpr(Stream *stream, GC_PARAM) {
+ for (;;) {
+ int ch = readNext(stream);
+ if (ch == EOF)
+ return NULL;
+ else if (ch == '\'')
+ return readUnary(stream, "quote", GC_ROOTS);
+ else if (ch == '\\')
+ return readUnary(stream, "say", GC_ROOTS);
+ else if (ch == '"')
+ return readString(stream, GC_ROOTS);
+ else if (ch == '(')
+ return readList(stream, GC_ROOTS);
+ else if (isSymbolChar(ch)
+ && (ch != '.' || isSymbolChar(streamPeek(stream))))
+ return readNumberOrSymbol(streamSeek(stream, -1), GC_ROOTS);
+ else
+ exception("unexpected character, `%c'", ch);
+ }
+}
+
+// WRITING OBJECTS ////////////////////////////////////////////////////////////
+
+void writeObject(Object *object, bool readably, FILE *file) {
+ switch (object->type) {
+#define CASE(type, ...) \
+ case type: \
+ fprintf(file, __VA_ARGS__); \
+ break
+ CASE(TYPE_NUMBER, "%g", object->number);
+ CASE(TYPE_SYMBOL, "%s", object->string);
+ CASE(TYPE_PRIMITIVE, "#<Primitive %s>", object->name);
+#undef CASE
+ case TYPE_STRING:
+ if (readably) {
+ fputc('"', file);
+ for (char *string = object->string; *string; ++string) {
+ switch (*string) {
+ case '"': fputs("\\\"", file); break;
+ case '\t': fputs("\\t", file); break;
+ case '\r': fputs("\\r", file); break;
+ case '\b': fputs("\\b", file); break;
+ case '\n': fputs("\\n", file); break;
+ case '\\': fputs("\\\\", file); break;
+ default: fputc(*string, file); break;
+ }
+ }
+ fputc('"', file);
+ } else
+ fprintf(file, "%s", object->string);
+ break;
+ case TYPE_CONS:
+ if (readably)
+ fputc('(', file);
+ writeObject(object->car, true, file);
+ while (object->cdr != nil) {
+ object = object->cdr;
+ if (object->type == TYPE_CONS) {
+ fputc(' ', file);
+ writeObject(object->car, true, file);
+ } else {
+ fputs(" . ", file);
+ writeObject(object, true, file);
+ break;
+ }
+ }
+ if (readably)
+ fputc(')', file);
+ break;
+#define CASE(type, name, object) \
+ case type: \
+ fprintf(file, "#<%s ", name); \
+ writeObject(object, readably, file); \
+ fprintf(file, ">"); \
+ break
+ CASE(TYPE_LAMBDA, "Lambda", object->params);
+ CASE(TYPE_MACRO, "Macro", object->params);
+ CASE(TYPE_ENV, "Env", object->vars);
+#undef CASE
+ }
+}
+
+// ENVIRONMENT ////////////////////////////////////////////////////////////////
+
+/* An environment consists of a pointer to its parent environment (if any) and
+ * two parallel lists - vars and vals.
+ *
+ * Case 1 - vars is a regular list:
+ * vars: (a b c), vals: (1 2 3) ; a = 1, b = 2, c = 3
+ *
+ * Case 2 - vars is a dotted list:
+ * vars: (a b . c), vals: (1 2) ; a = 1, b = 2, c = nil
+ * vars: (a b . c), vals: (1 2 3) ; a = 1, b = 2, c = (3)
+ * vars: (a b . c), vals: (1 2 3 4 5) ; a = 1, b = 2, c = (3 4 5)
+ *
+ * Case 3 - vars is a symbol:
+ * vars: a, vals: nil ; a = nil
+ * vars: a, vals: (1) ; a = (1)
+ * vars: a, vals: (1 2 3) ; a = (1 2 3)
+ *
+ * Case 4 - vars and vals are both nil:
+ * vars: nil, vals: nil
+ */
+
+Object *envLookup(Object *var, Object *env) {
+ for (; env != nil; env = env->parent) {
+ Object *vars = env->vars, *vals = env->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr)
+ if (vars->car == var)
+ return vals->car;
+
+ if (vars == var)
+ return vals;
+ }
+
+ exceptionWithObject(var, "has no value");
+}
+
+Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcVars, newCons(var, &nil, GC_ROOTS));
+ GC_TRACE(gcVals, newCons(val, &nil, GC_ROOTS));
+
+ (*gcVars)->cdr = (*env)->vars, (*env)->vars = *gcVars;
+ (*gcVals)->cdr = (*env)->vals, (*env)->vals = *gcVals;
+
+ return *val;
+}
+
+Object *envSet(Object **var, Object **val, Object **env, GC_PARAM) {
+ GC_TRACE(gcEnv, *env);
+
+ for (;;) {
+ Object *vars = (*gcEnv)->vars, *vals = (*gcEnv)->vals;
+
+ for (; vars->type == TYPE_CONS; vars = vars->cdr, vals = vals->cdr) {
+ if (vars->car == *var)
+ return vals->car = *val;
+ if (vars->cdr == *var)
+ return vals->cdr = *val;
+ }
+
+ if ((*gcEnv)->parent == nil)
+ return envAdd(var, val, gcEnv, GC_ROOTS);
+ else
+ *gcEnv = (*gcEnv)->parent;
+ }
+}
+
+// PRIMITIVES /////////////////////////////////////////////////////////////////
+
+Object *primitiveSpace(Object **args, GC_PARAM) {
+ return ((*args)->car == nil) ? t : f;
+}
+
+Object *primitiveAtom(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ return (first != nil && first->type != TYPE_CONS) ? t : f;
+}
+
+/* Object *primitiveEq(Object **args, GC_PARAM) {
+ * Object *first = (*args)->car, *second = (*args)->cdr->car;
+ *
+ * if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ * return (first->number == second->number) ? t : f;
+ * else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ * return !strcmp(first->string, second->string) ? t : f;
+ * else
+ * return (first == second) ? t : f;
+ * }
+ */
+
+Object *primitiveDif(Object **args, GC_PARAM) {
+ Object *first = (*args)->car, *second = (*args)->cdr->car;
+
+ if (first->type == TYPE_NUMBER && second->type == TYPE_NUMBER)
+ return (first->number != second->number) ? t : f;
+ else if (first->type == TYPE_STRING && second->type == TYPE_STRING)
+ return strcmp(first->string, second->string) ? t : f;
+ else
+ return (first != second) ? t : f;
+}
+
+Object *primitiveCar(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ else if (first->type == TYPE_CONS)
+ return first->car;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCdr(Object **args, GC_PARAM) {
+ Object *first = (*args)->car;
+
+ if (first == nil)
+ return nil;
+ else if (first->type == TYPE_CONS)
+ return first->cdr;
+ else
+ exceptionWithObject(first, "is not a list");
+}
+
+Object *primitiveCons(Object **args, GC_PARAM) {
+ GC_TRACE(gcFirst, (*args)->car);
+ GC_TRACE(gcSecond, (*args)->cdr->car);
+
+ return newCons(gcFirst, gcSecond, GC_ROOTS);
+}
+
+Object *primitivePrint(Object **args, GC_PARAM) {
+ writeObject((*args)->car, true, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitivePrinc(Object **args, GC_PARAM) {
+ writeObject((*args)->car, false, stdout);
+ fputc(' ', stdout);
+ return (*args)->car;
+}
+
+Object *primitiveNewline(Object **args, GC_PARAM) {
+ fputc('\n', stdout);
+ return n;
+}
+
+Object *primitiveRead(Object **args, GC_PARAM) {
+ Stream stream = { STREAM_TYPE_FILE, .fd = STDIN_FILENO };
+
+ fflush(stdout);
+ if (peekNext(&stream) == EOF) {
+ fputc('\n', stdout);
+ return n;
+ }
+ return readExpr(&stream, GC_ROOTS);
+}
+
+Object *primitiveTime(Object **args, GC_PARAM) {
+ time_t secs;
+ struct tm *today;
+
+ time(&secs);
+ today = localtime(&secs);
+
+ GC_TRACE(gcSymbol, nil);
+ GC_TRACE(gcCons, nil);
+
+ *gcSymbol = newNumber(today->tm_gmtoff, GC_ROOTS);
+ *gcCons = newCons(gcSymbol, gcCons, GC_ROOTS);
+ *gcCons = newCons(today->tm_isdst ? &t : &f, gcCons, GC_ROOTS);
+
+ int tmp [7] = {
+ today->tm_wday, today->tm_year+1900, today->tm_mon+1, today->tm_mday,
+ today->tm_hour, today->tm_min, today->tm_sec
+ };
+
+ for (int i = 0; i < 7; i++) {
+ *gcSymbol = newNumber(tmp[i], GC_ROOTS);
+ *gcCons = newCons(gcSymbol, gcCons, GC_ROOTS);
+ }
+
+ return *gcCons;
+}
+
+Object *primitiveRandom(Object **args, GC_PARAM) {
+ srandom((unsigned int)(seed + time(NULL)));
+ double number = (double)random();
+ seed = (unsigned int)number;
+
+ if (*args == nil)
+ return newNumber(number, GC_ROOTS);
+ else
+ return
+ newNumber(((unsigned int)number % (unsigned int)(*args)->car->number),
+ GC_ROOTS);
+}
+
+#define DEFINE_PRIMITIVE_ARITHMETIC(name, op, init, datatype) \
+ Object *name(Object **args, GC_PARAM) { \
+ if (*args == nil) \
+ return newNumber(init, GC_ROOTS); \
+ else if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *object, *rest; \
+ \
+ if ((*args)->cdr == nil) { \
+ object = newNumber(init, GC_ROOTS); \
+ rest = *args; \
+ } else { \
+ GC_TRACE(gcFirst, (*args)->car); \
+ object = newObjectFrom(gcFirst, GC_ROOTS); \
+ rest = (*args)->cdr; \
+ } \
+ \
+ for (; rest != nil; rest = rest->cdr) { \
+ if (rest->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->car, "is not a number"); \
+ \
+ object->number = \
+ (datatype)object->number op (datatype)rest->car->number; \
+ } \
+ \
+ return object; \
+ } \
+ }
+
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveAdd, +, 0, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveSubtract, -, 0, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveMultiply, *, 1, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveDivide, /, 1, double)
+DEFINE_PRIMITIVE_ARITHMETIC(primitiveRemainder, %, 1, int )
+
+#define DEFINE_PRIMITIVE_RELATIONAL(name, op, until_the_end) \
+ Object *name(Object **args, GC_PARAM) { \
+ if ((*args)->car->type != TYPE_NUMBER) \
+ exceptionWithObject((*args)->car, "is not a number"); \
+ else { \
+ Object *rest = *args; \
+ bool result = until_the_end; \
+ \
+ for (; (result == until_the_end) && rest->cdr != nil; \
+ rest = rest->cdr) { \
+ if (rest->cdr->car->type != TYPE_NUMBER) \
+ exceptionWithObject(rest->cdr->car, "is not a number"); \
+ else if (until_the_end) \
+ result &= rest->car->number op rest->cdr->car->number; \
+ else \
+ result = rest->car->number op rest->cdr->car->number; \
+ } \
+ return result ? t : f; \
+ } \
+ }
+
+DEFINE_PRIMITIVE_RELATIONAL(primitiveDifferent, !=, false)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveEqual, ==, true) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveLess, < , true)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveLessEqual, <=, true) */
+DEFINE_PRIMITIVE_RELATIONAL(primitiveGreater, > , true)
+/* DEFINE_PRIMITIVE_RELATIONAL(primitiveGreaterEqual, >=, true) */
+
+typedef struct Primitive {
+ char *name;
+ int nMinArgs, nMaxArgs;
+ Object *(* eval)(Object **args, GC_PARAM);
+} Primitive;
+
+Primitive primitives[] = {
+ { "eval", 1, 1 /* special form */ },
+ { "quote", 1, 1 /* special form */ },
+ { "say", 1, 1 /* special form */ },
+ { "set", 0, -2 /* special form */ },
+ { "prog", 0, -1 /* special form */ },
+ { "progs", 1, -1 /* special form */ },
+ /* { "if", 2, 3 /\* special form *\/ }, */
+ { "cond", 0, -1 /* special form */ },
+ { "fill", 0, -1 /* special form */ },
+ { "lambda", 1, -1 /* special form */ },
+ { "macro", 1, -1 /* special form */ },
+ { "space", 1, 1, primitiveSpace },
+ { "atom", 1, 1, primitiveAtom },
+ /* { "eq", 2, 2, primitiveEq }, */
+ { "dif", 2, 2, primitiveDif },
+ { "car", 1, 1, primitiveCar },
+ { "cdr", 1, 1, primitiveCdr },
+ { "cons", 2, 2, primitiveCons },
+ { "print", 1, 1, primitivePrint },
+ { "princ", 1, 1, primitivePrinc },
+ { "newline", 0, 0, primitiveNewline },
+ { "read", 0, 0, primitiveRead },
+ { "time", 0, 0, primitiveTime },
+ { "random", 0, 1, primitiveRandom },
+ { "+", 0, -1, primitiveAdd },
+ { "-", 1, -1, primitiveSubtract },
+ { "*", 0, -1, primitiveMultiply },
+ { "/", 1, -1, primitiveDivide },
+ { "%", 1, -1, primitiveRemainder },
+ /* { "=", 1, -1, primitiveEqual }, */
+ { "!", 1, -1, primitiveDifferent },
+ { "<", 1, -1, primitiveLess },
+ /* { "<=", 1, -1, primitiveLessEqual }, */
+ { ">", 1, -1, primitiveGreater },
+ /* { ">=", 1, -1, primitiveGreaterEqual } */
+};
+
+// Special forms handled by evalExpr. Must be in the same order as above.
+enum {
+ PRIMITIVE_EVAL,
+ PRIMITIVE_QUOTE,
+ PRIMITIVE_SAY,
+ PRIMITIVE_SET,
+ PRIMITIVE_PROG,
+ PRIMITIVE_PROGS,
+ /* PRIMITIVE_IF, */
+ PRIMITIVE_COND,
+ PRIMITIVE_FILL,
+ PRIMITIVE_LAMBDA,
+ PRIMITIVE_MACRO
+};
+
+// EVALUATION /////////////////////////////////////////////////////////////////
+
+/* Scheme-style tail recursive evaluation. evalProg, evalProgs, evalCond, and
+ * PRIMITIVE_EVAL, return the object in the tail recursive position to be
+ * evaluated by evalExpr. Macros are expanded in-place the first time they are
+ * evaluated.
+ */
+
+bool isSymbolAPrimitive(Object *symbol) {
+ int nPrimitives = sizeof (primitives) / sizeof (primitives[0]);
+ for (int i = 0; i < nPrimitives; ++i)
+ if (!strcmp(primitives[i].name, symbol->string)) return true;
+
+ return false;
+}
+
+Object *evalSet(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else {
+ GC_TRACE(gcVar, (*args)->car);
+ GC_TRACE(gcVal, (*args)->cdr->car);
+
+ *gcVar = evalExpr(gcVar, env, GC_ROOTS);
+
+ if (*gcVar == nil || *gcVar == n || *gcVar == t || *gcVar == f)
+ exceptionWithObject(*gcVar, "is a constant and cannot be set");
+ else if (isSymbolAPrimitive(*gcVar))
+ exceptionWithObject(*gcVar, "is a primitive name and cannot be set");
+ else if ((*gcVar)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcVar, "is not an atom and cannot be set");
+
+ *gcVal = evalExpr(gcVal, env, GC_ROOTS);
+ envSet(gcVar, gcVal, env, GC_ROOTS);
+
+ if ((*args)->cdr->cdr == nil)
+ return *gcVal;
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr->cdr);
+ return evalSet(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalProg(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, nil);
+ GC_TRACE(gcCdr, nil);
+
+ for (;;) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->cdr == nil)
+ return (*args)->car;
+ else {
+ *gcObject = (*args)->car;
+ *gcCdr = (*args)->cdr;
+
+ evalExpr(gcObject, env, GC_ROOTS);
+ *args = *gcCdr;
+ /* return evalProg(gcCdr, env, GC_ROOTS); */
+ }
+ }
+}
+
+Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, nil);
+ GC_TRACE(gcCdr, nil);
+
+ for (;;) {
+ if (*body == nil)
+ return *stop;
+ else if ((*body)->cdr == nil)
+ return (*body)->car;
+ else {
+ /* GC_TRACE(gcEnv, *env); */
+ /* GC_TRACE(gcBody, nil); */
+ /* GC_TRACE(gcStop, nil); */
+
+ /* if ((*stop)->type == TYPE_LAMBDA) { */
+ /* *gcBody = (*stop)->body; */
+ /* *gcEnv = newEnv(stop, gcBody, GC_ROOTS); */
+ /* *gcStop = evalExpr(gcBody, gcEnv, GC_ROOTS); */
+
+ /* if (*gcStop == t) */
+ /* return *gcObject; */
+ /* } */
+
+ *gcObject = (*body)->car;
+ *gcCdr = (*body)->cdr;
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+
+ if (*gcObject == *stop)
+ return *gcObject;
+
+ *body = *gcCdr;
+ /* return progs1(stop, gcCdr, env, GC_ROOTS); */
+ }
+ }
+}
+
+Object *evalProgs(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcStop, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ *gcStop = evalExpr(gcStop, env, GC_ROOTS);
+
+ if ((*gcStop)->type != TYPE_SYMBOL)
+ exceptionWithObject(*gcStop, "is not a symbol");
+
+ return progs1(gcStop, gcBody, env, GC_ROOTS);
+}
+
+/* Object *evalIf(Object **args, Object **env, GC_PARAM) {
+ * GC_TRACE(gcObject, (*args)->car);
+ *
+ * if (evalExpr(gcObject, env, GC_ROOTS) != nil)
+ * return (*args)->cdr->car;
+ * else if ((*args)->cdr->cdr != nil)
+ * return (*args)->cdr->cdr->car;
+ * else
+ * return n;
+ * }
+ */
+
+Object *evalCond(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar != f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalCond(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalFill(Object **args, Object **env, GC_PARAM) {
+ if (*args == nil)
+ return n;
+ else if ((*args)->car->type != TYPE_CONS)
+ exceptionWithObject((*args)->car, "is not a list");
+ else {
+ GC_TRACE(gcCar, (*args)->car->car);
+ GC_TRACE(gcCdr, (*args)->car->cdr);
+
+ if (*gcCdr == nil) return *gcCar;
+
+ *gcCar = evalExpr(gcCar, env, GC_ROOTS);
+ if (*gcCar == n)
+ return n;
+ else if (*gcCar == f)
+ return evalProg(gcCdr, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcArgs, (*args)->cdr);
+ return evalFill(gcArgs, env, GC_ROOTS);
+ }
+ }
+}
+
+Object *evalLambda(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcParams, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ return newLambda(gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *evalMacro(Object **args, Object **env, GC_PARAM) {
+ GC_TRACE(gcParams, (*args)->car);
+ GC_TRACE(gcBody, (*args)->cdr);
+
+ return newMacro(gcParams, gcBody, env, GC_ROOTS);
+}
+
+Object *expandMacro(Object **macro, Object **args, GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(macro, args, GC_ROOTS));
+ GC_TRACE(gcBody, (*macro)->body);
+
+ GC_TRACE(gcObject, evalProg(gcBody, gcEnv, GC_ROOTS));
+ *gcObject = evalExpr(gcObject, gcEnv, GC_ROOTS);
+
+ return *gcObject;
+}
+
+Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM) {
+ GC_TRACE(gcObject, expandMacro(macro, args, GC_ROOTS));
+
+ if ((*gcObject)->type == TYPE_CONS) {
+ (*cons)->car = (*gcObject)->car;
+ (*cons)->cdr = (*gcObject)->cdr;
+ } else {
+ (*cons)->car = newSymbol("prog", GC_ROOTS);
+ (*cons)->cdr = newCons(gcObject, &nil, GC_ROOTS);
+ }
+
+ return *cons;
+}
+
+Object *evalList(Object **args, Object **env, GC_PARAM) {
+ if ((*args)->type != TYPE_CONS)
+ return evalExpr(args, env, GC_ROOTS);
+ else {
+ GC_TRACE(gcObject, (*args)->car);
+ GC_TRACE(gcArgs, (*args)->cdr);
+
+ *gcObject = evalExpr(gcObject, env, GC_ROOTS);
+ *gcArgs = evalList(gcArgs, env, GC_ROOTS);
+
+ return newCons(gcObject, gcArgs, GC_ROOTS);
+ }
+}
+
+Object *evalExpr(Object **object, Object **env, GC_PARAM) {
+ GC_TRACE(gcObject, *object);
+ GC_TRACE(gcEnv, *env);
+
+ GC_TRACE(gcFunc, nil);
+ GC_TRACE(gcArgs, nil);
+ GC_TRACE(gcBody, nil);
+
+ for (;;) {
+ if ((*gcObject)->type == TYPE_SYMBOL)
+ return envLookup(*gcObject, *gcEnv);
+ if ((*gcObject)->type != TYPE_CONS)
+ return *gcObject;
+
+ *gcFunc = (*gcObject)->car;
+ *gcArgs = (*gcObject)->cdr;
+
+ *gcFunc = evalExpr(gcFunc, gcEnv, GC_ROOTS);
+ *gcBody = nil;
+
+ if ((*gcFunc)->type == TYPE_LAMBDA) {
+ *gcBody = (*gcFunc)->body;
+ *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ *gcEnv = newEnv(gcFunc, gcArgs, GC_ROOTS);
+ *gcObject = evalProg(gcBody, gcEnv, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_MACRO) {
+ *gcObject = expandMacroTo(gcFunc, gcArgs, gcObject, GC_ROOTS);
+ } else if ((*gcFunc)->type == TYPE_PRIMITIVE) {
+ Primitive *primitive = &primitives[(*gcFunc)->primitive];
+ int nArgs = 0;
+
+ for (Object *args = *gcArgs; args != nil; args = args->cdr, nArgs++)
+ if (args->type != TYPE_CONS)
+ exceptionWithObject(args, "is not a list");
+
+ if (nArgs < primitive->nMinArgs)
+ exceptionWithObject(*gcFunc, "expects at least %d arguments",
+ primitive->nMinArgs);
+ if (nArgs > primitive->nMaxArgs && primitive->nMaxArgs >= 0)
+ exceptionWithObject(*gcFunc, "expects at most %d arguments",
+ primitive->nMaxArgs);
+ if (primitive->nMaxArgs < 0 && nArgs % -primitive->nMaxArgs)
+ exceptionWithObject(*gcFunc, "expects a multiple of %d arguments",
+ -primitive->nMaxArgs);
+
+ switch ((*gcFunc)->primitive) {
+ case PRIMITIVE_EVAL: *gcArgs = (*gcArgs)->car;
+ *gcObject = evalExpr(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_QUOTE: return (*gcArgs)->car;
+ case PRIMITIVE_SAY: *gcArgs = (*gcArgs)->car;
+ return newCons(&nil, gcArgs, GC_ROOTS);
+ case PRIMITIVE_SET: return evalSet(gcArgs, gcEnv, GC_ROOTS);
+ case PRIMITIVE_PROG: *gcObject = evalProg(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_PROGS: *gcObject = evalProgs(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ /* case PRIMITIVE_IF: *gcObject = evalIf(gcArgs, gcEnv, GC_ROOTS);
+ * break;
+ */
+ case PRIMITIVE_COND: *gcObject = evalCond(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_FILL: *gcObject = evalFill(gcArgs, gcEnv, GC_ROOTS);
+ break;
+ case PRIMITIVE_LAMBDA: return evalLambda(gcArgs, gcEnv, GC_ROOTS);
+ case PRIMITIVE_MACRO: return evalMacro(gcArgs, gcEnv, GC_ROOTS);
+ default: *gcArgs = evalList(gcArgs, gcEnv, GC_ROOTS);
+ return primitive->eval(gcArgs, GC_ROOTS);
+ }
+ } else
+ exceptionWithObject(*gcFunc, "is not a function");
+ }
+}
+
+// STANDARD LIBRARY ///////////////////////////////////////////////////////////
+
+#define LISP(...) #__VA_ARGS__
+
+static char *stdlib = LISP(
+ (set (quote list) (lambda args args))
+
+ (set (quote defmacro) (macro (name params . body)
+ (list (quote set) (list quote name) (list (quote macro) params . body))))
+
+ (defmacro defun (name params . body)
+ (list (quote set) (list quote name) (list (quote lambda) params . body)))
+
+ (defun not (x) (fill ((dif x n) n)
+ (x t)
+ (f f)))
+
+ (defun ap (x) (not (space x)))
+
+ (defun listp (x) (not (atom x)))
+
+ (defun zerop (x) (not (! x 0)))
+
+ (defmacro and args
+ (fill ((ap args) t)
+ ((ap (cdr args)) (car args))
+ (f (list (quote fill)
+ (list (list (quote not) (car args))
+ (cons (quote and) (cdr args)))
+ (list (quote f) (quote f))))))
+
+ (defun map (func xs)
+ (fill ((ap xs) ())
+ (f (cons (func (car xs)) (map func (cdr xs))))))
+
+ (defmacro or args
+ (fill ((ap args) f)
+ (f (cons (quote fill)
+ (append (map (lambda (x) (list (list (quote not) x) x))
+ args)
+ (list (list f f)))))))
+
+ (defun consp (x) (and (listp x) (ap x)))
+
+ (defun differ (x y)
+ (fill ((or (consp x) (consp y)) (dif x y))
+ (f (or (differ (car x) (car y))
+ (differ (cdr x) (cdr y))))))
+
+ (defun nth (num xs)
+ (fill ((dif num 0) (car xs))
+ (f (nth (- num 1) (cdr xs)))))
+
+ (defun append (xs y)
+ (fill ((ap xs) y)
+ (f (cons (car xs) (append (cdr xs) y)))))
+);
+
+// MAIN ///////////////////////////////////////////////////////////////////////
+
+Object *newRootEnv(GC_PARAM) {
+ GC_TRACE(gcEnv, newEnv(&nil, &nil, GC_ROOTS));
+ GC_TRACE(gcVar, nil);
+ GC_TRACE(gcVal, nil);
+
+ // add constants
+ envSet(&nil, &nil, gcEnv, GC_ROOTS);
+ envSet(&n, &n, gcEnv, GC_ROOTS);
+ envSet(&t, &t, gcEnv, GC_ROOTS);
+ envSet(&f, &f, gcEnv, GC_ROOTS);
+
+ // add primitives
+ int nPrimitives = sizeof (primitives) / sizeof (primitives[0]);
+
+ for (int i = 0; i < nPrimitives; ++i) {
+ *gcVar = newSymbol(primitives[i].name, GC_ROOTS);
+ *gcVal = newPrimitive(i, primitives[i].name, GC_ROOTS);
+
+ envSet(gcVar, gcVal, gcEnv, GC_ROOTS);
+ }
+
+ // add standard library
+ Stream stream = { STREAM_TYPE_STRING, .buffer = stdlib };
+ GC_TRACE(gcObject, nil);
+
+ while (peekNext(&stream) != EOF) {
+ *gcObject = nil;
+ *gcObject = readExpr(&stream, GC_ROOTS);
+ evalExpr(gcObject, gcEnv, GC_ROOTS);
+ }
+
+ return *gcEnv;
+}