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.
- kanonak_expression-0.1.0/PKG-INFO +20 -0
- kanonak_expression-0.1.0/README.md +8 -0
- kanonak_expression-0.1.0/kanonak_expression/__init__.py +244 -0
- kanonak_expression-0.1.0/kanonak_expression.egg-info/PKG-INFO +20 -0
- kanonak_expression-0.1.0/kanonak_expression.egg-info/SOURCES.txt +7 -0
- kanonak_expression-0.1.0/kanonak_expression.egg-info/dependency_links.txt +1 -0
- kanonak_expression-0.1.0/kanonak_expression.egg-info/top_level.txt +1 -0
- kanonak_expression-0.1.0/pyproject.toml +20 -0
- kanonak_expression-0.1.0/setup.cfg +4 -0
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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"]
|