A little scheme implementation on top of Clojure
Clojure

README.md

scheje - A little Scheme on Top of Clojure

License MIT Build Status Clojars Project Gratipay Team Slack Team

Scheje Logo

"If you give someone Fortran, he has Fortran. If you give someone Lisp, he has any language he pleases."

  • Guy L. Steele

Using the magnificent eval/apply dance (as in page 13 of the Lisp 1.5 Manual), scheje is a tiny scheme implementation on Top of Clojure.

Scheje properly implements define-syntax, ellipsis is properly expanded into relevant symbols, which can be respectively used in the generated syntax-rules templates. In fact, even let is implemented in terms of define-syntax.

The two defining forms, define and define-syntax generate new environments as per the bindings they introduce. Helper functions that interpret these along with other evaluations will be discussed later.

Usage

You can play with scheje on-line with the Web REPL.

The main interpretation function is form-eval in the interpreter namespace.

You give it a form and an environment, and you get your evaluation:

(form-eval '(+ x y) '{x 1 y 2})
;;=> 3

The function eval-prog, evaluates a whole program. This function is bootstrapped with a starting environment, called the library (see library.cljc for the contents of the library). When launched, eval-prog evaluates all the forms, only showing the value of the last one.

Here are some examples:

(eval-prog  '((define-syntax let
                (syntax-rules ()
                              ((let ((var expr) ...) body ...)
                               ((lambda (var ...) body ...) expr ...))))
              (let ((x 1)(y 2)) (+ x y))))
;;=>3

This example shows what we did to introduce the let form, though it is already implemented in the root environment used by scheje, so you don't have to define it every time you use eval-prog

Analogous to let, here's how one can declare and using a recursive define-syntax:

(eval-prog '((define-syntax and
               (syntax-rules ()
                             ((and x) x)
                             ((and) true)
                             ((and x y ...) (if x (and y ...) false))))
             (and true true true  true true false)))
;;=> false

Same here, this is already defined in root-env so you don't have to define it every time you use eval-prog. or is defined in the same way:

(eval-prog '((define-syntax or
               (syntax-rules ()
                             ((or x) x)
                             ((or) true)
                             ((or x y ...) (if x true (or y ...)))))
             (or false false false true false)))
;;=> true

Last but not least, here is the must-have append function, necessary for every decent scheme implementation!

(eval-prog '((define append
               (lambda (l1 l2)
                  (cond
                    ((null? l1) l2)
                    (else (cons (car l1) (append (cdr l1) l2))))))
             (append '(1 2 3) '(4 5 6))))
;;=> '(1 2 3 4 5 6)

Also, quote, quasiquote, unquote and unquote-splicing are supported:

(form-eval '(quasiquote
             (1
              (+ 1 (unquote-splicing (cdr '(1 2 3))))
              (unquote  (+ 1 a))))
           { 'a 5})
;;=> (1 (+ 1 2 3) 6)

Also, named let, let*, and many more are available. Please refer to the test files for the interpreter namespace to see possible uses.

Lexical Scoping in let Macros

To prevent symbol capture when using the let macro, when expanded, each let introduced expression's symbols are stored in its own scope. This avoids name clash:

In the following example, a is defined at the root-env, but "inner" a is returned from the let macro:

(eval-prog '((define a "outer")
             (let ((x (and false false))
                   (a "inner"))
               (if x "_" a))))
;;=> "inner"

If we access a symbol outside the macro, we get its root binding:

(eval-prog '((define a "outer")
             (let ((x (and false false))
                   (a "inner"))
               (if x "_" a))
             a))
;;=> "outer"

Hygienic Macros

Inspired by KFFD Algorithm. Now Scheje appends a timestamp with respect to the iteration in which every form is being expanded, thus preventing it from inadvertently capturing symbols across different expansion stages. If some symbols have timestamps that cannot be evaluated, Scheje tries to evaluate their "root" form, i.e, checks if their name, stripped out of the timestamps, isn't bound in the execution environment. This 'sort of' forces capture of such symbols, as to see if they were intended to be passed as globals, for instance.

For instance, these two examples work properly:

(eval-prog '(
             (define-syntax or2
               (syntax-rules ()
                             ((or2 e1 e2) (let ((t e1)) (if t t e2) ))))
             (let ((t true)) (or2 false t ))))
;;=> true

and

(eval-prog '(
             (let ((if (lambda(x y z) "oops")))
              (let ((g false))
                    (if g g false)))))
;;=> false

ClojureScript Support

Scheje is written using reader conditionals. The interpreter namespace can be used in a clojurescript project right away, in the browser, or in node.js if you want to use the file loading facilities.

A little REPL

Now a REPL namespace is shipped, exposing a little REPL permitting to evaluating expressions and loading files.

If you want to run it in Clojure/JVM, just clone the repo and launch

lein run

You can also build a ClojureScript/Node.js REPL and enjoy the ultra fast startup compared to JVM's version. Simply proceed like so:

#install npm dependencies
npm install readline-sync
# then build the node app
lein cljsbuild once
# and enjoy scheje.js :)
node target/repl_out/scheje_repl.js

Features

Please refer to the Changelog and the interpreter_test.clj test file to get a comprehensive list of the features and usage of scheje.

Contributors

Thanks to these wonderful people who helped the development of Scheje:

And to these awesome proof-readers:

Special thanks for the awesome logo go to:

TODOS - Wanna Contribute?

You can see how you can help by seeing the open issues

Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms.

Credits

Scheje's Node.js REPL uses readLineSync from @anseki, which is licensed under the MIT License.

License

Copyright © 2016 Rafik Naccache and Contributors. Distributed under the terms of the MIT License.