co-lambda 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- co_lambda/__init__.py +1 -0
- co_lambda/_analysis.py +92 -0
- co_lambda/_ast.py +372 -0
- co_lambda/_binnat.py +170 -0
- co_lambda/_codec.py +147 -0
- co_lambda/_compiler_artifact.py +50 -0
- co_lambda/_defun_codegen.py +321 -0
- co_lambda/_defun_runtime.py +188 -0
- co_lambda/_defunctionalize.py +470 -0
- co_lambda/_dsl.py +148 -0
- co_lambda/_generated/.gitattributes +5 -0
- co_lambda/_generated/__init__.py +1 -0
- co_lambda/_generated/_generated_defun_compiler_py311.py +7586 -0
- co_lambda/_generated/_generated_defun_compiler_py312.py +7586 -0
- co_lambda/_generated/_generated_defun_compiler_py313.py +7586 -0
- co_lambda/_hoas_latex.py +144 -0
- co_lambda/_latex.py +58 -0
- co_lambda/_prelude.py +163 -0
- co_lambda/_pyast.py +418 -0
- co_lambda/_pybuild.py +313 -0
- co_lambda/_reduce.py +145 -0
- co_lambda/_shape.py +224 -0
- co_lambda/_sugar.py +74 -0
- co_lambda/_typecheck.py +370 -0
- co_lambda-0.5.0.dist-info/METADATA +84 -0
- co_lambda-0.5.0.dist-info/RECORD +28 -0
- co_lambda-0.5.0.dist-info/WHEEL +4 -0
- co_lambda-0.5.0.dist-info/entry_points.txt +2 -0
co_lambda/_dsl.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""A Higher-Order Abstract Syntax surface, compiled to the first-order de Bruijn AST.
|
|
2
|
+
|
|
3
|
+
Object-language binders are written with Python ``lambda``, so terms read isomorphically to
|
|
4
|
+
the lambda-calculus, and Python's lexical scope is the implicit symbol table (no name
|
|
5
|
+
environment, no capture handling). The calculus is pure (``Var``/``Lam``/``App``); cyclic and
|
|
6
|
+
recursive data are written with the ``Y`` combinator (no recursion binder is needed, since
|
|
7
|
+
interning folds the structurally-repeating positions a ``Y`` recursion produces). The Python
|
|
8
|
+
lambdas run once at build time; the result is a pure first-order tree.
|
|
9
|
+
|
|
10
|
+
A ``Builder`` is a HOAS term: an abstract base whose subclasses (``_VarBuilder``, ``_LamBuilder``,
|
|
11
|
+
``_AppBuilder``) each know how to produce their de Bruijn node at a given binder depth. Calling a
|
|
12
|
+
builder is object-language application (``f(x, y)`` is ``((f x) y)``); the depth-indexed node is
|
|
13
|
+
read with ``.at(depth)`` (or ``build`` at depth zero).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import inspect
|
|
19
|
+
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
from co_lambda._ast import Node, make_app, make_lam, make_var
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Builder(ABC):
|
|
27
|
+
"""A HOAS term: given the current binder depth, produce a de Bruijn node.
|
|
28
|
+
|
|
29
|
+
``.at(depth)`` memoises the node by binder depth. A builder is a pure function of the binder
|
|
30
|
+
depth, so reusing the same builder object in several places (a shared subterm) yields the same
|
|
31
|
+
node at a given depth. Caching by depth makes a shared-builder DAG build in time linear in its
|
|
32
|
+
distinct nodes instead of unfolding it into a tree: ``build`` reaches each child builder per
|
|
33
|
+
occurrence, so without the cache a builder reused in ``n`` places is re-run ``n`` times. The
|
|
34
|
+
result nodes are interned regardless; this shares the construction work too.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
__slots__ = ("_node_by_depth",)
|
|
38
|
+
|
|
39
|
+
def __init__(self) -> None:
|
|
40
|
+
self._node_by_depth: dict[int, Node] = {}
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def _build_at(self, depth: int) -> Node:
|
|
44
|
+
"""Produce this term's de Bruijn node at ``depth`` (uncached); implemented per subclass."""
|
|
45
|
+
|
|
46
|
+
def at(self, depth: int) -> Node:
|
|
47
|
+
"""This HOAS term's de Bruijn node at the given binder depth (memoised)."""
|
|
48
|
+
node = self._node_by_depth.get(depth)
|
|
49
|
+
if node is None:
|
|
50
|
+
node = self._build_at(depth)
|
|
51
|
+
self._node_by_depth[depth] = node
|
|
52
|
+
return node
|
|
53
|
+
|
|
54
|
+
def __call__(self, *arguments: "Builder") -> "Builder":
|
|
55
|
+
"""Object-language application, left-folded: ``f(x, y, z)`` is ``((f x) y) z``."""
|
|
56
|
+
result: Builder = self
|
|
57
|
+
for argument in arguments:
|
|
58
|
+
result = _AppBuilder(result, argument)
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _VarBuilder(Builder):
|
|
63
|
+
"""A bound variable, named by the binder level it refers to (turned into a de Bruijn index by
|
|
64
|
+
subtracting from the current depth)."""
|
|
65
|
+
|
|
66
|
+
__slots__ = ("_level",)
|
|
67
|
+
|
|
68
|
+
def __init__(self, level: int) -> None:
|
|
69
|
+
super().__init__()
|
|
70
|
+
self._level = level
|
|
71
|
+
|
|
72
|
+
def _build_at(self, depth: int) -> Node:
|
|
73
|
+
return make_var(depth - self._level - 1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class _LamBuilder(Builder):
|
|
77
|
+
"""An abstraction: a Python function from the bound-variable builder to the body builder.
|
|
78
|
+
|
|
79
|
+
``_binder_hint`` is the readable name of the bound variable when it is known up front (``lam_named``,
|
|
80
|
+
used by ``curry`` to keep a decorated function's parameter names); ``None`` means a renderer should
|
|
81
|
+
recover the name from the Python lambda's own parameter. It never affects the built node.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
__slots__ = ("_body", "_binder_hint")
|
|
85
|
+
|
|
86
|
+
def __init__(self, body: "Callable[[Builder], Builder]", binder_hint: "str | None") -> None:
|
|
87
|
+
super().__init__()
|
|
88
|
+
self._body = body
|
|
89
|
+
self._binder_hint = binder_hint
|
|
90
|
+
|
|
91
|
+
def _build_at(self, depth: int) -> Node:
|
|
92
|
+
return make_lam(self._body(_VarBuilder(depth)).at(depth + 1))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class _AppBuilder(Builder):
|
|
96
|
+
"""An application of one builder to another."""
|
|
97
|
+
|
|
98
|
+
__slots__ = ("_function", "_argument")
|
|
99
|
+
|
|
100
|
+
def __init__(self, function: Builder, argument: Builder) -> None:
|
|
101
|
+
super().__init__()
|
|
102
|
+
self._function = function
|
|
103
|
+
self._argument = argument
|
|
104
|
+
|
|
105
|
+
def _build_at(self, depth: int) -> Node:
|
|
106
|
+
return make_app(self._function.at(depth), self._argument.at(depth))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def var_at(level: int) -> Builder:
|
|
110
|
+
return _VarBuilder(level)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def lam(body: "Callable[[Builder], Builder]") -> Builder:
|
|
114
|
+
return _LamBuilder(body, None)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def lam_named(binder_name: str, body: "Callable[[Builder], Builder]") -> Builder:
|
|
118
|
+
"""``lam`` with the bound variable's readable name given explicitly, for when the body callable does
|
|
119
|
+
not carry it (a ``curry`` wrapper); the name is for rendering only and never affects the built node."""
|
|
120
|
+
return _LamBuilder(body, binder_name)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def app(function: Builder, argument: Builder) -> Builder:
|
|
124
|
+
return _AppBuilder(function, argument)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def curry(body: "Callable[..., Builder]") -> Builder:
|
|
128
|
+
"""Expand an N-argument Python function into a curried HOAS lambda.
|
|
129
|
+
|
|
130
|
+
``curry(lambda a, b, c: e)`` is ``lam(lambda a: lam(lambda b: lam(lambda c: e)))``: the
|
|
131
|
+
function's parameter count (read with ``inspect``) fixes the binder arity, and each parameter
|
|
132
|
+
is the ``Builder`` for a bound variable. A zero-argument ``body`` builds no binder and returns
|
|
133
|
+
``body()`` directly. This is sugar for the nested ``lam`` chains that multi-argument terms need.
|
|
134
|
+
"""
|
|
135
|
+
parameter_names = tuple(inspect.signature(body).parameters)
|
|
136
|
+
arity = len(parameter_names)
|
|
137
|
+
|
|
138
|
+
def collect(arguments: "list[Builder]") -> Builder:
|
|
139
|
+
if len(arguments) == arity:
|
|
140
|
+
return body(*arguments)
|
|
141
|
+
return lam_named(parameter_names[len(arguments)], lambda bound: collect([*arguments, bound]))
|
|
142
|
+
|
|
143
|
+
return collect([])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def build(term: Builder) -> Node:
|
|
147
|
+
"""Finalize a HOAS term into a de Bruijn ``Node``."""
|
|
148
|
+
return term.at(0)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Machine-generated Python: the defunctionalization compiler (DEFUN) self-compiled to a module of
|
|
2
|
+
# @interned closure classes, per Python version (the _py<ver> tag). Do not edit by hand.
|
|
3
|
+
# Regenerate with the console script co-lambda-regen-compiler
|
|
4
|
+
# (python -m co_lambda._compiler_artifact).
|
|
5
|
+
* linguist-generated=true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Committed defunctionalized (compiled) modules for the bootstrap compiler."""
|