sutra-dev 0.2.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.
- sutra_compiler/__init__.py +49 -0
- sutra_compiler/__main__.py +514 -0
- sutra_compiler/ast_nodes.py +553 -0
- sutra_compiler/codegen.py +1811 -0
- sutra_compiler/codegen_base.py +2436 -0
- sutra_compiler/codegen_pytorch.py +1472 -0
- sutra_compiler/diagnostics.py +145 -0
- sutra_compiler/inliner.py +581 -0
- sutra_compiler/lexer.py +821 -0
- sutra_compiler/parser.py +2112 -0
- sutra_compiler/review.py +322 -0
- sutra_compiler/simplify.py +1046 -0
- sutra_compiler/simplify_egglog.py +674 -0
- sutra_compiler/stdlib/axons.su +53 -0
- sutra_compiler/stdlib/embed.su +48 -0
- sutra_compiler/stdlib/javascript_object.su +18 -0
- sutra_compiler/stdlib/logic.su +202 -0
- sutra_compiler/stdlib/math.su +12 -0
- sutra_compiler/stdlib/memory.su +82 -0
- sutra_compiler/stdlib/numbers.su +99 -0
- sutra_compiler/stdlib/rotation.su +83 -0
- sutra_compiler/stdlib/similarity.su +97 -0
- sutra_compiler/stdlib/strings.su +56 -0
- sutra_compiler/stdlib/tensor.su +82 -0
- sutra_compiler/stdlib/vectors.su +119 -0
- sutra_compiler/stdlib_loader.py +219 -0
- sutra_compiler/sutradb_embedded.py +273 -0
- sutra_compiler/trace.py +135 -0
- sutra_compiler/validator.py +552 -0
- sutra_compiler/workspace.py +655 -0
- sutra_dev-0.2.0.dist-info/METADATA +80 -0
- sutra_dev-0.2.0.dist-info/RECORD +36 -0
- sutra_dev-0.2.0.dist-info/WHEEL +5 -0
- sutra_dev-0.2.0.dist-info/entry_points.txt +2 -0
- sutra_dev-0.2.0.dist-info/licenses/LICENSE +201 -0
- sutra_dev-0.2.0.dist-info/top_level.txt +1 -0
sutra_compiler/review.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"""Review mode — language-level trace of parsing + algebraic simplification.
|
|
2
|
+
|
|
3
|
+
Run as:
|
|
4
|
+
|
|
5
|
+
python sutrac.py --review FILE.su
|
|
6
|
+
|
|
7
|
+
This mode is about seeing how the compiler understands your program
|
|
8
|
+
at the language level — how it parses, how it simplifies. It does
|
|
9
|
+
NOT show the emitted Python/PyTorch code (that's downstream codegen
|
|
10
|
+
boilerplate and not the interesting thing); use `--emit` if you
|
|
11
|
+
want that separately.
|
|
12
|
+
|
|
13
|
+
Stages shown:
|
|
14
|
+
|
|
15
|
+
1. Source — the .su source you wrote.
|
|
16
|
+
2. Parsing — Sutra text -> abstract syntax tree,
|
|
17
|
+
pretty-printed back as pseudo-Sutra.
|
|
18
|
+
3. Stdlib inlining — stdlib function bodies copied in-place.
|
|
19
|
+
4. Simplification — each rewrite rule that fired + before/after.
|
|
20
|
+
|
|
21
|
+
Stages that do nothing (e.g. inlining on a program with no stdlib
|
|
22
|
+
calls) are collapsed to one line instead of re-dumping the whole AST,
|
|
23
|
+
so the output focuses on what actually changed.
|
|
24
|
+
|
|
25
|
+
The headline stage is #4 — the simplification trace. That's where
|
|
26
|
+
you see which of the 16 algebraic rewrites touched your program and
|
|
27
|
+
what they turned your expressions into. If no rewrites fired, the
|
|
28
|
+
trace says so explicitly and points at `examples/review_demo.su` as
|
|
29
|
+
a sample program that does trigger several.
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
from typing import List, Tuple
|
|
36
|
+
|
|
37
|
+
from . import ast_nodes as ast
|
|
38
|
+
from .lexer import Lexer
|
|
39
|
+
from .parser import Parser
|
|
40
|
+
from .simplify import set_trace_callback, simplify_module
|
|
41
|
+
from .inliner import inline_stdlib_calls
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------
|
|
45
|
+
# Pretty-printer — emit a pseudo-Sutra-source form of the AST
|
|
46
|
+
# ---------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def pretty_expr(node) -> str:
|
|
50
|
+
if node is None:
|
|
51
|
+
return "<none>"
|
|
52
|
+
if isinstance(node, ast.Identifier):
|
|
53
|
+
return node.name
|
|
54
|
+
if isinstance(node, ast.IntLiteral):
|
|
55
|
+
return str(node.value)
|
|
56
|
+
if isinstance(node, ast.FloatLiteral):
|
|
57
|
+
return f"{node.value!r}"
|
|
58
|
+
if isinstance(node, ast.BoolLiteral):
|
|
59
|
+
return "true" if node.value else "false"
|
|
60
|
+
if isinstance(node, ast.StringLiteral):
|
|
61
|
+
return f'"{node.value}"'
|
|
62
|
+
if isinstance(node, ast.CharLiteral):
|
|
63
|
+
return f"'{chr(node.value)}'"
|
|
64
|
+
if isinstance(node, ast.ImaginaryLiteral):
|
|
65
|
+
return f"{node.value}i"
|
|
66
|
+
if isinstance(node, ast.ComplexLiteral):
|
|
67
|
+
return f"({node.re} + {node.im}i)"
|
|
68
|
+
if hasattr(ast, "UnknownLiteral") and isinstance(node, ast.UnknownLiteral):
|
|
69
|
+
return "unknown"
|
|
70
|
+
if isinstance(node, ast.Parenthesized):
|
|
71
|
+
return f"({pretty_expr(node.inner)})"
|
|
72
|
+
if isinstance(node, ast.UnaryOp):
|
|
73
|
+
return f"{node.op}{pretty_expr(node.operand)}"
|
|
74
|
+
if isinstance(node, ast.BinaryOp):
|
|
75
|
+
return f"{pretty_expr(node.left)} {node.op} {pretty_expr(node.right)}"
|
|
76
|
+
if isinstance(node, ast.Call):
|
|
77
|
+
callee = (node.callee.name
|
|
78
|
+
if isinstance(node.callee, ast.Identifier)
|
|
79
|
+
else pretty_expr(node.callee))
|
|
80
|
+
args = ", ".join(pretty_expr(a) for a in node.args)
|
|
81
|
+
return f"{callee}({args})"
|
|
82
|
+
if isinstance(node, ast.ArrayLiteral):
|
|
83
|
+
return "[" + ", ".join(pretty_expr(e) for e in node.elements) + "]"
|
|
84
|
+
if isinstance(node, ast.Subscript):
|
|
85
|
+
return f"{pretty_expr(node.target)}[{pretty_expr(node.index)}]"
|
|
86
|
+
if isinstance(node, ast.MemberAccess):
|
|
87
|
+
return f"{pretty_expr(node.obj)}.{node.member}"
|
|
88
|
+
if isinstance(node, ast.EmbedExpr):
|
|
89
|
+
return f"embed({pretty_expr(node.expr)})"
|
|
90
|
+
if isinstance(node, ast.DefuzzyExpr):
|
|
91
|
+
return f"defuzzy({pretty_expr(node.expr)})"
|
|
92
|
+
return f"<{type(node).__name__}>"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def pretty_stmt(stmt, indent: int = 0) -> List[str]:
|
|
96
|
+
pad = " " * indent
|
|
97
|
+
if isinstance(stmt, ast.VarDecl):
|
|
98
|
+
type_str = f"{stmt.type_ref.name} " if stmt.type_ref else "var "
|
|
99
|
+
init = f" = {pretty_expr(stmt.initializer)}" if stmt.initializer else ""
|
|
100
|
+
return [f"{pad}{type_str}{stmt.name}{init};"]
|
|
101
|
+
if isinstance(stmt, ast.ReturnStmt):
|
|
102
|
+
return [f"{pad}return {pretty_expr(stmt.value)};"]
|
|
103
|
+
if isinstance(stmt, ast.ExprStmt):
|
|
104
|
+
return [f"{pad}{pretty_expr(stmt.expr)};"]
|
|
105
|
+
if isinstance(stmt, ast.FunctionDecl):
|
|
106
|
+
ret = stmt.return_type.name if stmt.return_type else "void"
|
|
107
|
+
params = ", ".join(
|
|
108
|
+
f"{p.type_ref.name if p.type_ref else 'var'} {p.name}"
|
|
109
|
+
for p in (stmt.params or [])
|
|
110
|
+
)
|
|
111
|
+
lines = [f"{pad}function {ret} {stmt.name}({params}) {{"]
|
|
112
|
+
for s in stmt.body.statements:
|
|
113
|
+
lines.extend(pretty_stmt(s, indent + 1))
|
|
114
|
+
lines.append(f"{pad}}}")
|
|
115
|
+
return lines
|
|
116
|
+
if isinstance(stmt, ast.IfStmt):
|
|
117
|
+
lines = [f"{pad}if ({pretty_expr(stmt.condition)}) {{"]
|
|
118
|
+
for s in stmt.then_branch.statements:
|
|
119
|
+
lines.extend(pretty_stmt(s, indent + 1))
|
|
120
|
+
lines.append(f"{pad}}}")
|
|
121
|
+
return lines
|
|
122
|
+
return [f"{pad}<{type(stmt).__name__}>"]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def pretty_module(module: ast.Module) -> str:
|
|
126
|
+
"""One line per statement — keep it compact so diffs read cleanly."""
|
|
127
|
+
out: List[str] = []
|
|
128
|
+
for item in module.items:
|
|
129
|
+
out.extend(pretty_stmt(item))
|
|
130
|
+
return "\n".join(out)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------------------------------------------------------------------
|
|
134
|
+
# Trace collector
|
|
135
|
+
# ---------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class _TraceCollector:
|
|
139
|
+
def __init__(self):
|
|
140
|
+
self.events: List[Tuple[str, str, str]] = []
|
|
141
|
+
|
|
142
|
+
def __call__(self, rule: str, before, after) -> None:
|
|
143
|
+
self.events.append(
|
|
144
|
+
(rule, pretty_expr(before), pretty_expr(after))
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ---------------------------------------------------------------------
|
|
149
|
+
# Rendering
|
|
150
|
+
# ---------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
RULE_BAR = "=" * 72
|
|
154
|
+
SUB_BAR = "-" * 72
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _heading(num: int, title: str, explainer: str) -> str:
|
|
158
|
+
"""Numbered heading + a one-line explainer of what this section is."""
|
|
159
|
+
return (f"\n{RULE_BAR}\n"
|
|
160
|
+
f"Stage {num}: {title}\n"
|
|
161
|
+
f"{SUB_BAR}\n"
|
|
162
|
+
f" {explainer}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _compare(before: str, after: str) -> str:
|
|
166
|
+
"""One-line description of whether two stages differ."""
|
|
167
|
+
if before == after:
|
|
168
|
+
return "(no change from previous stage)"
|
|
169
|
+
n_before = before.count("\n") + 1
|
|
170
|
+
n_after = after.count("\n") + 1
|
|
171
|
+
delta = n_after - n_before
|
|
172
|
+
sign = "+" if delta >= 0 else ""
|
|
173
|
+
return f"(changed: {sign}{delta} lines vs previous stage)"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------
|
|
177
|
+
# Review entry point
|
|
178
|
+
# ---------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def review_file(path: str) -> int:
|
|
182
|
+
if not os.path.exists(path):
|
|
183
|
+
print(f"{path}: error: file not found", file=sys.stderr)
|
|
184
|
+
return 1
|
|
185
|
+
|
|
186
|
+
with open(path, encoding="utf-8") as f:
|
|
187
|
+
src = f.read()
|
|
188
|
+
|
|
189
|
+
# Banner — orient the reader before anything else.
|
|
190
|
+
print(RULE_BAR)
|
|
191
|
+
print(f"REVIEW MODE — {path}")
|
|
192
|
+
print(RULE_BAR)
|
|
193
|
+
print("""
|
|
194
|
+
Shows how the compiler parses your .su source and then applies
|
|
195
|
+
algebraic simplification rewrites to it. Four stages:
|
|
196
|
+
|
|
197
|
+
1. Source — what you wrote.
|
|
198
|
+
2. Parsing — the AST, pretty-printed back as Sutra-ish syntax.
|
|
199
|
+
3. Stdlib inlining — if you called any stdlib function, its body
|
|
200
|
+
gets copied in-place here.
|
|
201
|
+
4. Simplification — each algebraic rewrite that fired, with the
|
|
202
|
+
expression before and after.
|
|
203
|
+
|
|
204
|
+
Stage 4 is the interesting one. If zero rewrites fire, your program
|
|
205
|
+
is already in minimal form — try `examples/review_demo.su` to see a
|
|
206
|
+
program that triggers five of them.
|
|
207
|
+
|
|
208
|
+
(Downstream codegen -> PyTorch is not shown here — use `--emit` for
|
|
209
|
+
that if you want it. This mode is strictly language-level.)
|
|
210
|
+
""".strip())
|
|
211
|
+
|
|
212
|
+
# --- Stage 1: Source ---
|
|
213
|
+
print(_heading(1, "Source",
|
|
214
|
+
"The .su text you wrote. Input to the compiler."))
|
|
215
|
+
print()
|
|
216
|
+
for line in src.rstrip().splitlines():
|
|
217
|
+
print(f" {line}")
|
|
218
|
+
|
|
219
|
+
# --- Stage 2: Parsing ---
|
|
220
|
+
lexer = Lexer(src, file=path)
|
|
221
|
+
tokens = lexer.tokenize()
|
|
222
|
+
parser = Parser(tokens, file=path, diagnostics=lexer.diagnostics)
|
|
223
|
+
module = parser.parse_module()
|
|
224
|
+
if lexer.diagnostics.errors:
|
|
225
|
+
print("\nParse errors:")
|
|
226
|
+
for d in lexer.diagnostics.errors:
|
|
227
|
+
print(f" {d.format()}")
|
|
228
|
+
return 1
|
|
229
|
+
|
|
230
|
+
parsed_repr = pretty_module(module)
|
|
231
|
+
print(_heading(
|
|
232
|
+
2, "Parsing",
|
|
233
|
+
"Source text becomes an Abstract Syntax Tree. Below is the "
|
|
234
|
+
"AST pretty-printed back as (pseudo-)Sutra syntax — it should "
|
|
235
|
+
"look similar to your source, with comments removed and "
|
|
236
|
+
"expressions normalized."))
|
|
237
|
+
print()
|
|
238
|
+
for line in parsed_repr.splitlines():
|
|
239
|
+
print(f" {line}")
|
|
240
|
+
|
|
241
|
+
# --- Stage 3: Stdlib inlining ---
|
|
242
|
+
try:
|
|
243
|
+
inline_stdlib_calls(module)
|
|
244
|
+
except Exception as e:
|
|
245
|
+
print(f"\nInliner error: {type(e).__name__}: {e}")
|
|
246
|
+
return 1
|
|
247
|
+
inlined_repr = pretty_module(module)
|
|
248
|
+
print(_heading(
|
|
249
|
+
3, "Stdlib inlining",
|
|
250
|
+
"Calls to stdlib functions (e.g. logical_and, defuzzy) get "
|
|
251
|
+
"replaced by their polynomial bodies in-place, so the "
|
|
252
|
+
"downstream simplifier can fold literal inputs. If your "
|
|
253
|
+
"program doesn't call anything from stdlib, this is a no-op."))
|
|
254
|
+
print(f" {_compare(parsed_repr, inlined_repr)}")
|
|
255
|
+
if inlined_repr != parsed_repr:
|
|
256
|
+
print()
|
|
257
|
+
for line in inlined_repr.splitlines():
|
|
258
|
+
print(f" {line}")
|
|
259
|
+
|
|
260
|
+
# --- Stage 4: Simplification (the headline) ---
|
|
261
|
+
collector = _TraceCollector()
|
|
262
|
+
set_trace_callback(collector)
|
|
263
|
+
try:
|
|
264
|
+
simplify_module(module)
|
|
265
|
+
finally:
|
|
266
|
+
set_trace_callback(None)
|
|
267
|
+
simplified_repr = pretty_module(module)
|
|
268
|
+
|
|
269
|
+
print(_heading(
|
|
270
|
+
4, "Simplification (headline stage)",
|
|
271
|
+
"The compiler applies algebraic rewrite rules that preserve "
|
|
272
|
+
"meaning but reduce work. Each line below is one rewrite that "
|
|
273
|
+
"fired, showing the expression BEFORE and AFTER the rule "
|
|
274
|
+
"applied. Rule names (R01-R16) refer to the numbered rules "
|
|
275
|
+
"documented in sdk/sutra-compiler/sutra_compiler/simplify.py."))
|
|
276
|
+
print()
|
|
277
|
+
if not collector.events:
|
|
278
|
+
print(" No rewrites fired on this program.")
|
|
279
|
+
print()
|
|
280
|
+
print(" This means either (a) the program is already in minimal")
|
|
281
|
+
print(" form, or (b) its expressions don't match any of the 16")
|
|
282
|
+
print(" rewrite patterns. Programs that DO trigger rewrites:")
|
|
283
|
+
print(" - examples/review_demo.su (R01, R04, R05, R08, R16)")
|
|
284
|
+
print(" - bundle(v) (R01)")
|
|
285
|
+
print(" - similarity(king, king) (R04)")
|
|
286
|
+
print(" - unbind(r, bind(r, king)) (R08)")
|
|
287
|
+
print(" - 2 + 3 (R16)")
|
|
288
|
+
else:
|
|
289
|
+
print(f" {len(collector.events)} rewrite(s) fired:")
|
|
290
|
+
print()
|
|
291
|
+
for i, (rule, before, after) in enumerate(collector.events, 1):
|
|
292
|
+
print(f" [{i}] {rule}")
|
|
293
|
+
print(f" before: {before}")
|
|
294
|
+
print(f" after: {after}")
|
|
295
|
+
print()
|
|
296
|
+
|
|
297
|
+
# Only re-print the AST if simplification actually changed something.
|
|
298
|
+
if simplified_repr != inlined_repr:
|
|
299
|
+
print(f" {SUB_BAR[2:]}")
|
|
300
|
+
print(f" Simplified AST:")
|
|
301
|
+
print()
|
|
302
|
+
for line in simplified_repr.splitlines():
|
|
303
|
+
print(f" {line}")
|
|
304
|
+
|
|
305
|
+
# --- Final summary ---
|
|
306
|
+
print(_heading(5, "Summary", "One-line recap."))
|
|
307
|
+
print(f" source: {src.count(chr(10)) + 1} lines "
|
|
308
|
+
f"({len(src)} chars)")
|
|
309
|
+
print(f" AST statements: {len(module.items)} top-level items")
|
|
310
|
+
print(f" inlining: {'changed the AST' if inlined_repr != parsed_repr else 'no-op (no stdlib calls)'}")
|
|
311
|
+
print(f" simplification: {len(collector.events)} rewrite(s) fired")
|
|
312
|
+
print()
|
|
313
|
+
print(" (To see the emitted Python/PyTorch code, run "
|
|
314
|
+
"`python sutrac.py --emit FILE.su`.)")
|
|
315
|
+
return 0
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if __name__ == "__main__":
|
|
319
|
+
if len(sys.argv) != 2:
|
|
320
|
+
print("usage: python -m sutra_compiler.review FILE.su", file=sys.stderr)
|
|
321
|
+
sys.exit(2)
|
|
322
|
+
sys.exit(review_file(sys.argv[1]))
|