kanonak-expression 0.1.0__tar.gz

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.
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: kanonak-expression
3
+ Version: 0.1.0
4
+ Summary: Kanonak expression runtime (expressionRuntimeVersion 1). Conformant port of @kanonak-protocol/expression, a deterministic tx + math tree-walker, verified against the shared parity vectors. Standard-library only.
5
+ Author: Kanonak Maintainers
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://kanonak.org
8
+ Project-URL: Repository, https://github.com/kanonak-protocol/runtime
9
+ Project-URL: Issues, https://github.com/kanonak-protocol/runtime/issues
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+
13
+ # kanonak-expression
14
+
15
+ The expression runtime for the [Kanonak Protocol](https://kanonak.org) - a Python
16
+ port of `@kanonak-protocol/expression` (`expressionRuntimeVersion "1"`), a
17
+ deterministic `tx` + `math` tree-walker verified against the shared parity
18
+ vectors. Standard library only.
19
+
20
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,8 @@
1
+ # kanonak-expression
2
+
3
+ The expression runtime for the [Kanonak Protocol](https://kanonak.org) - a Python
4
+ port of `@kanonak-protocol/expression` (`expressionRuntimeVersion "1"`), a
5
+ deterministic `tx` + `math` tree-walker verified against the shared parity
6
+ vectors. Standard library only.
7
+
8
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,244 @@
1
+ """Kanonak expression runtime (expressionRuntimeVersion "1").
2
+
3
+ A small, deterministic tree-walker that folds a ``kanonak.org/transformations``
4
+ (``tx``) + ``kanonak.org/math`` expression tree to a single number. An
5
+ independent conformant Python port of ``@kanonak-protocol/expression``, verified
6
+ against the shared parity vectors. Standard library only.
7
+
8
+ Three layers, exactly as the reference kernel establishes:
9
+
10
+ 1. DISPATCH -- ``OPERATOR_ARITY``, derived from the ``tx`` superclass hierarchy.
11
+ 2. PRIMITIVES -- ``UNARY`` / ``BINARY``, the authored determinism-bearing table.
12
+ 3. THE FOLD -- ``evaluate``: operators recurse + apply a primitive; literals
13
+ yield their numeric value; EVERYTHING ELSE (a typed VarRef, a domain leaf,
14
+ any future node) is handed to the caller's ``resolve(node, ctx, evaluate)``.
15
+
16
+ The runtime is a pure operator engine; binding and domain-leaf semantics are the
17
+ caller's business. It never privileges ``tx.VarRef`` -- that is just one leaf a
18
+ domain may resolve. ``EXPRESSION_RUNTIME_VERSION`` freezes the determinism
19
+ contract; a change to any primitive, value rule, or dispatch entry requires a NEW
20
+ version, never an edit in place.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ from typing import Any, Callable, Dict, Mapping
27
+
28
+ # The frozen expression-runtime version (determinism contract). Not hashed.
29
+ EXPRESSION_RUNTIME_VERSION = "1"
30
+
31
+ TX = "kanonak.org/transformations"
32
+ MATH = "kanonak.org/math"
33
+
34
+ # A node is a mapping with a "type" field (canonical URI string) plus operand
35
+ # keys. ``resolve(node, ctx, evaluate) -> number`` resolves any node the kernel
36
+ # does not recognise as an operator or literal.
37
+ ExprNode = Mapping[str, Any]
38
+ Resolve = Callable[[ExprNode, Any, Callable[[ExprNode, Any], float]], float]
39
+
40
+
41
+ class ExpressionError(Exception):
42
+ """Raised on any structural or domain error during evaluation."""
43
+
44
+
45
+ # ===========================================================================
46
+ # Dispatch -- operand shape per operator, derived from the tx superclass hierarchy
47
+ # ===========================================================================
48
+
49
+
50
+ def _un(operand: str):
51
+ return ("unary", operand)
52
+
53
+
54
+ def _bin(left: str, right: str):
55
+ return ("binary", left, right)
56
+
57
+
58
+ # UnaryNumericOp -> `value`; BinaryArithmetic -> arithLeft/arithRight;
59
+ # BinaryComparison -> compareLeft/compareRight; BooleanLogic -> operands list;
60
+ # Not -> operand (handled explicitly); Clip -> ternary.
61
+ _ARITH = _bin("arithLeft", "arithRight")
62
+ _COMPARE = _bin("compareLeft", "compareRight")
63
+ _VALUE = _un("value")
64
+
65
+ OPERATOR_ARITY: Dict[str, Any] = {
66
+ f"{TX}/Add": _ARITH,
67
+ f"{TX}/Subtract": _ARITH,
68
+ f"{TX}/Multiply": _ARITH,
69
+ f"{TX}/Divide": _ARITH,
70
+ f"{MATH}/Power": _ARITH,
71
+ f"{MATH}/Modulo": _ARITH,
72
+ f"{MATH}/Minimum": _ARITH,
73
+ f"{MATH}/Maximum": _ARITH,
74
+ f"{TX}/Abs": _VALUE,
75
+ f"{TX}/Negate": _VALUE,
76
+ f"{MATH}/Exp": _VALUE,
77
+ f"{MATH}/Ln": _VALUE,
78
+ f"{MATH}/Log10": _VALUE,
79
+ f"{MATH}/Sqrt": _VALUE,
80
+ f"{MATH}/Floor": _VALUE,
81
+ f"{MATH}/Ceil": _VALUE,
82
+ f"{MATH}/Round": _VALUE,
83
+ f"{MATH}/Sign": _VALUE,
84
+ f"{TX}/Equals": _COMPARE,
85
+ f"{TX}/GreaterThan": _COMPARE,
86
+ f"{TX}/LessThan": _COMPARE,
87
+ f"{TX}/GreaterThanOrEqual": _COMPARE,
88
+ f"{TX}/LessThanOrEqual": _COMPARE,
89
+ f"{TX}/And": ("nary", "operands"),
90
+ f"{TX}/Or": ("nary", "operands"),
91
+ # `Not` is a direct Expression subclass with boolean (not numeric-unary)
92
+ # semantics -- handled explicitly in `evaluate`, not via the numeric tables.
93
+ f"{MATH}/Clip": ("ternary", "clipValue", "clipLower", "clipUpper"),
94
+ }
95
+
96
+
97
+ # ===========================================================================
98
+ # Primitives -- the authored, determinism-bearing table (matched per language)
99
+ # ===========================================================================
100
+
101
+
102
+ def _require_domain(ok: bool, msg: str) -> None:
103
+ if not ok:
104
+ raise ExpressionError(msg)
105
+
106
+
107
+ def _floored_mod(a: float, b: float) -> float:
108
+ """Floored modulo: Modulo(-7, 3) = 2, Modulo(7, -3) = -2."""
109
+ if b == 0:
110
+ raise ExpressionError("Modulo by zero")
111
+ return a - b * math.floor(a / b)
112
+
113
+
114
+ def _round_half_away(a: float) -> float:
115
+ """Round half away from zero: Round(2.5) = 3, Round(-2.5) = -3."""
116
+ return math.copysign(math.floor(abs(a) + 0.5), a)
117
+
118
+
119
+ def _sign(x: float) -> float:
120
+ if x > 0:
121
+ return 1.0
122
+ if x < 0:
123
+ return -1.0
124
+ return 0.0
125
+
126
+
127
+ def _truthy(n: float) -> bool:
128
+ return n != 0
129
+
130
+
131
+ def _bool(b: bool) -> float:
132
+ return 1.0 if b else 0.0
133
+
134
+
135
+ UNARY: Dict[str, Callable[[float], float]] = {
136
+ f"{TX}/Abs": lambda x: abs(x),
137
+ f"{TX}/Negate": lambda x: -x,
138
+ f"{MATH}/Exp": lambda x: math.exp(x),
139
+ f"{MATH}/Ln": lambda x: (_require_domain(x > 0, "Ln of a non-positive number"), math.log(x))[1],
140
+ f"{MATH}/Log10": lambda x: (_require_domain(x > 0, "Log10 of a non-positive number"), math.log10(x))[1],
141
+ f"{MATH}/Sqrt": lambda x: (_require_domain(x >= 0, "Sqrt of a negative number"), math.sqrt(x))[1],
142
+ f"{MATH}/Floor": lambda x: float(math.floor(x)),
143
+ f"{MATH}/Ceil": lambda x: float(math.ceil(x)),
144
+ f"{MATH}/Round": _round_half_away,
145
+ f"{MATH}/Sign": _sign,
146
+ }
147
+
148
+ BINARY: Dict[str, Callable[[float, float], float]] = {
149
+ f"{TX}/Add": lambda a, b: a + b,
150
+ f"{TX}/Subtract": lambda a, b: a - b,
151
+ f"{TX}/Multiply": lambda a, b: a * b,
152
+ f"{TX}/Divide": lambda a, b: (_require_domain(b != 0, "Divide by zero"), a / b)[1],
153
+ f"{MATH}/Power": lambda a, b: math.pow(a, b),
154
+ f"{MATH}/Modulo": _floored_mod,
155
+ f"{MATH}/Minimum": lambda a, b: min(a, b),
156
+ f"{MATH}/Maximum": lambda a, b: max(a, b),
157
+ f"{TX}/Equals": lambda a, b: _bool(a == b),
158
+ f"{TX}/GreaterThan": lambda a, b: _bool(a > b),
159
+ f"{TX}/LessThan": lambda a, b: _bool(a < b),
160
+ f"{TX}/GreaterThanOrEqual": lambda a, b: _bool(a >= b),
161
+ f"{TX}/LessThanOrEqual": lambda a, b: _bool(a <= b),
162
+ }
163
+
164
+
165
+ def _literal_value(node: ExprNode):
166
+ """Numeric value of a literal node, or ``None`` if it is not a literal."""
167
+ t = node.get("type")
168
+ if t == f"{TX}/IntegerLiteral":
169
+ return float(node["integerLiteral"])
170
+ if t == f"{TX}/DecimalLiteral":
171
+ return float(node["decimalLiteral"])
172
+ if t == f"{TX}/BooleanLiteral":
173
+ v = node["booleanLiteral"]
174
+ return _bool(v is True or v == "true")
175
+ return None
176
+
177
+
178
+ def _operand(node: ExprNode, key: str) -> ExprNode:
179
+ v = node.get(key)
180
+ if not isinstance(v, Mapping):
181
+ raise ExpressionError(f"{node.get('type')} is missing operand '{key}'")
182
+ return v
183
+
184
+
185
+ def evaluate(node: ExprNode, ctx: Any, resolve: Resolve) -> float:
186
+ """Evaluate an expression tree to a number.
187
+
188
+ Operators fold via the frozen dispatch + primitive tables; literals yield
189
+ their numeric value; any other node is delegated to ``resolve``.
190
+ """
191
+
192
+ def recurse(n: ExprNode, c: Any) -> float:
193
+ return evaluate(n, c, resolve)
194
+
195
+ node_type = node.get("type")
196
+ arity = OPERATOR_ARITY.get(node_type)
197
+ if arity is not None:
198
+ kind = arity[0]
199
+ if kind == "unary":
200
+ x = recurse(_operand(node, arity[1]), ctx)
201
+ return UNARY[node_type](x)
202
+ if kind == "binary":
203
+ a = recurse(_operand(node, arity[1]), ctx)
204
+ b = recurse(_operand(node, arity[2]), ctx)
205
+ return BINARY[node_type](a, b)
206
+ if kind == "nary":
207
+ items = node.get(arity[1])
208
+ if not isinstance(items, (list, tuple)):
209
+ raise ExpressionError(f"{node_type} expects an '{arity[1]}' list")
210
+ is_and = node_type == f"{TX}/And"
211
+ # Short-circuit; empty And is vacuously true, empty Or vacuously false.
212
+ for item in items:
213
+ v = _truthy(recurse(item, ctx))
214
+ if is_and and not v:
215
+ return 0.0
216
+ if not is_and and v:
217
+ return 1.0
218
+ return _bool(is_and)
219
+ if kind == "ternary":
220
+ # Only Clip today: clamp clipValue into [clipLower, clipUpper].
221
+ v = recurse(_operand(node, arity[1]), ctx)
222
+ lo = recurse(_operand(node, arity[2]), ctx)
223
+ hi = recurse(_operand(node, arity[3]), ctx)
224
+ return min(max(v, lo), hi)
225
+
226
+ if node_type == f"{TX}/Not":
227
+ return _bool(not _truthy(recurse(_operand(node, "operand"), ctx)))
228
+
229
+ lit = _literal_value(node)
230
+ if lit is not None:
231
+ return lit
232
+
233
+ # Not an operator or literal -- a binding or domain leaf. The caller owns it.
234
+ return resolve(node, ctx, recurse)
235
+
236
+
237
+ __all__ = [
238
+ "EXPRESSION_RUNTIME_VERSION",
239
+ "ExpressionError",
240
+ "OPERATOR_ARITY",
241
+ "UNARY",
242
+ "BINARY",
243
+ "evaluate",
244
+ ]
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: kanonak-expression
3
+ Version: 0.1.0
4
+ Summary: Kanonak expression runtime (expressionRuntimeVersion 1). Conformant port of @kanonak-protocol/expression, a deterministic tx + math tree-walker, verified against the shared parity vectors. Standard-library only.
5
+ Author: Kanonak Maintainers
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://kanonak.org
8
+ Project-URL: Repository, https://github.com/kanonak-protocol/runtime
9
+ Project-URL: Issues, https://github.com/kanonak-protocol/runtime/issues
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+
13
+ # kanonak-expression
14
+
15
+ The expression runtime for the [Kanonak Protocol](https://kanonak.org) - a Python
16
+ port of `@kanonak-protocol/expression` (`expressionRuntimeVersion "1"`), a
17
+ deterministic `tx` + `math` tree-walker verified against the shared parity
18
+ vectors. Standard library only.
19
+
20
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,7 @@
1
+ README.md
2
+ pyproject.toml
3
+ kanonak_expression/__init__.py
4
+ kanonak_expression.egg-info/PKG-INFO
5
+ kanonak_expression.egg-info/SOURCES.txt
6
+ kanonak_expression.egg-info/dependency_links.txt
7
+ kanonak_expression.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ kanonak_expression
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kanonak-expression"
7
+ version = "0.1.0"
8
+ readme = "README.md"
9
+ description = "Kanonak expression runtime (expressionRuntimeVersion 1). Conformant port of @kanonak-protocol/expression, a deterministic tx + math tree-walker, verified against the shared parity vectors. Standard-library only."
10
+ requires-python = ">=3.8"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Kanonak Maintainers" }]
13
+
14
+ [project.urls]
15
+ Homepage = "https://kanonak.org"
16
+ Repository = "https://github.com/kanonak-protocol/runtime"
17
+ Issues = "https://github.com/kanonak-protocol/runtime/issues"
18
+
19
+ [tool.setuptools]
20
+ packages = ["kanonak_expression"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+