@xiaohuo123212/elench 0.0.0-stage → 0.1.0

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.
Files changed (69) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +53 -0
  3. package/README.md +298 -2
  4. package/bin/elench.js +69 -0
  5. package/package.json +37 -4
  6. package/scripts/clean-pycache.js +37 -0
  7. package/scripts/npm_smoke.js +88 -0
  8. package/src/README-/345/205/274/345/256/271/345/261/202.md +26 -0
  9. package/src/ast_util.py +111 -0
  10. package/src/bench.py +376 -0
  11. package/src/borrow.py +282 -0
  12. package/src/bound.py +13 -0
  13. package/src/cfg.py +219 -0
  14. package/src/chart.py +209 -0
  15. package/src/check_docs.py +156 -0
  16. package/src/checker.py +2473 -0
  17. package/src/contract_metric.py +87 -0
  18. package/src/cost_check.py +13 -0
  19. package/src/cost_diff.py +533 -0
  20. package/src/cost_domain/__init__.py +32 -0
  21. package/src/cost_domain/bound.py +739 -0
  22. package/src/cost_domain/cost_check.py +394 -0
  23. package/src/cost_domain/cost_interp.py +1998 -0
  24. package/src/cost_domain/symbolic.py +554 -0
  25. package/src/cost_explain.py +503 -0
  26. package/src/cost_interp.py +13 -0
  27. package/src/cxx_map.py +184 -0
  28. package/src/dispatch.py +314 -0
  29. package/src/dsh.py +3980 -0
  30. package/src/dup_scan.py +180 -0
  31. package/src/elench.py +15 -0
  32. package/src/emit.py +56 -0
  33. package/src/ffi_c.py +70 -0
  34. package/src/fmt.py +115 -0
  35. package/src/formatter.py +596 -0
  36. package/src/grammar.py +272 -0
  37. package/src/guarantees.py +116 -0
  38. package/src/highlight.py +156 -0
  39. package/src/interp.py +2119 -0
  40. package/src/isolated.py +161 -0
  41. package/src/jsonval.py +236 -0
  42. package/src/kappa_vs_measured.py +347 -0
  43. package/src/kinds.py +122 -0
  44. package/src/lexer.py +559 -0
  45. package/src/migrate_docs.py +115 -0
  46. package/src/otel.py +579 -0
  47. package/src/otel_dashboard.py +218 -0
  48. package/src/otel_export.py +380 -0
  49. package/src/otel_local_sink.py +141 -0
  50. package/src/otel_view.py +156 -0
  51. package/src/parser.py +1909 -0
  52. package/src/parser.py.bak +1781 -0
  53. package/src/policy.py +271 -0
  54. package/src/rename_ch.py +81 -0
  55. package/src/requires.py +242 -0
  56. package/src/segment_plan.py +150 -0
  57. package/src/shape.py +130 -0
  58. package/src/stdlib.py +1856 -0
  59. package/src/stdlib.py.bak +1751 -0
  60. package/src/symbolic.py +13 -0
  61. package/src/sync_docs.py +225 -0
  62. package/src/test_checker.py.bak +443 -0
  63. package/src/test_formatter.py.bak +201 -0
  64. package/src/test_invalid.py.bak +342 -0
  65. package/src/test_parser.py.bak +423 -0
  66. package/src/typecheck.py +865 -0
  67. package/src/typecheck.py.bak +741 -0
  68. package/src/typenames.py +56 -0
  69. package/src/walk.py +197 -0
@@ -0,0 +1,423 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ test_parser.py —— M0 解析器测试
5
+
6
+ 覆盖:
7
+ · 两份样例完整解析(M0 验收 ⑦)
8
+ · 关键构造的形状(target / model / effects / repeats / choose / kernel)
9
+ · 字符字面量、元组类型、&mut、命名实参、match 臂、`?`、`as`
10
+ · 错误恢复与终止符不匹配
11
+
12
+ 运行:
13
+ cd src; $env:PYTHONIOENCODING="utf-8"; python test_parser.py
14
+ """
15
+
16
+ import os
17
+ import sys
18
+
19
+ sys.stdout.reconfigure(encoding="utf-8")
20
+
21
+ from lexer import Kind
22
+ from parser import Node, parse_source
23
+
24
+ HERE = os.path.dirname(os.path.abspath(__file__))
25
+ SAMPLE = os.path.join(HERE, "..", "文档", "设计", "目前设计", "样例")
26
+
27
+
28
+ # ---------------------------------------------------------------- 工具
29
+
30
+ _passed = 0
31
+ _failed = []
32
+
33
+
34
+ def check(cond, name, detail=""):
35
+ global _passed
36
+ if cond:
37
+ _passed += 1
38
+ else:
39
+ _failed.append(f"{name} {detail}")
40
+ print(f" FAIL {name} {detail}")
41
+
42
+
43
+ def items(mod, kind):
44
+ return [i for i in mod.children if i.kind == kind]
45
+
46
+
47
+ def spec_by_name(mod, name):
48
+ for it in items(mod, "spec"):
49
+ if it.name == name:
50
+ return it
51
+ return None
52
+
53
+
54
+ def clauses(spec):
55
+ """取出 spec 下的所有子句(在 spec-body 里)。"""
56
+ out = []
57
+ for c in spec.children:
58
+ if c.kind == "spec-body":
59
+ out.extend(x for x in c.children if x.kind == "clause")
60
+ elif c.kind == "clause":
61
+ out.append(c)
62
+ return out
63
+
64
+
65
+ def clause(spec, name):
66
+ for c in clauses(spec):
67
+ if c.name == name:
68
+ return c
69
+ return None
70
+
71
+
72
+ def models(spec):
73
+ out = []
74
+ for c in spec.children:
75
+ if c.kind == "spec-body":
76
+ out.extend(x for x in c.children if x.kind == "model")
77
+ return out
78
+
79
+
80
+ def walk(node, kind, acc=None):
81
+ if acc is None:
82
+ acc = []
83
+ if node.kind == kind:
84
+ acc.append(node)
85
+ for c in node.children:
86
+ walk(c, kind, acc)
87
+ return acc
88
+
89
+
90
+ def load(name):
91
+ with open(os.path.join(SAMPLE, name), "r", encoding="utf-8") as f:
92
+ return f.read()
93
+
94
+
95
+ # ---------------------------------------------------------------- 1. 样例
96
+
97
+ def test_samples():
98
+ print("[1] 样例完整解析(M0 ⑦)")
99
+
100
+ tu = parse_source(load("calculator.tu"), "calculator.tu")
101
+ check(tu.ok, "calculator.tu 无诊断",
102
+ f"实际 {len(tu.diagnostics)} 条: "
103
+ + "; ".join(f"{d.code}@{d.line}:{d.col}" for d in tu.diagnostics[:3]))
104
+ check(len(items(tu.module, "use")) == 1, "tu: 1 个 use")
105
+ check(len(items(tu.module, "const")) == 12, "tu: 12 个 const",
106
+ f"实际 {len(items(tu.module, 'const'))}")
107
+ check(len(items(tu.module, "enum")) == 2, "tu: 2 个 enum")
108
+ check(len(items(tu.module, "spec")) == 10, "tu: 10 个 spec")
109
+
110
+ ai = parse_source(load("calculator.ai"), "calculator.ai")
111
+ check(ai.ok, "calculator.ai 无诊断",
112
+ f"实际 {len(ai.diagnostics)} 条: "
113
+ + "; ".join(f"{d.code}@{d.line}:{d.col}" for d in ai.diagnostics[:3]))
114
+ check(len(items(ai.module, "fn")) == 10, "ai: 10 个 fn")
115
+ check(len(items(ai.module, "const")) == 0, "ai: 零声明(F-4)")
116
+ check(len(items(ai.module, "use")) == 0, "ai: 零 use(F-8)")
117
+
118
+
119
+ # ---------------------------------------------------------------- 2. spec 子句
120
+
121
+ def test_spec_clauses():
122
+ print("[2] spec 子句形状")
123
+ tu = parse_source(load("calculator.tu"), "tu")
124
+ ev = spec_by_name(tu.module, "evaluate")
125
+
126
+ check(ev is not None, "找到 spec evaluate")
127
+ eff = clause(ev, "effects")
128
+ check(eff is not None and eff.value == ["alloc"], "evaluate.effects = {alloc}",
129
+ f"实际 {getattr(eff, 'value', None)}")
130
+
131
+ req = clause(ev, "requires")
132
+ check(req is not None and len(req.children) == 1, "evaluate 有 requires")
133
+ check(req.children[0].kind == "binary"
134
+ and req.children[0].value == "<=", "requires 是 <= 比较")
135
+
136
+ tgt = clause(ev, "target")
137
+ check(tgt is not None, "evaluate 有 target")
138
+ dims = [d.name for d in tgt.children]
139
+ check(dims == ["time", "allocation"], "target 两个维度 time/allocation",
140
+ f"实际 {dims}")
141
+
142
+ time_dim = tgt.children[0]
143
+ slots = [s.name for s in time_dim.children]
144
+ check(slots == ["max"], "time 维度只有 max 槽位(target 只有上限)",
145
+ f"实际 {slots}")
146
+
147
+ ms = models(ev)
148
+ check([m.name for m in ms] == ["normal", "stress"], "两个 model 场景",
149
+ f"实际 {[m.name for m in ms]}")
150
+ check(ms[0].children[0].name == "text", "model 的参数名是 text")
151
+
152
+ sd = spec_by_name(tu.module, "is_digit")
153
+ e2 = clause(sd, "effects")
154
+ check(e2 is not None and e2.value == "pure", "is_digit.effects = pure",
155
+ f"实际 {getattr(e2, 'value', None)}")
156
+
157
+ sr = spec_by_name(tu.module, "should_reduce")
158
+ rep = clause(sr, "repeats")
159
+ check(rep is not None, "should_reduce 有 repeats(T13 情形二)")
160
+
161
+
162
+ # ---------------------------------------------------------------- 3. 类型
163
+
164
+ def test_types():
165
+ print("[3] 类型形状")
166
+ tu = parse_source(load("calculator.tu"), "tu")
167
+
168
+ ev = spec_by_name(tu.module, "evaluate")
169
+ # ev.children: params, type-name(Result), spec-body
170
+ tys = [c for c in ev.children if c.kind.startswith("type")]
171
+ check(tys and tys[0].name == "Result", "evaluate 返回 Result")
172
+ check([t.name for t in tys[0].children] == ["i64", "CalcError"],
173
+ "Result 的两个类型参数")
174
+
175
+ p = spec_by_name(tu.module, "pop_until_open")
176
+ params = [c for c in p.children if c.kind == "params"][0]
177
+ refs = walk(params, "type-ref")
178
+ check(len(refs) == 2, "pop_until_open 两个 &mut 参数", f"实际 {len(refs)}")
179
+ check(all(r.value == "mut" for r in refs), "两个都是 &mut")
180
+
181
+ rn = spec_by_name(tu.module, "read_number")
182
+ tuples = walk(rn, "type-tuple")
183
+ check(len(tuples) == 1, "read_number 返回元组类型")
184
+ check([t.name for t in tuples[0].children] == ["i64", "u64"], "元组元素 i64, u64")
185
+
186
+ tk = spec_by_name(tu.module, "tokenize")
187
+ slices = walk(tk, "type-slice")
188
+ check(len(slices) == 1, "tokenize 参数是切片 []u8")
189
+
190
+
191
+ # ---------------------------------------------------------------- 4. enum
192
+
193
+ def test_enum():
194
+ print("[4] enum 与载荷")
195
+ tu = parse_source(load("calculator.tu"), "tu")
196
+ enums = {e.name: e for e in items(tu.module, "enum")}
197
+ check(set(enums) == {"Token", "CalcError"}, "两个 enum: Token / CalcError")
198
+
199
+ tok = enums["Token"]
200
+ names = [v.name for v in tok.children]
201
+ check(names[:2] == ["Number", "Plus"], "Token 变体顺序",
202
+ f"实际 {names[:2]}")
203
+
204
+ arith = [v for v in enums["CalcError"].children if v.name == "Arith"][0]
205
+ fields = [f.name for f in arith.children if f.kind == "field"]
206
+ check(fields == ["cause"], "CalcError.Arith 有具名载荷 cause")
207
+ ftype = [c for c in arith.children if c.kind == "field"][0].children[0]
208
+ check(ftype.name == "Arithmetic", "载荷类型是 Arithmetic")
209
+
210
+
211
+ # ---------------------------------------------------------------- 5. 语句
212
+
213
+ def test_statements():
214
+ print("[5] 语句形状")
215
+ ai = parse_source(load("calculator.ai"), "ai")
216
+
217
+ def fn(name):
218
+ for it in items(ai.module, "fn"):
219
+ if it.name == name:
220
+ return it
221
+ return None
222
+
223
+ ev = fn("evaluate")
224
+ check(ev.value["pub"] is True, "evaluate 是 pub")
225
+ body = [c for c in ev.children if c.kind == "block"][0]
226
+ kinds = [s.kind for s in body.children]
227
+ check(kinds == ["val-decl", "return"], "evaluate 体是 let + return", f"实际 {kinds}")
228
+ check(walk(body, "try"), "evaluate 用了 ?")
229
+
230
+ tk = fn("tokenize")
231
+ tbody = [c for c in tk.children if c.kind == "block"][0]
232
+ check(any(s.kind == "while" for s in tbody.children), "tokenize 有 while")
233
+ wh = [s for s in tbody.children if s.kind == "while"][0]
234
+ check(walk(wh, "if"), "while 体内有 if 链")
235
+ check(len(walk(wh, "method-call")) > 0, "有方法调用")
236
+
237
+ rd = fn("reduce")
238
+ rbody = [c for c in rd.children if c.kind == "block"][0]
239
+ mts = walk(rbody, "match")
240
+ check(len(mts) == 1, "reduce 体内有一个 match", f"实际 {len(mts)}")
241
+ mt = mts[0]
242
+ check(len(mt.children) == 5, "reduce 的 match 有 4 臂 + 判别式",
243
+ f"实际 {len(mt.children)}")
244
+ arms = [c for c in mt.children if c.kind == "arm"]
245
+ check(len(arms) == 4, "4 个臂")
246
+ check(any(walk(a, "pattern-wildcard") for a in arms), "有 _ 通配臂")
247
+
248
+ at = fn("apply_top")
249
+ abody = [c for c in at.children if c.kind == "block"][0]
250
+ named = walk(abody, "named-arg")
251
+ check(len(named) >= 1, "apply_top 有命名实参(cause = …)", f"实际 {len(named)}")
252
+
253
+ dv = fn("digit_value")
254
+ dbody = [c for c in dv.children if c.kind == "block"][0]
255
+ check(walk(dbody, "cast"), "digit_value 用了 as 转换")
256
+
257
+ sr = fn("should_reduce")
258
+ sbody = [c for c in sr.children if c.kind == "block"][0]
259
+ check(sbody.children[0].kind == "if", "should_reduce 以 if 开头")
260
+
261
+
262
+ # ---------------------------------------------------------------- 6. 字符字面量
263
+
264
+ def test_char_literals():
265
+ print("[6] 字符字面量(L18)")
266
+ tu = parse_source(load("calculator.tu"), "tu")
267
+ consts = {c.name: c for c in items(tu.module, "const")}
268
+ sp = consts["SPACE"]
269
+ lit = walk(sp, "lit-char")[0]
270
+ check(lit.value == "' '", "SPACE 的值是字符字面量 ' '", f"实际 {lit.value!r}")
271
+ check(walk(consts["OPEN_PAREN"], "lit-char"), "OPEN_PAREN 是字符字面量")
272
+
273
+
274
+ # ---------------------------------------------------------------- 7. choose / kernel / ?!
275
+
276
+ def test_choose_kernel_dispatch():
277
+ print("[7] choose / kernel / ?!(B-16、F-9)")
278
+ src = """
279
+ spec average(scores: []u8) -> Result[u64, CalcError] {
280
+ effects {alloc};
281
+ model small { scores: make_u8s(max_len = 16), };
282
+ target {
283
+ time : { max: O(scores.length()) },
284
+ allocation: { max: 0 },
285
+ };
286
+ choose sum_kernel(scores: []u8) -> u64 {
287
+ over {sum_by_index, sum_by_iter pending};
288
+ usemodel {small};
289
+ objective time;
290
+ } end choose
291
+ } end spec
292
+ """
293
+ r = parse_source(src, "t.tu")
294
+ check(r.ok, "tu 片段无诊断",
295
+ "; ".join(f"{d.code}@{d.line}:{d.message}" for d in r.diagnostics[:2]))
296
+
297
+ spec = items(r.module, "spec")[0]
298
+ body = items(spec, "spec-body")[0]
299
+ chs = items(body, "choose")
300
+ check(len(chs) == 1, "choose 挂在 spec 里")
301
+ ch = chs[0]
302
+ check(ch.name == "sum_kernel", "choose 标签", ch.name)
303
+ names = {c.name: c.value for c in ch.children}
304
+ over = names.get("over") or {}
305
+ check(over.get("names") == ["sum_by_index", "sum_by_iter"], "over 名单",
306
+ f"实际 {over.get('names')}")
307
+ check(over.get("pending") == ["sum_by_iter"], "over 里的 pending 标记",
308
+ f"实际 {over.get('pending')}")
309
+ check(names.get("usemodel") == ["small"], "usemodel 集合")
310
+ check(names.get("objective") == "time", "objective")
311
+
312
+ ai_src = """
313
+ pub fn average(scores: []u8) -> Result[u64, CalcError] {
314
+ val n = scores.length();
315
+ if n == 0 { return Err(CalcError.Empty); } end if
316
+ val total = ?!sum_kernel(scores);
317
+ return (total / n)?;
318
+ } kernels sum_kernel(scores: []u8) -> u64 {
319
+ sum_by_index {
320
+ var s = 0; var i = 0;
321
+ while i < scores.length() { s = s + *scores.at(i); i = i + 1; } end while
322
+ return s;
323
+ }
324
+ sum_by_iter {
325
+ return 0;
326
+ }
327
+ } end fn
328
+ """
329
+ r2 = parse_source(ai_src, "t.ai")
330
+ check(r2.ok, "ai 片段无诊断",
331
+ "; ".join(f"{d.code}@{d.line}:{d.message}" for d in r2.diagnostics[:2]))
332
+
333
+ disp = walk(r2.module, "dispatch")
334
+ check(len(disp) == 1, "一个 ?! 决策点")
335
+ check(disp[0].name == "sum_kernel", "标签名 sum_kernel")
336
+
337
+ fn = items(r2.module, "fn")[0]
338
+ ks = items(fn, "kernels")
339
+ check(len(ks) == 1, "fn 上一个 kernels 块")
340
+ cands = items(ks[0], "candidate")
341
+ check([c.name for c in cands] == ["sum_by_index", "sum_by_iter"], "两个候选",
342
+ str([c.name for c in cands]))
343
+
344
+
345
+ # ---------------------------------------------------------------- 8. 错误与恢复
346
+
347
+ def test_errors():
348
+ print("[8] 错误诊断与恢复")
349
+
350
+ r = parse_source("spec a() -> u64 { effects {}; } end spec", "x")
351
+ check(r.ok, "最简 spec 可解析",
352
+ "; ".join(f"{d.code}@{d.line}" for d in r.diagnostics))
353
+
354
+ r = parse_source("spec a() -> u64 { effects {}; } end fn", "x")
355
+ check(not r.ok, "end fn 错配 spec 应报错")
356
+ check(any(d.code == "E-Parse-EndKindMismatch" for d in r.diagnostics),
357
+ "报 E-Parse-EndKindMismatch",
358
+ f"实际 {[d.code for d in r.diagnostics]}")
359
+
360
+ r = parse_source("spec a() -> u64 { effects {};", "x")
361
+ check(not r.ok, "缺 } 应报错")
362
+ check(any(d.code.startswith("E-Parse") for d in r.diagnostics), "给出 E-Parse-* 诊断")
363
+
364
+ r = parse_source("} } garbage", "x")
365
+ check(not r.ok, "顶层垃圾应报错")
366
+ check(len(r.diagnostics) >= 1, "至少一条诊断")
367
+
368
+ # 恢复:坏一个 item 之后仍能解析后面的
369
+ r = parse_source("enum E { A, B } end enum\nspec ok() -> u64 { effects {}; } end spec\n", "x")
370
+ check(r.ok, "前面好后面也好时无诊断",
371
+ "; ".join(f"{d.code}@{d.line}" for d in r.diagnostics))
372
+ check(len(items(r.module, "spec")) == 1, "恢复后仍拿到 spec")
373
+
374
+ # 不允许死循环:大量垃圾必须终止
375
+ r = parse_source("§§§ " * 200, "x")
376
+ check(not r.ok, "垃圾输入报错")
377
+ check(len(r.diagnostics) > 0, "有诊断且未挂死")
378
+
379
+ r = parse_source("", "x")
380
+ check(r.ok, "空文件合法")
381
+ check(r.module is not None and len(r.module.children) == 0, "空模块零 item")
382
+
383
+
384
+ # ---------------------------------------------------------------- 9. 确定性
385
+
386
+ def test_determinism():
387
+ print("[9] 确定性(同输入同输出)")
388
+ src = load("calculator.ai")
389
+ a = parse_source(src, "ai")
390
+ b = parse_source(src, "ai")
391
+ check(a.ok and b.ok, "两次都成功")
392
+ check(a.module.dump() == b.module.dump(), "AST 转储逐字节相同")
393
+ da = [d.to_dict() for d in a.diagnostics]
394
+ db = [d.to_dict() for d in b.diagnostics]
395
+ check(da == db, "诊断相同")
396
+
397
+
398
+ # ---------------------------------------------------------------- main
399
+
400
+ def main():
401
+ test_samples()
402
+ test_spec_clauses()
403
+ test_types()
404
+ test_enum()
405
+ test_statements()
406
+ test_char_literals()
407
+ test_choose_kernel_dispatch()
408
+ test_errors()
409
+ test_determinism()
410
+
411
+ print()
412
+ total = _passed + len(_failed)
413
+ if _failed:
414
+ print(f"失败 {len(_failed)} / {total}")
415
+ for f in _failed:
416
+ print(" - " + f)
417
+ return 1
418
+ print(f"全部通过:{_passed} 项")
419
+ return 0
420
+
421
+
422
+ if __name__ == "__main__":
423
+ sys.exit(main())