Awesome
Eva
Eva is a Scheme interpreter written in C.
Build
Just run make
.
Usage
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.
Language
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.
Data types
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
'foo
evaluates 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
long
is on your machine. - Boolean. There are two boolean constants:
#t
and#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
f
can 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!
.
Evaluation
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
, andc
unevaluated 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"))
.
Macros
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)))
;; => 12
Another 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.
Input/output
Eva has seven IO procedures worth mentioning:
(load str)
: Loads an Eva file. Ifstr
is "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 thatread
would 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.
R5RS conformity
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
Implementation
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.
License
© 2016 Mitchell Kember
Eva is available under the MIT License; see LICENSE for details.