/*
* 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 .
*
*
* lali software is based on tiny-lisp
* version from 2016, written by Matthias Pirstitz, which is released to public
* domain under Unlicense .
*/
#ifndef LIBLALI_H
#define LIBLALI_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
typedef struct Object Object;
typedef enum Type {
TYPE_NUMBER,
TYPE_STRING,
TYPE_SYMBOL,
TYPE_CONS,
TYPE_LAMBDA,
TYPE_MACRO,
TYPE_PRIMITIVE,
TYPE_ENV
} Type;
struct Object {
Type type;
size_t size;
union {
/* struct { double number; }; // number */
struct { char string[sizeof (Object *[3])]; }; // string, symbol, number
struct { Object *car, *cdr; }; // cons
struct { Object *params, *body, *env; }; // lambda, macro
struct { int primitive; char *name; }; // primitive
struct { Object *parent, *vars, *vals; }; // env
struct { Object *forward; }; // forwarding pointer
};
};
extern Object *symbols;
extern Object *nil;
extern Object *n;
extern Object *t;
extern Object *f;
typedef enum StreamType {
STREAM_TYPE_STRING,
STREAM_TYPE_FILE
} StreamType;
typedef struct Stream {
StreamType type;
char *buffer;
int fd;
size_t length, capacity;
off_t offset, size;
} Stream;
typedef struct Memory {
size_t capacity, fromOffset, toOffset;
void *fromSpace, *toSpace;
} Memory;
extern jmp_buf exceptionEnv;
// EXCEPTION HANDLING /////////////////////////////////////////////////////////
#define exception(...) exceptionWithObject(NULL, __VA_ARGS__)
#ifdef __GNUC__
void exceptionWithObject(Object *object, char *format, ...)
__attribute__ ((noreturn, format(printf, 2, 3)));
#endif
void exceptionWithObject(Object *object, char *format, ...);
// 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.
*/
#define GC_ROOTS gcRoots
#define GC_PARAM Object *GC_ROOTS
#define GC_PASTE1(name, id) name ## id
#define GC_PASTE2(name, id) GC_PASTE1(name, id)
#define GC_UNIQUE(name) GC_PASTE2(name, __LINE__)
#define GC_TRACE(name, init) \
Object GC_UNIQUE(GC_ROOTS) = { TYPE_CONS, .car = init, .cdr = GC_ROOTS }; \
Object **name = &GC_UNIQUE(GC_ROOTS).car; \
GC_ROOTS = &GC_UNIQUE(GC_ROOTS)
Object *gcMoveObject(Object *object);
void gc(GC_PARAM);
// MEMORY MANAGEMENT //////////////////////////////////////////////////////////
size_t memoryAlign(size_t size, size_t alignment);
Object *memoryAllocObject(Type type, size_t size, GC_PARAM);
// CONSTRUCTING OBJECTS ///////////////////////////////////////////////////////
Object *newObject(Type type, GC_PARAM);
Object *newObjectFrom(Object **from, GC_PARAM);
//Object *newNumber(double number, GC_PARAM);
Object *newNumber(char *string, GC_PARAM);
Object *newObjectWithString(Type type, size_t size, GC_PARAM);
Object *newStringWithLength(char *string, size_t length, GC_PARAM);
Object *newString(char *string, GC_PARAM);
Object *newCons(Object **car, Object **cdr, GC_PARAM);
//Object *newSymbolWithLength(char *string, size_t length, GC_PARAM);
Object *newSymbolWithLength(Type type, char *string, size_t length, GC_PARAM);
Object *newSymbol(char *string, GC_PARAM);
Object *newObjectWithClosure(Type type, Object **params, Object **body, Object **env, GC_PARAM);
Object *newLambda(Object **params, Object **body, Object **env, GC_PARAM);
Object *newMacro(Object **params, Object **body, Object **env, GC_PARAM);
Object *newPrimitive(int primitive, char *name, GC_PARAM);
Object *newEnv(Object **func, Object **vals, GC_PARAM);
// 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);
Stream *streamSeek(Stream *stream, int offset);
int streamPeek(Stream *stream);
// READING S-EXPRESSIONS //////////////////////////////////////////////////////
Object *readExpr(Stream *stream, GC_PARAM);
int readNext(Stream *stream);
int initialReadNext(Stream *stream);
int peekForward(Stream *stream, bool shebang);
int peekNext(Stream *stream);
int readWhile(Stream *stream, int (*predicate)(int ch));
Object *readUnary(Stream *stream, char *symbol, GC_PARAM);
Object *readString(Stream *stream, GC_PARAM);
int isSymbolChar(int ch);
Object *readNumberOrSymbol(Stream *stream, GC_PARAM);
Object *reverseList(Object *list);
Object *readList(Stream *stream, GC_PARAM);
// WRITING OBJECTS ////////////////////////////////////////////////////////////
char *removeZeroPadding(char *string);
void writeObject(Object *object, bool readably, FILE *file);
// 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);
Object *envAdd(Object **var, Object **val, Object **env, GC_PARAM);
Object *envSet(Object **var, Object **val, Object **env, GC_PARAM);
// PRIMITIVES /////////////////////////////////////////////////////////////////
Object *primitiveSpace(Object **args, GC_PARAM);
Object *primitiveAtom(Object **args, GC_PARAM);
/* Object *primitiveEq(Object **args, GC_PARAM);
*/
Object *primitiveDif(Object **args, GC_PARAM);
Object *primitiveCar(Object **args, GC_PARAM);
Object *primitiveCdr(Object **args, GC_PARAM);
Object *primitiveCons(Object **args, GC_PARAM);
Object *primitivePrint(Object **args, GC_PARAM);
Object *primitivePrinc(Object **args, GC_PARAM);
Object *primitiveNewline(Object **args, GC_PARAM);
Object *primitiveRead(Object **args, GC_PARAM);
Object *primitiveTime(Object **args, GC_PARAM);
Object *primitiveRandom(Object **args, GC_PARAM);
Object *primitiveAdd(Object **args, GC_PARAM);
Object *primitiveSubtract(Object **args, GC_PARAM);
Object *primitiveMultiply(Object **args, GC_PARAM);
Object *primitiveDivide(Object **args, GC_PARAM);
Object *primitiveRemainder(Object **args, GC_PARAM);
Object *primitiveLess(Object **args, GC_PARAM);
Object *primitiveGreater(Object **args, GC_PARAM);
Object *primitiveJoin(Object **args, GC_PARAM);
Object *primitiveSplit(Object **args, GC_PARAM);
// 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);
Object *evalExpr(Object **object, Object **env, GC_PARAM);
Object *evalSet(Object **args, Object **env, GC_PARAM);
Object *evalProg(Object **args, Object **env, GC_PARAM);
Object *progs1(Object **stop, Object **body, Object **env, GC_PARAM);
Object *evalProgs(Object **args, Object **env, GC_PARAM);
Object *evalCond(Object **args, Object **env, GC_PARAM);
Object *evalFill(Object **args, Object **env, GC_PARAM);
Object *evalLambda(Object **args, Object **env, GC_PARAM);
Object *evalMacro(Object **args, Object **env, GC_PARAM);
Object *expandMacro(Object **macro, Object **args, GC_PARAM);
Object *expandMacroTo(Object **macro, Object **args, Object **cons, GC_PARAM);
Object *evalList(Object **args, Object **env, GC_PARAM);
Object *newRootEnv(GC_PARAM);
#endif