diff options
| -rw-r--r-- | LANGUAGE.rst | 25 | ||||
| -rw-r--r-- | actinide/__init__.py | 2 | ||||
| -rw-r--r-- | actinide/builtin.py | 4 | ||||
| -rw-r--r-- | actinide/evaluator.py | 13 | ||||
| -rw-r--r-- | actinide/types.py | 43 |
5 files changed, 76 insertions, 11 deletions
diff --git a/LANGUAGE.rst b/LANGUAGE.rst index 79f8265..36f5793 100644 --- a/LANGUAGE.rst +++ b/LANGUAGE.rst @@ -57,6 +57,19 @@ Forms * Examples: ``; this function is extremely spicy.`` +* Compound forms: conses and dotted pairs + + * An opening ``(``, followed by a head subform, followed by spaces, + followed by a ``.``, followed by a tail subform, followed by a closing + ``)``. + + * Represents a *cons* with the given head and tail. + + * A dotted pair which appears as the head of a dotted pair does not require + an additional dot. + + * Examples: ``(1 . 2)``, ``(1 2 . 3)``, ``(('ll . 'lr) . ('rl . 'rr))`` + * Compound forms: lists * An opening ``(``, followed by a sequence of "subforms" separated by @@ -194,7 +207,10 @@ Lists that begin with one of the following symbols are evaluated specially. procedure value which can be used to apply the newly-defined procedure. * Must include a ``formals`` subform, which is generally a list of argument - names (as symbols). + names (as symbols). If the formals subform is a bare symbol, or a dotted + pair whose tail is a symbol, the function has variable arity, and all + arguments not assigned to a name from the formals list are collected into + a list and bound to that symbol. * May include a sequence of body subforms, which are evaluated in order (as if by ``begin``) whenever the function is applied. @@ -234,6 +250,13 @@ Lists that begin with one of the following symbols are evaluated specially. for the ``+`` function itself, but it illustrates the idea that functions can include other functions. + :: + + (lambda (a . b) b) + + This defines a function which takes one or more arguments, whose + evaluation is the list of arguments other than the first. + * ``define``: A ``define`` form sets the value of a new binding in the current environment. This has two forms: diff --git a/actinide/__init__.py b/actinide/__init__.py index cfcb921..33a199f 100644 --- a/actinide/__init__.py +++ b/actinide/__init__.py @@ -62,6 +62,8 @@ class BaseSession(object): self.bind(name, binding) for name, binding in registry.macros: self.macro_bind(name, binding) + for source in registry.evals: + self.run(source) def get(self, symb): symb = self.symbol(symb) diff --git a/actinide/builtin.py b/actinide/builtin.py index e8132af..9bbf2f9 100644 --- a/actinide/builtin.py +++ b/actinide/builtin.py @@ -79,6 +79,7 @@ class Registry(object): def __init__(self): self.bindings = [] self.macros = [] + self.evals = [] def bind(self, name, value): self.bindings.append((name, value)) @@ -111,3 +112,6 @@ class Registry(object): def macro_builtin(self, f): self.macro_bind(lisp_name(f), f) return f + + def eval(self, source): + self.evals.append(source) diff --git a/actinide/evaluator.py b/actinide/evaluator.py index 45046a5..614acbf 100644 --- a/actinide/evaluator.py +++ b/actinide/evaluator.py @@ -31,6 +31,12 @@ from . import types as t # exception bubbles up out of the interpreter and destroys the state of the # computation. +# Raised if a form cannot be evaluated. This is generally raised as soon as an +# unevaluatable form is detected by the compiler, which may be substantially +# before the program would try to evaluate that form. +class EvalError(Exception): + pass + # Reduce a continuation to its final value. # # This iteratively calls the current continuation with the current arguments @@ -77,8 +83,7 @@ def quote(quoted, continuation): # form. (The head of the lambda form must be discarded before calling this # factory.) def lambda_(defn, symbols, continuation): - formals = t.flatten(t.head(defn)) - body = t.head(t.tail(defn)) + formals, body = t.flatten(defn) def lambda__(env, macros): proc = t.Procedure(body, formals, env, macros, symbols) return (continuation, env, macros, proc) @@ -182,8 +187,10 @@ def tail_graft(continuation, environment, macros, guarded): def eval(value, symbols, continuation): if t.symbol_p(value): return symbol(value, continuation) - if t.nil_p(value) or not t.list_p(value): + if t.nil_p(value) or not t.cons_p(value): return literal(value, continuation) + if not t.list_p(value): + raise EvalError("Cannot evaluate a dotted pair") # Special forms (all of which begin with a special symbol, discarded here) if t.head(value) == symbols['if']: return if_(t.tail(value), symbols, continuation) diff --git a/actinide/types.py b/actinide/types.py index 2929371..2961771 100644 --- a/actinide/types.py +++ b/actinide/types.py @@ -1,5 +1,6 @@ # ## Actinide Types +import builtins as b from collections import namedtuple from decimal import Decimal, InvalidOperation @@ -228,21 +229,49 @@ def flatten(list): # ### Procedures +class ProcedureError(Exception): + pass + class Procedure(object): def __init__(self, body, formals, environment, macros, symbols): - self.body = body - self.continuation = e.eval(body, symbols, None) - self.formals = formals self.environment = environment self.macros = macros + self.symbols = symbols + self.body = body + self.continuation = self.compile() + self.formals, self.tail_formal = self.parse_formals(formals) + + def compile(self, continuation=None): + return e.eval(self.body, self.symbols, continuation) + + def invocation_environment(self, *args): + if b.len(args) < b.len(self.formals) or \ + b.len(args) > b.len(self.formals) and not self.tail_formal: + args_syntax = append(list(*self.formals), self.tail_formal) + call_syntax = list(*args) + raise ProcedureError(f'Procedure with arguments {display(args_syntax, self.symbols)} called with arguments {display(call_syntax, self.symbols)}') + + named_args = b.list(zip(self.formals, args)) + tail_arg = [] + if self.tail_formal: + tail_list = list(*args[b.len(self.formals):]) + tail_binding = (self.tail_formal, tail_list) + tail_arg.append(tail_binding) + + return Environment(named_args + tail_arg, self.environment) def __call__(self, *args): call_env = self.invocation_environment(*args) call_macros = Environment(parent=self.macros) return e.run(self.continuation, call_env, call_macros, ()) - def invocation_environment(self, *args): - return Environment(zip(self.formals, args), self.environment) + @classmethod + def parse_formals(cls, formals): + names = [] + while not nil_p(formals) and cons_p(formals): + formal, formals = uncons(formals) + names.append(formal) + return names, formals @An.fn def procedure_p(value): @@ -250,9 +279,9 @@ def procedure_p(value): def display_procedure(proc, symbols): if isinstance(proc, Procedure): - formals = ' '.join(display(formal, symbols) for formal in proc.formals) + formals = display(append(list(*proc.formals), proc.tail_formal), symbols) body = display(proc.body, symbols) - return f'<procedure: (lambda ({formals}) {body})>' + return f'<procedure: (lambda {formals} {body})>' return f'<builtin: {proc.__name__}>' # ### General-purpose functions |
