summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LANGUAGE.rst25
-rw-r--r--actinide/__init__.py2
-rw-r--r--actinide/builtin.py4
-rw-r--r--actinide/evaluator.py13
-rw-r--r--actinide/types.py43
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