summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.org28
-rw-r--r--src/runtime.c10
3 files changed, 35 insertions, 5 deletions
diff --git a/Makefile b/Makefile
index 6eab26f..1de4ff4 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ SRCS := src/main.c src/utils.c src/lexer.c src/parser.c src/runtime.c src/librar
OBJECTS := $(SRCS:src/%.c=build/%.o)
EXE := pit
-CC := musl-gcc
+CC ?= gcc
CHK_SOURCES ?= $(SRCS)
CPPFLAGS ?= -MMD -MP
CFLAGS ?= -Ideps/ -Isrc/ -Wall -Wextra -Wpedantic -ftrapv --std=c23 -g
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..9a71e0f
--- /dev/null
+++ b/README.org
@@ -0,0 +1,28 @@
+#+title: pit - a little lisp
+
+~pit~ is a small Lisp. I made it for fun, to understand Lisp better, and maybe also to use as a scripting language for games and other things.
+There are no dependencies - just run ~make~!
+
+It's a [[https://en.wikipedia.org/wiki/Common_Lisp#The_function_namespace][Lisp-2]] like Emacs Lisp and Common Lisp - symbols have separate bindings for functions and for values.
+Variables have lexical scope.
+
+#+begin_src lisp
+(defun say-hi ()
+ (princ "hello computer"))
+(say-hi)
+(setq counter 42)
+(let ((counter 0))
+ (fset 'count (lambda () (setq counter (+ counter 1))))
+ (fset 'query (lambda () counter)))
+(print (count)) (print (query))
+(print (count)) (print (query))
+#+end_src
+* embedding
+It is easy to use ~pit~ from C.
+Take a look at [[./src/library.c]] for examples of defining new functions and macros from C.
+Not many standard Lisp functions and macros are currently defined, mostly for no particular good reason.
+When using this, I'd probably define just what I need and not much else.
+* memory
+The interpreter uses a unconventional strategy for memory management. When the interpreter is initialized, it is provided with several memory regions of fixed size. During evaluation, some of these regions are used as temporary stacks, and others are used as arenas to allocate values (most values are NaN-boxed, so only "heavy" values like cons cells and bytestrings need to be allocated). There is no garbage collection - these arenas only grow during normal execution. By calling ~pit_runtime_freeze~, an interpreter can be "frozen", recording the next-free-position pointer for each arena. Subsequently, any attempt to modify values or symbol bindings that occur before these recorded pointers causes an error. By later calling ~pit_runtime_reset~, the arena next-free-positions are reset to the recorded ones, effectively undoing all memory usage that has happened since the interpreter was frozen.
+This model matches the intended usage of the interpreter as a scripting language for games. The intended usage is that the game engine will initialize the interpreter with all routines necessary for scripting, and then freeze the runtime. Scripts can be evaluated, and then the interpreter can be reset back to its starting state. This allows many scripts to be run on the interpreter without running out of memory, prevents undesirable changes to global state, and does not require any unpredictable garbage collection pass. Memory limits can also be easily enforced - simply specify smaller arena/stack sizes when initializing the interpreter.
+Who knows how well this works in practice! It seemed interesting though, and it was simple to implement.
diff --git a/src/runtime.c b/src/runtime.c
index 633ad32..2d30420 100644
--- a/src/runtime.c
+++ b/src/runtime.c
@@ -750,9 +750,10 @@ pit_value pit_apply(pit_runtime *rt, pit_value f, pit_value args) {
pit_value_heavy *h = pit_deref(rt, pit_as_ref(rt, f));
if (!h) { pit_error(rt, "bad ref"); return PIT_NIL; }
if (h->hsort == PIT_VALUE_HEAVY_SORT_FUNC) {
+ // calling a Lisp function is simple!
pit_value bound = PIT_NIL;
pit_value env = h->func.env;
- while (env != PIT_NIL) {
+ while (env != PIT_NIL) { // first, bind all entries in the closure
pit_value b = pit_car(rt, env);
pit_value nm = pit_car(rt, b);
pit_bind(rt, nm, pit_cdr(rt, b));
@@ -760,7 +761,7 @@ pit_value pit_apply(pit_runtime *rt, pit_value f, pit_value args) {
env = pit_cdr(rt, env);
}
pit_value anames = h->func.args;
- while (anames != PIT_NIL) {
+ while (anames != PIT_NIL) { // bind all argument names to their values
pit_value aform = pit_car(rt, anames);
pit_value nm = pit_car(rt, aform);
pit_value cell = pit_cdr(rt, aform);
@@ -770,13 +771,14 @@ pit_value pit_apply(pit_runtime *rt, pit_value f, pit_value args) {
args = pit_cdr(rt, args);
anames = pit_cdr(rt, anames);
}
- pit_value ret = pit_eval(rt, h->func.body);
- while (bound != PIT_NIL) {
+ pit_value ret = pit_eval(rt, h->func.body); // evaluate the body
+ while (bound != PIT_NIL) { // unbind everything we bound earlier, in reverse
pit_unbind(rt, pit_car(rt, bound));
bound = pit_cdr(rt, bound);
}
return ret;
} else if (h->hsort == PIT_VALUE_HEAVY_SORT_NATIVEFUNC) {
+ // calling native functions is even simpler
return h->nativefunc(rt, args);
} else {
pit_error(rt, "attempt to apply non-nativefunc ref");