plan-kernel 0.1.1__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.
- plan_kernel/__init__.py +3 -0
- plan_kernel/__main__.py +14 -0
- plan_kernel/evaluator.py +305 -0
- plan_kernel/expander.py +23 -0
- plan_kernel/kernel.py +219 -0
- plan_kernel/magics.py +72 -0
- plan_kernel/parser.py +11 -0
- plan_kernel/prelude.py +77 -0
- plan_kernel/render.py +161 -0
- plan_kernel/runtime/__init__.py +1 -0
- plan_kernel/runtime/bplan.py +20 -0
- plan_kernel/runtime/bplan_deps.py +115 -0
- plan_kernel/runtime/plan.py +133 -0
- plan_kernel-0.1.1.dist-info/METADATA +219 -0
- plan_kernel-0.1.1.dist-info/RECORD +17 -0
- plan_kernel-0.1.1.dist-info/WHEEL +5 -0
- plan_kernel-0.1.1.dist-info/top_level.txt +1 -0
plan_kernel/prelude.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""BPLAN op prelude — boot.plan-style wrappers auto-loaded on kernel start.
|
|
2
|
+
|
|
3
|
+
For each ``(name, arity)`` in ``runtime.bplan_deps.ALL_DEPS``, this module
|
|
4
|
+
constructs the boot.plan-style wrapper:
|
|
5
|
+
|
|
6
|
+
.. code-block:: text
|
|
7
|
+
|
|
8
|
+
(#bind Name
|
|
9
|
+
(#pin
|
|
10
|
+
(#law "Name" (Name a b ...)
|
|
11
|
+
((#pin "B") ("Name" a b ...)))))
|
|
12
|
+
|
|
13
|
+
and evaluates it against the supplied env. After loading, expressions like
|
|
14
|
+
``(Add 2 3)``, ``(Inc 41)``, ``(Eq x y)`` work in any cell.
|
|
15
|
+
|
|
16
|
+
Under Phase E (the Marduk swap), the runtime is :mod:`marduk.runtime` and
|
|
17
|
+
the BPLAN op table is :mod:`marduk.runtime.bplan`. Wrappers for op names
|
|
18
|
+
in ``ALL_DEPS`` that aren't yet implemented in Marduk's op table still
|
|
19
|
+
*bind* successfully — the wrapper Law is constructed without evaluating
|
|
20
|
+
its body — but calling them at runtime will raise NotImplementedError
|
|
21
|
+
from the BPLAN dispatcher. Add the op upstream rather than working
|
|
22
|
+
around it here.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from marduk.asm import Env, eval_form, parse_many
|
|
28
|
+
from marduk.runtime.strnat import str_nat
|
|
29
|
+
|
|
30
|
+
from .runtime.bplan_deps import ALL_DEPS
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__all__ = ["PRELUDE_NAMES", "load_prelude"]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Single-letter argument names. ``ALL_DEPS`` arities cap at 6 (``Elim``);
|
|
37
|
+
# 26 letters is plenty.
|
|
38
|
+
_ARG_LETTERS = "abcdefghijklmnopqrstuvwxyz"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _arg_names(arity: int) -> str:
|
|
42
|
+
if arity > len(_ARG_LETTERS):
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"prelude: arity {arity} exceeds supported letters ({len(_ARG_LETTERS)})"
|
|
45
|
+
)
|
|
46
|
+
return " ".join(_ARG_LETTERS[i] for i in range(arity))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _wrapper_source(name: str, arity: int) -> str:
|
|
50
|
+
args = _arg_names(arity)
|
|
51
|
+
sig = f"{name} {args}"
|
|
52
|
+
body = f'((#pin "B") ("{name}" {args}))'
|
|
53
|
+
return f'(#bind {name} (#pin (#law "{name}" ({sig}) {body})))'
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def load_prelude(env: Env) -> set[int]:
|
|
57
|
+
"""Load every BPLAN op wrapper into ``env``.
|
|
58
|
+
|
|
59
|
+
Returns the set of name nats that were bound — useful for the
|
|
60
|
+
evaluator's ``%env`` magic to filter prelude noise from user bindings.
|
|
61
|
+
"""
|
|
62
|
+
bound: set[int] = set()
|
|
63
|
+
for name, arity in ALL_DEPS.items():
|
|
64
|
+
if arity < 1:
|
|
65
|
+
continue
|
|
66
|
+
src = _wrapper_source(name, arity)
|
|
67
|
+
for form in parse_many(src):
|
|
68
|
+
eval_form(form, env)
|
|
69
|
+
bound.add(str_nat(name))
|
|
70
|
+
return bound
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Precomputed set of all prelude name nats. Equivalent to the return value
|
|
74
|
+
# of ``load_prelude(...)`` but available without performing the load.
|
|
75
|
+
PRELUDE_NAMES: frozenset[int] = frozenset(
|
|
76
|
+
str_nat(name) for name, arity in ALL_DEPS.items() if arity >= 1
|
|
77
|
+
)
|
plan_kernel/render.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Structural value renderer.
|
|
2
|
+
|
|
3
|
+
``render_value(val)`` returns a ``(text/plain, text/html)`` pair the
|
|
4
|
+
kernel emits as a Jupyter MIME bundle. Both forms are depth-bounded so
|
|
5
|
+
a pathological structure (a malformed App tree, a Pin loop) can't blow
|
|
6
|
+
the stack while we're trying to *show* it.
|
|
7
|
+
|
|
8
|
+
Format:
|
|
9
|
+
|
|
10
|
+
- Nat ``n`` → ``"42"`` (decimal).
|
|
11
|
+
- Pin ``v`` → ``"<v>"``.
|
|
12
|
+
- Pin ``Law`` → ``"<{'name'…}>"`` when ``pretty=True`` (default);
|
|
13
|
+
otherwise expanded as ``"<{name arity body}>"``.
|
|
14
|
+
- Law ``{a n b}`` → ``"{'name' arity body}"``; ``name`` is decoded
|
|
15
|
+
via ``nat_str`` and shown single-quoted (or
|
|
16
|
+
decimal if the nat doesn't decode to printable
|
|
17
|
+
UTF-8).
|
|
18
|
+
- App ``f x`` → ``"(f x)"``.
|
|
19
|
+
|
|
20
|
+
The HTML output uses inline ``style="…"`` (Jupyter strips ``<style>``
|
|
21
|
+
blocks) and a cross-theme palette that reads on both light and dark
|
|
22
|
+
backgrounds.
|
|
23
|
+
|
|
24
|
+
Phase E note: this module now reads Marduk's ``Val`` accessors —
|
|
25
|
+
``.head``/``.tail`` for App, ``.item`` for Pin, ``.args`` for Law's
|
|
26
|
+
arity (a ``Val``, not an int), ``.name`` (also a ``Val``). The
|
|
27
|
+
predicates ``is_app``/``is_law``/etc. come from the runtime shim,
|
|
28
|
+
which dispatches on ``val.type``.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import html
|
|
34
|
+
|
|
35
|
+
from marduk.runtime.strnat import nat_str
|
|
36
|
+
|
|
37
|
+
from .runtime.plan import is_app, is_law, is_nat, is_pin
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = ["render_value"]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_DEFAULT_DEPTH = 32
|
|
44
|
+
|
|
45
|
+
# Cross-theme-friendly palette.
|
|
46
|
+
_NAT = "color:#0097a7" # cyan
|
|
47
|
+
_LAW = "color:#e65100;font-style:italic" # orange italic
|
|
48
|
+
_NAME = "color:#388e3c" # green (decoded name)
|
|
49
|
+
_MUTED = "color:#999" # gray (brackets)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def render_value(val, *, pretty: bool = True,
|
|
53
|
+
max_depth: int = _DEFAULT_DEPTH) -> tuple[str, str]:
|
|
54
|
+
"""Return ``(text, html)`` for ``val``.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
pretty
|
|
59
|
+
Collapse ``Pin(Law(...))`` to ``<{name…}>`` for terse display
|
|
60
|
+
(default). Set to ``False`` to fully expand into
|
|
61
|
+
``<{name arity body}>``.
|
|
62
|
+
max_depth
|
|
63
|
+
Recursion bound. Past this depth the renderer emits an ellipsis.
|
|
64
|
+
"""
|
|
65
|
+
text = _text(val, depth=0, pretty=pretty, max_depth=max_depth)
|
|
66
|
+
body = _html(val, depth=0, pretty=pretty, max_depth=max_depth)
|
|
67
|
+
full_html = (
|
|
68
|
+
'<code style="font-family:ui-monospace,SFMono-Regular,Menlo,monospace;'
|
|
69
|
+
f'font-size:0.9em">{body}</code>'
|
|
70
|
+
)
|
|
71
|
+
return text, full_html
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
# text/plain
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
def _text(val, *, depth: int, pretty: bool, max_depth: int) -> str:
|
|
79
|
+
if depth >= max_depth:
|
|
80
|
+
return "..."
|
|
81
|
+
if is_nat(val):
|
|
82
|
+
return str(val.nat)
|
|
83
|
+
if is_pin(val):
|
|
84
|
+
if pretty and is_law(val.item):
|
|
85
|
+
return f"<{{{_law_name_text(val.item)}…}}>"
|
|
86
|
+
inner = _text(val.item, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
87
|
+
return f"<{inner}>"
|
|
88
|
+
if is_law(val):
|
|
89
|
+
body = _text(val.body, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
90
|
+
return f"{{{_law_name_text(val)} {val.args.nat} {body}}}"
|
|
91
|
+
if is_app(val):
|
|
92
|
+
f = _text(val.head, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
93
|
+
a = _text(val.tail, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
94
|
+
return f"({f} {a})"
|
|
95
|
+
return repr(val)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _law_name_text(law) -> str:
|
|
99
|
+
"""Decode a law's ``name`` via ``nat_str`` for display.
|
|
100
|
+
|
|
101
|
+
Returns ``'identifier'`` (single-quoted) for printable UTF-8 nats, or
|
|
102
|
+
decimal for nats that don't decode. Non-nat names recurse through the
|
|
103
|
+
text renderer with a small depth bound.
|
|
104
|
+
"""
|
|
105
|
+
if is_nat(law.name):
|
|
106
|
+
s = nat_str(law.name.nat)
|
|
107
|
+
if s and not s.startswith("<nat:"):
|
|
108
|
+
return f"'{s}'"
|
|
109
|
+
return str(law.name.nat)
|
|
110
|
+
return _text(law.name, depth=0, pretty=True, max_depth=4)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# text/html
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
def _html(val, *, depth: int, pretty: bool, max_depth: int) -> str:
|
|
118
|
+
if depth >= max_depth:
|
|
119
|
+
return f'<span style="{_MUTED}">…</span>'
|
|
120
|
+
if is_nat(val):
|
|
121
|
+
return f'<span style="{_NAT}">{val.nat}</span>'
|
|
122
|
+
if is_pin(val):
|
|
123
|
+
if pretty and is_law(val.item):
|
|
124
|
+
return (
|
|
125
|
+
f'<span style="{_MUTED}"><{{</span>'
|
|
126
|
+
f"{_law_name_html(val.item)}"
|
|
127
|
+
f'<span style="{_MUTED}">…}}></span>'
|
|
128
|
+
)
|
|
129
|
+
inner = _html(val.item, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
130
|
+
return (
|
|
131
|
+
f'<span style="{_MUTED}"><</span>'
|
|
132
|
+
f"{inner}"
|
|
133
|
+
f'<span style="{_MUTED}">></span>'
|
|
134
|
+
)
|
|
135
|
+
if is_law(val):
|
|
136
|
+
body = _html(val.body, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
137
|
+
return (
|
|
138
|
+
f'<span style="{_MUTED}">{{</span>'
|
|
139
|
+
f"{_law_name_html(val)} "
|
|
140
|
+
f'<span style="{_NAT}">{val.args.nat}</span> '
|
|
141
|
+
f"{body}"
|
|
142
|
+
f'<span style="{_MUTED}">}}</span>'
|
|
143
|
+
)
|
|
144
|
+
if is_app(val):
|
|
145
|
+
f = _html(val.head, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
146
|
+
a = _html(val.tail, depth=depth + 1, pretty=pretty, max_depth=max_depth)
|
|
147
|
+
return (
|
|
148
|
+
f'<span style="{_MUTED}">(</span>'
|
|
149
|
+
f"{f} {a}"
|
|
150
|
+
f'<span style="{_MUTED}">)</span>'
|
|
151
|
+
)
|
|
152
|
+
return html.escape(repr(val), quote=False)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _law_name_html(law) -> str:
|
|
156
|
+
if is_nat(law.name):
|
|
157
|
+
s = nat_str(law.name.nat)
|
|
158
|
+
if s and not s.startswith("<nat:"):
|
|
159
|
+
return f'<span style="{_NAME}">\'{html.escape(s)}\'</span>'
|
|
160
|
+
return f'<span style="{_NAT}">{law.name.nat}</span>'
|
|
161
|
+
return f'<span style="{_LAW}">{html.escape(repr(law.name))}</span>'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Vendored PLAN runtime — see VENDOR.md for source provenance and sync policy."""
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""BPLAN harness — compatibility shim.
|
|
2
|
+
|
|
3
|
+
The legacy plan-kernel BPLAN harness implemented jet-based dispatch on
|
|
4
|
+
top of the old runtime. Phase E moves the canonical BPLAN op table into
|
|
5
|
+
:mod:`marduk.runtime.bplan`, where dispatch is integrated with the
|
|
6
|
+
spec-faithful core. This module re-exports the new table so callers
|
|
7
|
+
that import from ``plan_kernel.runtime.bplan`` continue to resolve.
|
|
8
|
+
|
|
9
|
+
Note: the legacy file used to define a separate ``bevaluate`` driver
|
|
10
|
+
distinct from ``evaluate``. With Marduk both modes share the same
|
|
11
|
+
evaluator (the spec-faithful core does the right thing without a jet
|
|
12
|
+
overlay), so the historic distinction has gone away — ``bevaluate``
|
|
13
|
+
here is an alias for ``evaluate``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from marduk.runtime import evaluate as bevaluate
|
|
17
|
+
from marduk.runtime.bplan import OPS, dispatch
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["OPS", "dispatch", "bevaluate"]
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BPLAN named-op dependencies of the Gallowglass codegen.
|
|
3
|
+
|
|
4
|
+
Per `DECISIONS.md §"Upstream PLAN authority"`, the canonical PLAN spec is
|
|
5
|
+
`vendor/reaver/src/hs/Plan.hs`. PLAN proper and the Plan Asm text format are
|
|
6
|
+
frozen; the BPLAN named-op set may drift over time.
|
|
7
|
+
|
|
8
|
+
This module enumerates every BPLAN intrinsic that gallowglass-emitted code
|
|
9
|
+
relies on, by name and arity. `tests/sanity/test_bplan_deps.py` greps the
|
|
10
|
+
pinned `vendor/reaver/src/hs/Plan.hs` and asserts presence at the right arity.
|
|
11
|
+
This is the canary that fires when `vendor.lock` is bumped to a Reaver SHA
|
|
12
|
+
that has renamed, removed, or rearity'd one of our deps.
|
|
13
|
+
|
|
14
|
+
Each entry: `name -> arity`.
|
|
15
|
+
|
|
16
|
+
Categories:
|
|
17
|
+
Core primitives — needed by codegen for opcode-equivalent operations.
|
|
18
|
+
Arithmetic/bytes — needed by Core.Nat / Core.Text / Core.Bytes prelude.
|
|
19
|
+
RPLAN — needed for I/O (deferred; see Phase G).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Core primitives gallowglass emits directly from codegen.
|
|
26
|
+
# Replaces the legacy P(N(0))..P(N(4)) opcode-pin shape with BPLAN named-Law
|
|
27
|
+
# references that delegate to (P("B")) ("Name" args).
|
|
28
|
+
CORE_PRIMITIVES: dict[str, int] = {
|
|
29
|
+
'Pin': 1, # was P(N(0))
|
|
30
|
+
'Law': 3, # was P(N(1)) / MkLaw
|
|
31
|
+
'Inc': 1, # was P(N(2))
|
|
32
|
+
'Elim': 6, # was P(N(3)) / Case_ — canonical 6-arity dispatch
|
|
33
|
+
'Force': 1, # was P(N(4))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Prelude arithmetic / introspection — used by the BPLAN harness as Python
|
|
38
|
+
# fast-path jets, but their existence in Reaver is what makes gallowglass-
|
|
39
|
+
# emitted prelude code actually run on Reaver at all.
|
|
40
|
+
PRELUDE_INTRINSICS: dict[str, int] = {
|
|
41
|
+
# Arithmetic
|
|
42
|
+
'Add': 2,
|
|
43
|
+
'Sub': 2,
|
|
44
|
+
'Mul': 2,
|
|
45
|
+
'Div': 2,
|
|
46
|
+
'Mod': 2,
|
|
47
|
+
'Dec': 1,
|
|
48
|
+
# Comparison — full set
|
|
49
|
+
'Eq': 2,
|
|
50
|
+
'Ne': 2,
|
|
51
|
+
'Lt': 2,
|
|
52
|
+
'Le': 2,
|
|
53
|
+
'Gt': 2,
|
|
54
|
+
'Ge': 2,
|
|
55
|
+
'Cmp': 2,
|
|
56
|
+
# Boolean / control
|
|
57
|
+
'Truth': 1,
|
|
58
|
+
'Or': 2,
|
|
59
|
+
'And': 2,
|
|
60
|
+
'If': 3,
|
|
61
|
+
'Ifz': 3,
|
|
62
|
+
# Bit ops (used by bytes encoding)
|
|
63
|
+
'Lsh': 2,
|
|
64
|
+
'Rsh': 2,
|
|
65
|
+
'Bex': 1, # 2^n — used for bytesBar high-bit decoding in REPL demos
|
|
66
|
+
# Introspection (used in the harness; future codegen may emit these)
|
|
67
|
+
'Type': 1,
|
|
68
|
+
'IsPin': 1,
|
|
69
|
+
'IsLaw': 1,
|
|
70
|
+
'IsApp': 1,
|
|
71
|
+
'IsNat': 1,
|
|
72
|
+
'Hd': 1,
|
|
73
|
+
'Sz': 1,
|
|
74
|
+
'Unpin': 1,
|
|
75
|
+
'Arity': 1,
|
|
76
|
+
'Name': 1,
|
|
77
|
+
'Body': 1,
|
|
78
|
+
# Sequencing
|
|
79
|
+
'Seq': 2,
|
|
80
|
+
# Diagnostics
|
|
81
|
+
'Trace': 2,
|
|
82
|
+
# Small Case dispatchers — Case<N> takes one nat plus N-1 indexed
|
|
83
|
+
# branches plus a fallback, so total arity is N+1.
|
|
84
|
+
'Case2': 3,
|
|
85
|
+
'Case3': 4,
|
|
86
|
+
'Case4': 5,
|
|
87
|
+
'Case5': 6,
|
|
88
|
+
'Case6': 7,
|
|
89
|
+
'Case7': 8,
|
|
90
|
+
'Case8': 9,
|
|
91
|
+
'Case9': 10,
|
|
92
|
+
'Case10': 11,
|
|
93
|
+
'Case11': 12,
|
|
94
|
+
'Case12': 13,
|
|
95
|
+
'Case13': 14,
|
|
96
|
+
'Case14': 15,
|
|
97
|
+
'Case15': 16,
|
|
98
|
+
'Case16': 17,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# All BPLAN deps as a single dict, suitable for the sanity test.
|
|
103
|
+
ALL_DEPS: dict[str, int] = {
|
|
104
|
+
**CORE_PRIMITIVES,
|
|
105
|
+
**PRELUDE_INTRINSICS,
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# strNat encoding helper — packs UTF-8 bytes little-endian into a Python int.
|
|
110
|
+
# Mirrors `bootstrap.codegen.encode_name`. Defined here so `bplan_deps`
|
|
111
|
+
# stays free of bootstrap dependencies and can be imported from tests.
|
|
112
|
+
def str_nat(s: str) -> int:
|
|
113
|
+
"""Encode s as a little-endian nat (Reaver's `strNat`)."""
|
|
114
|
+
raw = s.encode('utf-8')
|
|
115
|
+
return int.from_bytes(raw, 'little')
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""PLAN runtime — compatibility shim re-exporting Marduk's runtime under
|
|
2
|
+
the legacy plan-kernel API names.
|
|
3
|
+
|
|
4
|
+
Plan-kernel originally vendored a copy of the gallowglass harness at
|
|
5
|
+
``runtime/plan.py``. As part of the Phase E swap, the runtime is now
|
|
6
|
+
provided by :mod:`marduk.runtime`; this file maps the old names
|
|
7
|
+
(``A``, ``L``, ``N``, ``P``, ``is_app``/``is_law``/``is_nat``/``is_pin``,
|
|
8
|
+
``_unapp``, ``evaluate``, ``str_nat``, ``nat_str``) onto the new API so
|
|
9
|
+
existing callers don't have to change.
|
|
10
|
+
|
|
11
|
+
A few semantics differ from the legacy runtime that matter for callers:
|
|
12
|
+
|
|
13
|
+
* ``L(arity, name, body)`` constructs a Marduk law via the smart
|
|
14
|
+
constructor — it forces the body's let-binding spine via ``B`` (per
|
|
15
|
+
spec), so callers shouldn't expect deferred evaluation of binding
|
|
16
|
+
values.
|
|
17
|
+
* ``N(n)`` returns a ``Val`` (not a raw int). Comparisons of the form
|
|
18
|
+
``some_val == 0`` no longer work — use ``some_val.nat == 0``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from marduk.runtime import (
|
|
24
|
+
Val, Hol,
|
|
25
|
+
Nat as _Nat,
|
|
26
|
+
Pin as _Pin,
|
|
27
|
+
App as _App,
|
|
28
|
+
Law as _Law,
|
|
29
|
+
evaluate as _evaluate,
|
|
30
|
+
force,
|
|
31
|
+
PlanError,
|
|
32
|
+
PlanLoop,
|
|
33
|
+
)
|
|
34
|
+
from marduk.runtime.strnat import str_nat, nat_str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# constructors (legacy short names)
|
|
39
|
+
"A", "L", "N", "P", "Hol",
|
|
40
|
+
# new long names also exposed for forward-compat
|
|
41
|
+
"App", "Law", "Nat", "Pin", "Val",
|
|
42
|
+
# type predicates
|
|
43
|
+
"is_app", "is_law", "is_nat", "is_pin",
|
|
44
|
+
# spine flatten
|
|
45
|
+
"_unapp", "unapp",
|
|
46
|
+
# drivers
|
|
47
|
+
"evaluate", "force",
|
|
48
|
+
# str-nat helpers
|
|
49
|
+
"str_nat", "nat_str",
|
|
50
|
+
# exceptions
|
|
51
|
+
"PlanError", "PlanLoop",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ---- New-API aliases -------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
App = _App
|
|
58
|
+
Law = _Law
|
|
59
|
+
Nat = _Nat
|
|
60
|
+
Pin = _Pin
|
|
61
|
+
evaluate = _evaluate
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---- Legacy short-name aliases --------------------------------------------
|
|
65
|
+
|
|
66
|
+
def _coerce(x):
|
|
67
|
+
"""Coerce a Python int to a Marduk ``Val``; pass other ``Val`` through.
|
|
68
|
+
|
|
69
|
+
Plan-kernel's legacy runtime treated raw Python ints as nats, so call
|
|
70
|
+
sites like ``P(5)`` meant "pin a nat 5". Marduk has no raw-int
|
|
71
|
+
representation for nats — every nat is a ``Val``. The legacy
|
|
72
|
+
constructor shims auto-coerce so existing code keeps working.
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(x, Val):
|
|
75
|
+
return x
|
|
76
|
+
if isinstance(x, int):
|
|
77
|
+
return _Nat(x)
|
|
78
|
+
return x
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def A(f, x):
|
|
82
|
+
"""Legacy alias for :func:`marduk.runtime.App`."""
|
|
83
|
+
return _App(_coerce(f), _coerce(x))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def L(arity, name, body):
|
|
87
|
+
"""Legacy law constructor: ``L(arity, name, body)``.
|
|
88
|
+
|
|
89
|
+
Plan-kernel's ``class L`` was ``__init__(self, arity, name, body)``.
|
|
90
|
+
Marduk's smart constructor uses ``Law(name, arity, body)``; this
|
|
91
|
+
shim flips the order back, wraps int arity in a ``Nat``, and
|
|
92
|
+
coerces int name/body args.
|
|
93
|
+
"""
|
|
94
|
+
arity_val = arity if isinstance(arity, Val) else _Nat(arity)
|
|
95
|
+
return _Law(_coerce(name), arity_val, _coerce(body))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def N(n):
|
|
99
|
+
"""Legacy alias for :func:`marduk.runtime.Nat`. Wraps an int as a Val."""
|
|
100
|
+
return _Nat(n)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def P(item):
|
|
104
|
+
"""Legacy alias for :func:`marduk.runtime.Pin`."""
|
|
105
|
+
return _Pin(_coerce(item))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---- Type predicates -------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
def is_app(v):
|
|
111
|
+
return isinstance(v, Val) and v.type == "app"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def is_law(v):
|
|
115
|
+
return isinstance(v, Val) and v.type == "law"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def is_nat(v):
|
|
119
|
+
return isinstance(v, Val) and v.type == "nat"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def is_pin(v):
|
|
123
|
+
return isinstance(v, Val) and v.type == "pin"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---- Spine flatten ---------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
def unapp(v):
|
|
129
|
+
"""Public alias for :attr:`marduk.runtime.Val.spine`."""
|
|
130
|
+
return v.spine
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
_unapp = unapp
|