Based on a stupid-lisp PoC I’ve written a while ago. Inspired by LISP-1.5 (see LISP 1.5 Programmer’s Manual by McCarthy) and PicoLisp.
Was mainly developed to practice Golang
go build
./glisp yourcode.lispThe interpreter simply needs a Bindigings list which can be extended with
native functions via BuildNativeFn.
Every function is simply a list starting with lambda symbol (or a native
function which is bound directly from Golang).
This means that non-native functions can be inspected as lists and even modified (although not sure why you’d want that).
Also the way a lambda is invoked is its parameters are passed to it unevaluated. Consider this:
(let ((x 122)
(inc (lambda ARG
(+ ARG 1))))
(inc x))This will throw an error as ARG will be simply (x) which isn’t even an
integer.
In order to make this work, we need to evaluate the arguments explicitly:
(let ((x 122)
(inc (lambda ARG
(let ((first-arg (eval (car ARG))))
(+ first-arg 1)))))
(inc x))or implicitly (which is how you’d expect a lambda to work):
(let ((x 122)
(inc (lambda (x)
(+ x 1))))
(inc x))This feature has some interesting consequences:
- No macros needed. Macros are basically lisp functions that are called at compile time and they work with their arguments unevaluated. This is a completely different plane of execution. “Code is data” is a bit of a buzzword at this point because in modern lisps it really isn’t (only at compile time).
- Quote isn’t special.
(let ((quote (lambda ARG (car ARG)))) (quote a ignored)) - Dynamic binding. Since code is actually data, it means binding has to happen at the moment of evaluation which may be in entirely different context from where the function was created. While dynamic binding isn’t popular (only emacs and bash/etc come to mind from popular software), it does have some fun properties. Read some PicoLisp docs to learn more.
- Symbolic programming ?
In lisps usually you expect to see nil and t. This lisp doesn’t treat them
any special way and instead simply treats () as empty list. nil and t can
be bound manually to () and (quote t) respectively.
(let ((quote (lambda ARG (car ARG)))
(not (lambda (x) (if x () (quote true))))
(mapcar (lambda (f lst)
(if (not lst)
lst
(cons (f (car lst)) (mapcar f (cdr lst))))))
(list (lambda ARG
(mapcar eval ARG))))
(list 1 2 (list 4 5) list))
;; ==> (1 2 (4 5) (lambda ARG (mapcar eval ARG)));; depends on: not, list
(define reduce
(lambda (initial-value f lst)
(if (not lst)
initial-value
(reduce (f initial-value (car lst)) f (cdr lst)))))
(define push-last
(lambda (x lst)
(cons (car lst)
(if (cdr lst)
(push-last x (cdr lst))
(list x)))))
(define ->>
(lambda ->>ARGS
(eval (reduce (car ->>ARGS)
push-last
(cdr ->>ARGS)))))
(->> 100
(+ 20)
(+ 3)
(list)
(push-last 456))
;; ==> (123 456)(load "lang/core.lisp")
(->> 100
(+ 20)
(+ 3)
(list)
(push-last 456)
(print))
;; ==> (123 456)Since this lisp can be embedded and extended, one could use this as a DSL. It’s also got potential for interactive programming. I made an example of a Go web server with handlers written in glisp.
See examples/embedded/webapi/webapi.go.
Run with:
go run nondv.io/glisp/examples/embedded/webapi- Errors from lisp
- Better string escaping. Currently, only
\"is supported and there’s no way to provide\"as actual part of a string formatfunction to output text (printprovidesread-able output, which is different).- floats
- arithmetic (currently, only
+is implemented) - Reader macros. It’d be nice to have stuff like
'awork - Emacs integration
- Requires REPL to accept multi-line input