summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2017-11-18 17:58:33 -0500
committerOwen Jacobson <owen@grimoire.ca>2017-11-18 17:59:14 -0500
commit8d289bffdd2bf2062a6677530c4a4226175d87b2 (patch)
tree6bf97eae7dbc91b80f04f84c63d269892885205a
parentcd0a85b468b4e125000d2636df0d44e5f8e2b622 (diff)
Replace the registry mega-tuple with a type.
Add some missing builtins: * and * or * uncons
-rw-r--r--README.rst33
-rw-r--r--actinide/__init__.py7
-rw-r--r--actinide/builtin.py47
-rw-r--r--actinide/core.py7
-rw-r--r--actinide/expander.py3
-rw-r--r--actinide/ports.py12
-rw-r--r--actinide/stdlib.py36
-rw-r--r--actinide/types.py62
8 files changed, 105 insertions, 102 deletions
diff --git a/README.rst b/README.rst
index d4bc4c9..2b1bfcc 100644
--- a/README.rst
+++ b/README.rst
@@ -176,39 +176,28 @@ helper ultimately calls that module's ``wrap_void`` to wrap the function, and
wrapped.) If you prefer to manually bind functions using ``bind``, they must be
wrapped appropriately.
-Finally Actinide can bind specially-crafted Python modules. If a module
-contains top-level symbols named ``ACTINIDE_BINDINGS``, ``ACTINIDE_VOIDS``,
-``ACTINIDE_FNS``, and/or ``ACTINIDE_BUILTINS``, it can be passed to the
-session's ``bind_module`` method. The semantics of each symbol are as follows:
-
-* ``ACTINIDE_BINDINGS`` is a list of name, value pairs. Each binding binding
- will be installed verbatim, without any function mangling, as if by
- ``session.bind``.
-
-* ``ACTINIDE_VOIDS``, ``ACTINIDE_FNS``, and ``ACTINIDE_BUILTINS`` are lists of
- function objects. Each will be bound as if by the corresponding
- ``session.bind_void``, ``session.bind_fn``, or ``session.bind_builtin``
- method.
-
-The ``actinide.builtin`` module contains a helper function, ``make_registry``,
-which can simplify construction of these fields:
+Finally, Actinide can bind specially-crafted Python modules. If a module
+contains a top-level symbol named ``An`` (for the informal chemical symbol for
+the actinide series), it can be passed to the session's ``bind_module`` method.
+The symbol must be bound to an instance of the ``Registry`` class from the
+``actinide.builtin`` module:
.. code:: python
- from actinide.builtin import make_registry
- ACTINIDE_BINDINGS, ACTINIDE_VOIDS, ACTINIDE_FNS, ACTINIDE_BUILTINS, bind, void, fn, builtin = make_registry()
+ from actinide.builtin import Registry
+ An = Registry()
- five = bind('five', 5)
+ five = An.bind('five', 5)
- @void
+ @An.void
def python_print(*args):
print(*args)
- @fn
+ @An.fn
def bitwise_and(a, b):
return a & b
- @builtin
+ @An.builtin
def two_values():
return 1, "Two"
diff --git a/actinide/__init__.py b/actinide/__init__.py
index dbd2b9b..90ccbfb 100644
--- a/actinide/__init__.py
+++ b/actinide/__init__.py
@@ -44,12 +44,9 @@ class BaseSession(object):
return symb
def bind_module(self, module):
- for name, binding in getattr(module, 'ACTINIDE_BINDINGS', []):
+ registry = module.An
+ for name, binding in registry.bindings:
self.bind(name, binding)
- for fn in getattr(module, 'ACTINIDE_FNS', []):
- self.bind_fn(fn)
- for builtin in getattr(module, 'ACTINIDE_BUILTINS', []):
- self.bind_builtin(builtin)
def get(self, symb):
symb = self.symbol(symb)
diff --git a/actinide/builtin.py b/actinide/builtin.py
index 68ed698..485fa96 100644
--- a/actinide/builtin.py
+++ b/actinide/builtin.py
@@ -15,14 +15,19 @@ dunder_names = {
'__ge__': '>',
}
+class BindError(Exception):
+ pass
+
# Derives the lisp name of a python function or method handle, as follows:
#
-# * Python dunder names are translated to their corresponding operator or
-# symbol usind the ``dunder_names`` table.
+# * Python dunder names are translated to their corresponding operator or symbol
+# usind the ``dunder_names`` table.
#
# * A trailing '_p' in the Python name is converted into a question mark in the
# lisp name.
#
+# * A lone trailing '_' in the Python name stripped.
+#
# * Any remaining underscores in the Python name are converted to dashes in the
# lisp name.
#
@@ -31,8 +36,9 @@ dunder_names = {
def lisp_name(fn):
name = fn.__name__
if name == '<lambda>':
- return None
+ raise BindError(f'Lambda {repr(fn)} has no name')
+ # Translate operators early, and throw out the existing name
if name in dunder_names:
return dunder_names[name]
@@ -40,6 +46,10 @@ def lisp_name(fn):
if name.endswith('_p'):
name = name[:-2] + '?'
+ # Trailing _ is probably a name that conflicts with a python keyword
+ if name.endswith('_') and not name.endswith('__'):
+ name = name[:-1]
+
# Remaining underscores are dashes
name = name.replace('_', '-')
@@ -62,26 +72,25 @@ def wrap_fn(fn):
return fn(*args),
return wrapper
-def make_registry():
- bindings = []
- voids = []
- fns = []
- builtins = []
+# An Actinide registry allows its containing module to be loaded into a Session
+# to provide additional functions and syntax to code run in that Session.
- def bind(name, val):
- bindings.append((name, val))
- return val
+class Registry(object):
+ def __init__(self):
+ self.bindings = []
- def void(f):
- voids.append(f)
- return f
+ def bind(self, name, value):
+ self.bindings.append((name, value))
+ return value
- def fn(f):
- fns.append(f)
+ def void(self, f):
+ self.bind(lisp_name(f), wrap_void(f))
return f
- def builtin(f):
- builtins.append(f)
+ def fn(self, f):
+ self.bind(lisp_name(f), wrap_fn(f))
return f
- return bindings, voids, fns, builtins, bind, void, fn, builtin
+ def builtin(self, f):
+ self.bind(lisp_name(f), f)
+ return f
diff --git a/actinide/core.py b/actinide/core.py
index 467f04f..305e93c 100644
--- a/actinide/core.py
+++ b/actinide/core.py
@@ -1,8 +1,7 @@
# Core functions
-from .builtin import make_registry
+from .builtin import Registry
+An = Registry()
-ACTINIDE_BINDINGS, ACTINIDE_VOIDS, ACTINIDE_FNS, ACTINIDE_BUILTINS, bind, void, fn, builtin = make_registry()
-
-@builtin
+@An.builtin
def values(*args):
return args
diff --git a/actinide/expander.py b/actinide/expander.py
index e925c19..9518854 100644
--- a/actinide/expander.py
+++ b/actinide/expander.py
@@ -125,6 +125,3 @@ def expand_macro(form, symbols, macros):
args = flatten(args)
expansion, = macro_body(*args)
return expand(expansion, symbols, macros)
-
-def uncons(value):
- return head(value), tail(value)
diff --git a/actinide/ports.py b/actinide/ports.py
index 2c116e8..76a9540 100644
--- a/actinide/ports.py
+++ b/actinide/ports.py
@@ -1,8 +1,8 @@
import io
-from .builtin import make_registry
+from .builtin import Registry
-ACTINIDE_BINDINGS, ACTINIDE_VOIDS, ACTINIDE_FNS, ACTINIDE_BUILTINS, bind, void, fn, builtin = make_registry()
+An = Registry()
# ## PORTS
#
@@ -40,23 +40,23 @@ class Port(object):
# Read at least 1 and up to ``n`` characters from a port. This consumes them
# from the port: they are no longer available to future peeks or reads. ``n``
# must be strictly positive.
-@fn
+@An.fn
def read_port(port, n):
return port.read(n)
# Read all remaining input from a port, consuming it.
-@fn
+@An.fn
def read_port_fully(port):
return port.read_fully()
# Read at least 1 and up to ``n`` characters from a port, without consuming
# them. They will be available on future peeks and reads. ``n`` must be strictly
# positive.
-@fn
+@An.fn
def peek_port(port, n):
return port.peek(n)
# Create an input port from a string.
-@fn
+@An.fn
def string_to_input_port(string):
return Port(io.StringIO(string))
diff --git a/actinide/stdlib.py b/actinide/stdlib.py
index 6401863..8abf71c 100644
--- a/actinide/stdlib.py
+++ b/actinide/stdlib.py
@@ -2,59 +2,67 @@ import operator as op
import functools as f
from .types import *
-from .builtin import make_registry
+from .builtin import Registry
-ACTINIDE_BINDINGS, ACTINIDE_VOIDS, ACTINIDE_FNS, ACTINIDE_BUILTINS, bind, void, fn, builtin = make_registry()
+An = Registry()
-@fn
+@An.fn
def __add__(*vals):
return f.reduce(op.add, vals)
-@fn
+@An.fn
def __sub__(val, *vals):
if vals:
return f.reduce(op.sub, (val, *vals))
return op.neg(val)
-@fn
+@An.fn
def __mul__(*vals):
return f.reduce(op.mul, vals)
-@fn
+@An.fn
def __floordiv__(*vals):
div = op.floordiv
if any(decimal_p(val) for val in vals):
div = op.truediv
return f.reduce(div, vals)
-@fn
+@An.fn
def __eq__(a, b):
return op.eq(a, b)
-@fn
+@An.fn
def __ne__(a, b):
return op.ne(a, b)
-@fn
+@An.fn
def __lt__(a, b):
return op.lt(a, b)
-@fn
+@An.fn
def __le__(a, b):
return op.le(a, b)
-@fn
+@An.fn
def __gt__(a, b):
return op.gt(a, b)
-@fn
+@An.fn
def __ge__(a, b):
return op.ge(a, b)
-@fn
+@An.fn
def eq_p(a, b):
return op.is_(a, b)
-@fn
+@An.fn
def equal_p(a, b):
return op.eq(a, b)
+
+@An.fn
+def and_(a, b):
+ return op.and_(a, b)
+
+@An.fn
+def or_(a, b):
+ return op.or_(a, b)
diff --git a/actinide/types.py b/actinide/types.py
index bae0547..2929371 100644
--- a/actinide/types.py
+++ b/actinide/types.py
@@ -9,21 +9,21 @@ from decimal import Decimal, InvalidOperation
from . import evaluator as e
from .environment import *
-from .builtin import make_registry
+from .builtin import Registry
-ACTINIDE_BINDINGS, ACTINIDE_VOIDS, ACTINIDE_FNS, ACTINIDE_BUILTINS, bind, void, fn, builtin = make_registry()
+An = Registry()
# ### Nil
#
# Nil is a type with a single value, usually taken to denote no value.
-nil = bind('nil', None)
+nil = An.bind('nil', None)
-@fn
+@An.fn
def nil_p(value):
return value is None
-@fn
+@An.fn
def read_nil(value):
return nil
@@ -34,14 +34,14 @@ def display_nil(value):
#
# The true and false values.
-true = bind('#t', True)
-false = bind('#f', False)
+true = An.bind('#t', True)
+false = An.bind('#f', False)
-@fn
+@An.fn
def boolean_p(value):
return value is true or value is false
-@fn
+@An.fn
def read_boolean(value):
if value == '#t':
return true
@@ -58,15 +58,15 @@ def display_boolean(value):
# These are fixed-precision numbers with no decimal part, obeying common notions
# of machine integer arithmetic. They support large values.
-@fn
+@An.fn
def integer(value):
return int(value)
-@fn
+@An.fn
def integer_p(value):
return isinstance(value, int)
-@fn
+@An.fn
def read_integer(value):
try:
return integer(value)
@@ -80,15 +80,15 @@ def display_integer(value):
#
# These are variable-precision numbers, which may have a decimal part.
-@fn
+@An.fn
def decimal(value):
return Decimal(value)
-@fn
+@An.fn
def decimal_p(value):
return isinstance(value, Decimal)
-@fn
+@An.fn
def read_decimal(value):
try:
return decimal(value)
@@ -102,15 +102,15 @@ def display_decimal(value):
#
# Sequences of characters.
-@fn
+@An.fn
def string(value):
return string(Value)
-@fn
+@An.fn
def string_p(value):
return not symbol_p(value) and isinstance(value, str)
-@fn
+@An.fn
def read_string(value):
value = value[1:-1]
value = value.replace('\\"', '"')
@@ -141,11 +141,11 @@ class Symbol(object):
def symbol(string, symbol_table):
return symbol_table[string]
-@fn
+@An.fn
def symbol_p(value):
return isinstance(value, Symbol)
-@fn
+@An.fn
def read_symbol(value, symbol_table):
return symbol(value, symbol_table)
@@ -158,22 +158,26 @@ def display_symbol(value):
Cons = namedtuple('Cons', 'head tail')
-@fn
+@An.fn
def cons(head, tail):
return Cons(head, tail)
-@fn
+@An.fn
def cons_p(value):
return isinstance(value, Cons)
-@fn
+@An.fn
def head(cons):
return cons.head
-@fn
+@An.fn
def tail(cons):
return cons.tail
+@An.builtin
+def uncons(cons):
+ return head(cons), tail(cons)
+
def display_cons(value, symbols):
parts = []
while cons_p(value):
@@ -186,7 +190,7 @@ def display_cons(value, symbols):
# ### Lists
-@fn
+@An.fn
def list(*elems):
if elems:
head, *tail = elems
@@ -194,11 +198,11 @@ def list(*elems):
else:
return nil
-@fn
+@An.fn
def list_p(value):
return nil_p(value) or cons_p(value) and list_p(tail(value))
-@fn
+@An.fn
def append(list, *lists):
if not lists:
return list
@@ -207,7 +211,7 @@ def append(list, *lists):
value, next = head(list), tail(list)
return cons(value, append(next, *lists))
-@fn
+@An.fn
def len(list):
l = 0
while not nil_p(list):
@@ -240,7 +244,7 @@ class Procedure(object):
def invocation_environment(self, *args):
return Environment(zip(self.formals, args), self.environment)
-@fn
+@An.fn
def procedure_p(value):
return callable(value)