Eva is a Scheme interpreter written in C.
Just run make.
There are three ways to use the program bin/eva:
eva: Start a new REPL session.eva -e expression: Evaluate expressions and print their results.eva file1 file2 ...: Execute one or more Scheme files.
In addition, you can pass the -n or --no-prelude flag to disable automatic loading of the prelude.
All Schemes are different. The Eva dialect is fairly minimal. It supports some cool things, like first-class macros, but it lacks other things I didn't feel like implementing, such as floating-point numbers and tail-call optimization.
Eva has 9 types:
- Null. There is only one null value, written
(). Unlike in most Schemes,()does not need to be quoted. - Symbol. Symbols are implemented as interned strings. The quoted expression
'fooevaluates to the symbolfoo. (Another round of evaluation would look up a variable called "foo.") - Number. Numbers in Eva are signed integers. Their width is whatever Clang decides
longis on your machine. - Boolean. There are two boolean constants:
#tand#f. Everything in Eva is truthy (considered true in a boolean context) except for#f. - Character. These are just single-byte ASCII characters. They are written like
#\A, and then there are the special characters#\space,#\newline,#\return, and#\tab. - String. A string of characters. Unlike symbols, these are not interned, and they are mutable. They are written with double quotes, like
"Hello, World!". - Pair. You can't have Lisp without pairs. These are your standard cons cells. For example,
(cons 1 2)evaluates to the pair(1 . 2), and(cons 1 (cons 2 ()))evaluates to(1 2). - Procedure. Procedures are created by lambda abstractions. A procedure
fcan be called like(f a b c). - Macro. Macros are just procedures that follow different evaluation rules. They allow the syntax of Eva to be extended.
There is also a void type for the result of operations with side effects such as define and set!.
When Eva sees (f a b c), it evaluates as follows:
- Evaluate the operator,
f.- If it evaluated to a procedure:
- Evaluate the operands
a,b, andc. - Substitute them into the function body.
- Evaluate the resulting body.
- Evaluate the operands
- If it evaluated to a macro:
- Substitute the operands
a,b, andcunevaluated into the macro body. - Evaluate the resulting body to get the code.
- Evaluate the resulting code at the call site.
- Substitute the operands
- If it evaluated to a procedure:
These rules make it possible for macros to be first-class values in Eva. Although this may seem like a strange feature, it actually results in a simpler implementation. There are no special cases: if you really want to, you can (define d define) to save 5 characters. You could then write (d (define x y) (error "Use d")).
In Eva, any function f can be turned into a macro simply by writing (macro f). For this new object, macro? will return #t and procedure? will return false.
For example, consider the following function which converts infix arithmetic to prefix form:
(define (infix->prefix code)
(define operators
'(+ - * / = < > <= >=))
(if (not (pair? code))
code
(let ((c (map infix->prefix code)))
(if (and (= (length c) 3)
(memq (cadr c) operators))
(list (cadr c) (car c) (car (cddr c)))
c))))
(infix->prefix '(1 + ((4 * (5 - 1)) / 3)))
;; => (+ 1 (/ (* 4 (- 5 1)) 3))Now, let's turn it into a macro:
(define with-infix (macro infix->prefix))
(macro? with-infix)
;; => #t
(with-infix
(let ((x (1 + ((4 * (5 - 1)) / 3))))
(x + x)))
;; => 12Another benefit of having first-class macros is that reducing a list with a macro like and or or works. First-class macros are very cool and powerful, but I'm sure they'd be a nightmare if anyone actually used them in a large project.
Eva has seven IO procedures worth mentioning:
(load str): Loads an Eva file. Ifstris "prelude," then it loads the prelude. Otherwise, it tries to open a file.(error expr1 ...): Creates an error. This can be used anywhere. The arguments will be printed when the error is reported.(read): Reads an expression using the same parser as for code.(write expr): Writes an expression in a format thatreadwould accept.(display expr): Displays an expression to standard output without a trailing newline. Strings are displayed without double quotes or escaped characters.(newline): Prints a newline to standard output.(print expr): Likedisplay, except it adds a trailing newline and it recursively enters lists to print each item individually.
Eva implements the following standard macros (also called special forms) from R5RS:
define set!
lambda begin
if cond and or
let let*
quote quasiquote unquote unquote-splicing
The quotation special forms can be used via the usual ', `, and , syntax. Due to the way environments are implemented, there is no need for letrec. You can write mutually recursive definitions using let bindings.
Eva implements the following standard procedures from R5RS:
eq? eqv? equal?
number? integer?
+ - * / quotient remainder modulo
= < > <= >=
zero? positive? negative? even? odd?
min max abs gcd lcm expt
number->string string->number
boolean? not
pair? cons car cdr set-car! set-cdr!
caar cadr cdar cddr
null? list? list length append reverse
list-tail list-ref
memq memv member assq assv assoc
symbol? symbol->string string->symbol
char? char=? char<? char>? char<=? char>=?
char-alphabetic? char-numeric? char-whitespace?
char-lower-case? char-upper-case?
char->integer integer->char
char-upcase char-downcase
string? string make-string string-length string-ref string-set!
string=? string<? string>? string<=? string>=?
substring string-append string->list list->string
string-copy string-fill!
procedure? eval apply map for-each force delay
read write load
Eva is implemented in 15 parts:
main.c: Implements the main function. Handles command-line arguments.util.c: Utilities for reading files, allocating memory, etc.repl.c: Implements the REPL and a routine for executing files.parse.c: Parser for the language.expr.c: Defines the Expression struct and related functions.type.c: Typechecking for applications of standard procedures and macros.eval.c: Implements the core of the interpreter (eval and apply).proc.c: Implementation functions for standard procedures.macro.c: Implementation functions for standard macros.env.c: Data structure for environment frames.intern.c: Table for interning strings.list.c: Helper functions for dealing with linked lists.set.c: Set data structure for detecting duplicates.error.c: Creating and printing error messages.prelude.c: Auto-generated fromprelude.scm, the prelude.
© 2016 Mitchell Kember
Eva is available under the MIT License; see LICENSE for details.