diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2017-11-18 17:58:33 -0500 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2017-11-18 17:59:14 -0500 |
| commit | 8d289bffdd2bf2062a6677530c4a4226175d87b2 (patch) | |
| tree | 6bf97eae7dbc91b80f04f84c63d269892885205a | |
| parent | cd0a85b468b4e125000d2636df0d44e5f8e2b622 (diff) | |
Replace the registry mega-tuple with a type.
Add some missing builtins:
* and
* or
* uncons
| -rw-r--r-- | README.rst | 33 | ||||
| -rw-r--r-- | actinide/__init__.py | 7 | ||||
| -rw-r--r-- | actinide/builtin.py | 47 | ||||
| -rw-r--r-- | actinide/core.py | 7 | ||||
| -rw-r--r-- | actinide/expander.py | 3 | ||||
| -rw-r--r-- | actinide/ports.py | 12 | ||||
| -rw-r--r-- | actinide/stdlib.py | 36 | ||||
| -rw-r--r-- | actinide/types.py | 62 |
8 files changed, 105 insertions, 102 deletions
@@ -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) |
