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.
@@ -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]))