arxo-codegen 0.4.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.
Files changed (152) hide show
  1. arxo_codegen/__init__.py +5 -0
  2. arxo_codegen/__main__.py +7 -0
  3. arxo_codegen/active-outcomes.json +6837 -0
  4. arxo_codegen/check.py +158 -0
  5. arxo_codegen/check_active_canaries.py +262 -0
  6. arxo_codegen/check_api.py +76 -0
  7. arxo_codegen/check_conformance.py +414 -0
  8. arxo_codegen/check_matrix.py +131 -0
  9. arxo_codegen/check_packages.py +281 -0
  10. arxo_codegen/check_semantic_probes.py +293 -0
  11. arxo_codegen/check_xlsx.py +320 -0
  12. arxo_codegen/consumer_runner.py +316 -0
  13. arxo_codegen/core_support.py +203 -0
  14. arxo_codegen/coverage.py +341 -0
  15. arxo_codegen/deliver.py +180 -0
  16. arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
  17. arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
  18. arxo_codegen/excel_plan.py +602 -0
  19. arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
  20. arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
  21. arxo_codegen/inert-nodes.json +62 -0
  22. arxo_codegen/inert.py +91 -0
  23. arxo_codegen/inputs.py +59 -0
  24. arxo_codegen/package.py +565 -0
  25. arxo_codegen/plan.py +4053 -0
  26. arxo_codegen/recertify_registry.py +100 -0
  27. arxo_codegen/runtime-cert.json +5259 -0
  28. arxo_codegen/runtime_cert.py +233 -0
  29. arxo_codegen/survey.py +272 -0
  30. arxo_codegen/target_runner.py +101 -0
  31. arxo_codegen/targets/bsl/cert.bsl +113 -0
  32. arxo_codegen/targets/bsl/emit.py +356 -0
  33. arxo_codegen/targets/bsl/json.bsl +243 -0
  34. arxo_codegen/targets/bsl/runner.bsl +210 -0
  35. arxo_codegen/targets/bsl/runtime.bsl +858 -0
  36. arxo_codegen/targets/cs/Cert.cs +90 -0
  37. arxo_codegen/targets/cs/Runner.cs +154 -0
  38. arxo_codegen/targets/cs/Runtime.cs +243 -0
  39. arxo_codegen/targets/cs/emit.py +223 -0
  40. arxo_codegen/targets/dart/cert.dart +99 -0
  41. arxo_codegen/targets/dart/emit.py +206 -0
  42. arxo_codegen/targets/dart/json.dart +166 -0
  43. arxo_codegen/targets/dart/runner.dart +154 -0
  44. arxo_codegen/targets/dart/runtime.dart +293 -0
  45. arxo_codegen/targets/erl/cert.erl +64 -0
  46. arxo_codegen/targets/erl/emit.py +216 -0
  47. arxo_codegen/targets/erl/rt.erl +238 -0
  48. arxo_codegen/targets/erl/runner.erl +130 -0
  49. arxo_codegen/targets/ex/cert.exs +62 -0
  50. arxo_codegen/targets/ex/emit.py +204 -0
  51. arxo_codegen/targets/ex/runner.exs +124 -0
  52. arxo_codegen/targets/ex/runtime.ex +227 -0
  53. arxo_codegen/targets/go/apiBase.go +483 -0
  54. arxo_codegen/targets/go/apicheck.go +97 -0
  55. arxo_codegen/targets/go/cert.go +218 -0
  56. arxo_codegen/targets/go/emit.py +1806 -0
  57. arxo_codegen/targets/go/evaluate.go +1826 -0
  58. arxo_codegen/targets/go/runner.go +38 -0
  59. arxo_codegen/targets/go/runtime.go +2612 -0
  60. arxo_codegen/targets/java/Cert.java +91 -0
  61. arxo_codegen/targets/java/Json.java +148 -0
  62. arxo_codegen/targets/java/Runner.java +151 -0
  63. arxo_codegen/targets/java/Runtime.java +243 -0
  64. arxo_codegen/targets/java/emit.py +198 -0
  65. arxo_codegen/targets/kt/Cert.kt +67 -0
  66. arxo_codegen/targets/kt/Json.kt +103 -0
  67. arxo_codegen/targets/kt/Runner.kt +126 -0
  68. arxo_codegen/targets/kt/Runtime.kt +196 -0
  69. arxo_codegen/targets/kt/emit.py +195 -0
  70. arxo_codegen/targets/lua/cert.lua +82 -0
  71. arxo_codegen/targets/lua/emit.py +250 -0
  72. arxo_codegen/targets/lua/json.lua +133 -0
  73. arxo_codegen/targets/lua/runner.lua +167 -0
  74. arxo_codegen/targets/lua/runtime.lua +541 -0
  75. arxo_codegen/targets/php/cert.php +84 -0
  76. arxo_codegen/targets/php/emit.py +229 -0
  77. arxo_codegen/targets/php/json.php +144 -0
  78. arxo_codegen/targets/php/runner.php +189 -0
  79. arxo_codegen/targets/php/runtime.php +660 -0
  80. arxo_codegen/targets/py/api_base.py +334 -0
  81. arxo_codegen/targets/py/apicheck.py +51 -0
  82. arxo_codegen/targets/py/cert.py +140 -0
  83. arxo_codegen/targets/py/emit.py +1140 -0
  84. arxo_codegen/targets/py/evaluate.py +980 -0
  85. arxo_codegen/targets/py/runner.py +38 -0
  86. arxo_codegen/targets/py/runtime.py +1634 -0
  87. arxo_codegen/targets/rb/cert.rb +73 -0
  88. arxo_codegen/targets/rb/emit.py +203 -0
  89. arxo_codegen/targets/rb/runner.rb +130 -0
  90. arxo_codegen/targets/rb/runtime.rb +255 -0
  91. arxo_codegen/targets/rs/Cargo.lock +142 -0
  92. arxo_codegen/targets/rs/Cargo.toml +16 -0
  93. arxo_codegen/targets/rs/cert.rs +106 -0
  94. arxo_codegen/targets/rs/emit.py +233 -0
  95. arxo_codegen/targets/rs/prefetch.rs +1 -0
  96. arxo_codegen/targets/rs/runner.rs +181 -0
  97. arxo_codegen/targets/rs/runtime.rs +448 -0
  98. arxo_codegen/targets/scala/Cert.scala +77 -0
  99. arxo_codegen/targets/scala/Json.scala +140 -0
  100. arxo_codegen/targets/scala/Runner.scala +121 -0
  101. arxo_codegen/targets/scala/Runtime.scala +269 -0
  102. arxo_codegen/targets/scala/emit.py +193 -0
  103. arxo_codegen/targets/swift/cert.swift +105 -0
  104. arxo_codegen/targets/swift/emit.py +231 -0
  105. arxo_codegen/targets/swift/json.swift +208 -0
  106. arxo_codegen/targets/swift/runner.swift +133 -0
  107. arxo_codegen/targets/swift/runtime.swift +528 -0
  108. arxo_codegen/targets/ts/apiBase.ts +250 -0
  109. arxo_codegen/targets/ts/apicheck.ts +37 -0
  110. arxo_codegen/targets/ts/cert.ts +96 -0
  111. arxo_codegen/targets/ts/emit.py +1214 -0
  112. arxo_codegen/targets/ts/evaluate.ts +894 -0
  113. arxo_codegen/targets/ts/package-lock.json +45 -0
  114. arxo_codegen/targets/ts/package.json +9 -0
  115. arxo_codegen/targets/ts/runner.ts +31 -0
  116. arxo_codegen/targets/ts/runtime.ts +1369 -0
  117. arxo_codegen/targets/xlsx/README.ru.md +176 -0
  118. arxo_codegen/targets/xlsx/emit.py +198 -0
  119. arxo_codegen/temporal.py +59 -0
  120. arxo_codegen/test_alias_types.py +171 -0
  121. arxo_codegen/test_api_contract.py +89 -0
  122. arxo_codegen/test_calendar_policy.py +48 -0
  123. arxo_codegen/test_check.py +49 -0
  124. arxo_codegen/test_check_active_canaries.py +96 -0
  125. arxo_codegen/test_check_conformance.py +132 -0
  126. arxo_codegen/test_check_semantic_probes.py +85 -0
  127. arxo_codegen/test_check_xlsx.py +52 -0
  128. arxo_codegen/test_consumer_runner.py +175 -0
  129. arxo_codegen/test_core_support.py +80 -0
  130. arxo_codegen/test_e0212_decision_time.py +63 -0
  131. arxo_codegen/test_head_aggregate.py +71 -0
  132. arxo_codegen/test_inert.py +143 -0
  133. arxo_codegen/test_instant_types.py +113 -0
  134. arxo_codegen/test_key_conflict_scope.py +92 -0
  135. arxo_codegen/test_key_conflict_targets.py +17 -0
  136. arxo_codegen/test_package_layout.py +163 -0
  137. arxo_codegen/test_plan_language_version.py +44 -0
  138. arxo_codegen/test_positions_refusal.py +43 -0
  139. arxo_codegen/test_recertify_registry.py +77 -0
  140. arxo_codegen/test_ruby_target.py +41 -0
  141. arxo_codegen/test_runtime_cert.py +83 -0
  142. arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
  143. arxo_codegen/test_stage_r.py +108 -0
  144. arxo_codegen/test_standalone_inputs.py +71 -0
  145. arxo_codegen/test_support.py +150 -0
  146. arxo_codegen/test_survey_inputs.py +115 -0
  147. arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
  148. arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
  149. arxo_codegen-0.4.0.dist-info/RECORD +152 -0
  150. arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
  151. arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
  152. arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env python3
2
+ """Эмиттер Java. Модель — класс `Model` со статическими таблицами и правилами."""
3
+ import json
4
+ import shutil
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ SUPPORTED_PLAN = "law-backend-plan/0.7"
9
+
10
+
11
+ def check_plan(plan: dict) -> None:
12
+ v = plan.get("planVersion")
13
+ if v != SUPPORTED_PLAN:
14
+ raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержана {SUPPORTED_PLAN})")
15
+
16
+
17
+ def jstr(s: str) -> str:
18
+ return json.dumps(s, ensure_ascii=False)
19
+
20
+
21
+ def key_readers(plan: dict) -> dict[str, list[str]]:
22
+ readers: dict[str, set[str]] = {}
23
+ for rule in plan["rules"]:
24
+ for atom in rule.get("atoms") or []:
25
+ readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
26
+ for check in rule.get("checks") or []:
27
+ readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
28
+ return {source: sorted(consumers) for source, consumers in sorted(readers.items())}
29
+
30
+
31
+ def term(t: dict) -> str:
32
+ k = t["k"]
33
+ if k == "var":
34
+ return f'r{t["row"]}.get({t["col"]})'
35
+ if k == "lit":
36
+ if t["t"] == "money":
37
+ return f'Runtime.money({jstr(t["v"])}, {jstr(t["cur"])})'
38
+ if t["t"] == "dec":
39
+ return f'Runtime.dec({jstr(t["v"])})'
40
+ if t["t"] == "date":
41
+ return f'Runtime.date({jstr(t["v"])})'
42
+ if t["t"] == "enum":
43
+ return f'Runtime.sym({jstr(t["v"])})'
44
+ return jstr(t["v"])
45
+ if k == "bin":
46
+ return f'Runtime.{t["op"]}({term(t["l"])}, {term(t["r"])})'
47
+ if k == "call":
48
+ args = t["args"]
49
+ if t["fn"] == "round":
50
+ return f'Runtime.round({term(args[0])}, {term(args[1])}, {jstr(args[2]["v"])})'
51
+ return f'Runtime.{t["fn"]}(' + ", ".join(term(a) for a in args) + ')'
52
+ raise AssertionError(k)
53
+
54
+
55
+ def emit(plan: dict) -> str:
56
+ check_plan(plan)
57
+ L = ["import java.util.ArrayList;", "import java.util.HashSet;", "import java.util.LinkedHashMap;",
58
+ "import java.util.List;", "import java.util.Map;", "import java.util.Set;", "",
59
+ "public final class Model {",
60
+ " public static final Map<String, List<List<Object>>> TABLES = new LinkedHashMap<>();",
61
+ " // Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).",
62
+ " public static final Map<String, String[]> PARAMS = new LinkedHashMap<>();",
63
+ " // Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.",
64
+ " public static final Map<String, Object[]> CONSTS = new LinkedHashMap<>();",
65
+ " // Наследование сущностей: подтип подходит параметру базового типа.",
66
+ " public static final Map<String, String> BASES = new LinkedHashMap<>();",
67
+ " // Ключи отношений §45.1: позиции столбцов каждого ключа.",
68
+ " public static final Map<String, int[][]> KEYS = new LinkedHashMap<>();",
69
+ " public static final Map<String, String[]> KEY_READERS = new LinkedHashMap<>();",
70
+ " private static final Set<String> SEEN = new HashSet<>();",
71
+ "",
72
+ " static {"]
73
+ for t in plan["tables"]:
74
+ L.append(f' TABLES.put({jstr(t["pred"])}, new ArrayList<>());')
75
+ L.append(f' PARAMS.put({jstr(t["pred"])}, new String[]{{' + ", ".join(jstr(p) for p in t["params"]) + "});")
76
+ for t in plan["tables"]:
77
+ if t["keys"]:
78
+ L.append(f' KEYS.put({jstr(t["pred"])}, new int[][]{{' + ", ".join("{" + ", ".join(str(i) for i in k) + "}" for k in t["keys"]) + "});")
79
+ for source, consumers in key_readers(plan).items():
80
+ L.append(f' KEY_READERS.put({jstr(source)}, new String[]{{' + ", ".join(jstr(value) for value in consumers) + "});")
81
+ for cid, c in plan["consts"].items():
82
+ L.append(f' CONSTS.put({jstr(cid)}, new Object[]{{{jstr(c["type"])}, {term(c["term"])}}});')
83
+ for sub, base in plan["bases"].items():
84
+ L.append(f' BASES.put({jstr(sub)}, {jstr(base)});')
85
+ L.append(" }")
86
+ L.append("")
87
+ L.append(" // KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
88
+ L.append(" static int keyConflicts() {")
89
+ L.append(" int n = 0;")
90
+ L.append(" for (Map.Entry<String, int[][]> e : KEYS.entrySet()) {")
91
+ L.append(" for (int[] key : e.getValue()) {")
92
+ L.append(" Map<String, Integer> groups = new LinkedHashMap<>();")
93
+ L.append(" for (List<Object> row : TABLES.get(e.getKey())) {")
94
+ L.append(" List<Object> proj = new ArrayList<>();")
95
+ L.append(" for (int i : key) proj.add(row.get(i));")
96
+ L.append(" groups.merge(Runtime.key(proj), 1, Integer::sum);")
97
+ L.append(" }")
98
+ L.append(" for (int c : groups.values()) if (c > 1) n++;")
99
+ L.append(" }")
100
+ L.append(" }")
101
+ L.append(" return n;")
102
+ L.append(" }")
103
+ L.append(" static Set<String> keyConflictPredicates() {")
104
+ L.append(" Set<String> out = new HashSet<>();")
105
+ L.append(" for (Map.Entry<String, int[][]> e : KEYS.entrySet()) for (int[] key : e.getValue()) { Map<String, Integer> groups = new LinkedHashMap<>(); for (List<Object> row : TABLES.get(e.getKey())) { List<Object> proj = new ArrayList<>(); for (int i : key) proj.add(row.get(i)); groups.merge(Runtime.key(proj), 1, Integer::sum); } if (groups.values().stream().anyMatch(c -> c > 1)) out.add(e.getKey()); }")
106
+ L.append(" return out;")
107
+ L.append(" }")
108
+ L.append("")
109
+ L.append(" static boolean addRow(String urn, List<Object> row) {")
110
+ L.append(' String k = urn + "#" + Runtime.key(row);')
111
+ L.append(" if (!SEEN.add(k)) return false;")
112
+ L.append(" TABLES.get(urn).add(row);")
113
+ L.append(" return true;")
114
+ L.append(" }")
115
+ L.append("")
116
+ for r in plan["rules"]:
117
+ L.append(f' // {r["id"]}')
118
+ L.append(f' static boolean rule_{r["name"]}() {{')
119
+ L.append(" boolean grew = false;")
120
+ if r["window"]:
121
+ if r["window"].get("start"):
122
+ L.append(f' if (Runtime.legalTime().compareTo({jstr(r["window"]["start"])}) < 0) return false;')
123
+ if r["window"].get("end"):
124
+ L.append(f' if (Runtime.legalTime().compareTo({jstr(r["window"]["end"])}) >= 0) return false;')
125
+ ind = " "
126
+ for i, a in enumerate(r["atoms"]):
127
+ L.append(f'{ind}for (List<Object> r{i} : new ArrayList<>(TABLES.get({jstr(a["pred"])}))) {{')
128
+ ind += " "
129
+ for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
130
+ L.append(f'{ind}if (!Runtime.cmp(r{ai}.get({aj}), "eq", r{bi}.get({bj}))) continue;')
131
+ for f in r["filters"]:
132
+ L.append(f'{ind}if (!Runtime.eq(r{f["row"]}.get({f["col"]}), {term(f["term"])})) continue;')
133
+ for g in r["guards"]:
134
+ L.append(f'{ind}if (!Runtime.cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])})) continue;')
135
+ args = ", ".join(term(a) for a in r["head"]["args"])
136
+ L.append(f'{ind}if (addRow({jstr(r["head"]["pred"])}, Runtime.row({args}))) grew = true;')
137
+ for _ in r["atoms"]:
138
+ ind = ind[:-4]
139
+ L.append(ind + "}")
140
+ L.append(" return grew;")
141
+ L.append(" }")
142
+ L.append("")
143
+ names = [r["name"] for r in plan["rules"]]
144
+ L.append(" static void saturate() {")
145
+ L.append(" boolean grew = true;")
146
+ L.append(" while (grew) {")
147
+ L.append(" grew = false;")
148
+ for n in names:
149
+ L.append(f" if (rule_{n}()) grew = true;")
150
+ L.append(" }")
151
+ L.append(" }")
152
+ L.append("")
153
+ L.append(" static void register(String urn, List<Object> row) { addRow(urn, row); }")
154
+ L.append("")
155
+ L.append(" static void packageFacts() {")
156
+ for f in plan["facts"]:
157
+ L.append(f' addRow({jstr(f["pred"])}, Runtime.row(' + ", ".join(term(a) for a in f["args"]) + "));")
158
+ L.append(" }")
159
+ L.append("}")
160
+ return "\n".join(L) + "\n"
161
+
162
+
163
+ # --- дескриптор цели ---------------------------------------------------------
164
+ HERE = Path(__file__).resolve().parent
165
+ EXT = "java"
166
+ MODEL = "Model.java"
167
+ MAINS = {"runner": "Runner.java", "cert": "Cert.java"}
168
+
169
+
170
+ def available():
171
+ javac, java = shutil.which("javac"), shutil.which("java")
172
+ return (True, javac) if javac and java else (False, "нет javac/java в PATH")
173
+
174
+
175
+ def layout(proj, main: str) -> list:
176
+ proj = Path(proj)
177
+ proj.mkdir(parents=True, exist_ok=True)
178
+ out = []
179
+ for name in ("Runtime.java", "Json.java", MAINS[main]):
180
+ shutil.copyfile(HERE / name, proj / name)
181
+ out.append(proj / name)
182
+ return out
183
+
184
+
185
+ def build(proj):
186
+ proj = Path(proj)
187
+ return ["javac", "-encoding", "UTF-8", "-d", "out"] + sorted(p.name for p in proj.glob("*.java")), str(proj)
188
+
189
+
190
+ def run(proj, main: str):
191
+ return ["java", "-Dfile.encoding=UTF-8", "-cp", str(Path(proj) / "out"), MAINS[main][:-5]], None
192
+
193
+
194
+ if __name__ == "__main__":
195
+ sys.path.insert(0, str(HERE.parents[1]))
196
+ import plan as P
197
+ ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
198
+ Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
@@ -0,0 +1,67 @@
1
+ // Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
2
+ // что получилось. Ожидания сверяют ворота, харнесс их не читает.
3
+ import java.io.File
4
+ import java.math.BigInteger
5
+
6
+ object Cert {
7
+ fun lit(x: Any?): Any? {
8
+ if (x is String) return x
9
+ val m = Json.obj(x)
10
+ if (m.containsKey("dec")) return RT.dec(Json.str(m["dec"]))
11
+ if (m.containsKey("money")) return RT.money(Json.str(m["money"]), Json.str(m["cur"]))
12
+ if (m.containsKey("text")) return Json.str(m["text"])
13
+ if (m.containsKey("date")) return RT.date(Json.str(m["date"]))
14
+ if (m.containsKey("sym")) return RT.sym(Json.str(m["sym"]))
15
+ if (m.containsKey("row")) return Json.arr(m["row"]).map { lit(it) }
16
+ throw IllegalArgumentException("литерал фикстуры: $x")
17
+ }
18
+
19
+ // Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
20
+ fun digits(u: BigInteger, s: Int): String {
21
+ val neg = u.signum() < 0
22
+ val body = u.abs().toString().padStart(s + 1, '0')
23
+ val text = if (s == 0) body else body.substring(0, body.length - s) + "." + body.substring(body.length - s)
24
+ return (if (neg) "-" else "") + text
25
+ }
26
+
27
+ fun render(v: Any?): Any? = when (v) {
28
+ null, is Boolean, is String -> v
29
+ is Money -> digits(v.u, v.s) + " " + v.cur
30
+ is Date -> v.v
31
+ is Sym -> v.id
32
+ is Dec -> digits(v.u, v.s)
33
+ else -> throw IllegalStateException("отображение: $v")
34
+ }
35
+
36
+ @Suppress("UNCHECKED_CAST")
37
+ fun run(c: Map<String, Any?>): Any? {
38
+ val a = Json.arr(c["args"]).map { lit(it) }
39
+ return when (Json.str(c["op"])) {
40
+ "dec" -> RT.dec(a[0] as String)
41
+ "money" -> RT.money(a[0] as String, a[1] as String)
42
+ "date" -> RT.date(a[0] as String)
43
+ "sym" -> RT.sym(a[0] as String)
44
+ "add" -> RT.add(a[0], a[1]); "sub" -> RT.sub(a[0], a[1]); "mul" -> RT.mul(a[0], a[1])
45
+ "div" -> RT.div(a[0], a[1]); "mod" -> RT.mod(a[0], a[1]); "min" -> RT.min(a[0], a[1])
46
+ "max" -> RT.max(a[0], a[1]); "abs" -> RT.abs(a[0]); "pow" -> RT.pow(a[0], a[1])
47
+ "round" -> RT.round(a[0], a[1], a[2] as String)
48
+ "cmp" -> RT.cmp(a[0], a[1] as String, a[2])
49
+ "eq" -> RT.eq(a[0], a[1])
50
+ "keyEq" -> RT.key(a[0] as List<Any?>) == RT.key(a[1] as List<Any?>)
51
+ "legalTime" -> RT.legalTime()
52
+ "setLegalTime" -> { RT.setLegalTime(a[0] as String); null }
53
+ else -> throw IllegalArgumentException("неизвестная операция фикстуры: " + c["op"])
54
+ }
55
+ }
56
+ }
57
+
58
+ fun main(argv: Array<String>) {
59
+ val fixture = Json.obj(Json.parse(File(argv[0]).readText()))
60
+ val out = ArrayList<Any?>()
61
+ for (c in Json.arr(fixture["cases"])) {
62
+ val cm = Json.obj(c)
63
+ val got: Any? = try { Cert.render(Cert.run(cm)) } catch (e: Refused) { linkedMapOf("refused" to e.message) }
64
+ out.add(linkedMapOf("id" to cm["id"], "got" to got))
65
+ }
66
+ println(Json.write(out))
67
+ }
@@ -0,0 +1,103 @@
1
+ // Минимальный JSON для раннера и харнесса: у Kotlin/JVM без зависимостей его
2
+ // нет. Числа сохраняются ТЕКСТОМ (Num) — значение Integer дела не проходит
3
+ // через Double. Объекты — LinkedHashMap в порядке документа.
4
+ class Num(val text: String) { override fun toString() = text }
5
+
6
+ class Json private constructor(private val s: String) {
7
+ private var i = 0
8
+
9
+ companion object {
10
+ fun parse(text: String): Any? {
11
+ val p = Json(text)
12
+ val v = p.value()
13
+ p.ws()
14
+ require(p.i == p.s.length) { "JSON: хвост после значения" }
15
+ return v
16
+ }
17
+
18
+ fun quote(t: String): String {
19
+ val b = StringBuilder("\"")
20
+ for (c in t) when (c) {
21
+ '"' -> b.append("\\\""); '\\' -> b.append("\\\\"); '\n' -> b.append("\\n")
22
+ '\r' -> b.append("\\r"); '\t' -> b.append("\\t")
23
+ else -> if (c < ' ') b.append(String.format("\\u%04x", c.code)) else b.append(c)
24
+ }
25
+ return b.append('"').toString()
26
+ }
27
+
28
+ @Suppress("UNCHECKED_CAST")
29
+ fun write(v: Any?): String = when (v) {
30
+ null -> "null"
31
+ is Boolean, is Num -> v.toString()
32
+ is String -> quote(v)
33
+ is List<*> -> v.joinToString(",", "[", "]") { write(it) }
34
+ is Map<*, *> -> (v as Map<String, Any?>).entries.joinToString(",", "{", "}") { quote(it.key) + ":" + write(it.value) }
35
+ else -> throw IllegalArgumentException("JSON: вид ${v::class}")
36
+ }
37
+
38
+ @Suppress("UNCHECKED_CAST")
39
+ fun obj(v: Any?): Map<String, Any?> = v as Map<String, Any?>
40
+ @Suppress("UNCHECKED_CAST")
41
+ fun arr(v: Any?): List<Any?> = (v as? List<Any?>) ?: emptyList()
42
+ fun str(v: Any?): String = v?.toString() ?: ""
43
+ }
44
+
45
+ private fun ws() { while (i < s.length && s[i] in " \t\r\n") i++ }
46
+
47
+ private fun value(): Any? {
48
+ ws()
49
+ val c = s[i]
50
+ if (c == '{') return obj()
51
+ if (c == '[') return arr()
52
+ if (c == '"') return string()
53
+ if (s.startsWith("true", i)) { i += 4; return true }
54
+ if (s.startsWith("false", i)) { i += 5; return false }
55
+ if (s.startsWith("null", i)) { i += 4; return null }
56
+ val start = i
57
+ while (i < s.length && s[i] in "+-0123456789.eE") i++
58
+ require(start != i) { "JSON: неожиданный символ $c" }
59
+ return Num(s.substring(start, i))
60
+ }
61
+
62
+ private fun obj(): Map<String, Any?> {
63
+ val m = LinkedHashMap<String, Any?>()
64
+ i++; ws()
65
+ if (s[i] == '}') { i++; return m }
66
+ while (true) {
67
+ ws(); val k = string(); ws()
68
+ require(s[i] == ':') { "JSON: ожидалось ':'" }; i++
69
+ m[k] = value(); ws()
70
+ val c = s[i++]
71
+ if (c == '}') return m
72
+ require(c == ',') { "JSON: ожидалась ','" }
73
+ }
74
+ }
75
+
76
+ private fun arr(): List<Any?> {
77
+ val a = ArrayList<Any?>()
78
+ i++; ws()
79
+ if (s[i] == ']') { i++; return a }
80
+ while (true) {
81
+ a.add(value()); ws()
82
+ val c = s[i++]
83
+ if (c == ']') return a
84
+ require(c == ',') { "JSON: ожидалась ','" }
85
+ }
86
+ }
87
+
88
+ private fun string(): String {
89
+ require(s[i] == '"') { "JSON: ожидалась строка" }; i++
90
+ val b = StringBuilder()
91
+ while (true) {
92
+ val c = s[i++]
93
+ if (c == '"') return b.toString()
94
+ if (c != '\\') { b.append(c); continue }
95
+ when (val e = s[i++]) {
96
+ '"' -> b.append('"'); '\\' -> b.append('\\'); '/' -> b.append('/'); 'b' -> b.append('\b')
97
+ 'f' -> b.append(' '); 'n' -> b.append('\n'); 'r' -> b.append('\r'); 't' -> b.append('\t')
98
+ 'u' -> { b.append(s.substring(i, i + 4).toInt(16).toChar()); i += 4 }
99
+ else -> throw IllegalArgumentException("JSON: экран \\$e")
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,126 @@
1
+ // Раннер Kotlin-бэкенда: тот же вход и тот же ответ, что у runner.py.
2
+ //
3
+ // Протокол выхода общий для всех целей: ответ — JSON и код 0; названный отказ
4
+ // среза — JSON {"refused": текст} и код 3; любой другой код — крах.
5
+ import java.io.File
6
+ import kotlin.system.exitProcess
7
+
8
+ object Runner {
9
+ val ISSUES = ArrayList<Any?>()
10
+
11
+ fun arg(a: Map<String, Any?>): Any {
12
+ val kind = Json.str(a["kind"])
13
+ if (kind == "entity_ref") return Json.str(a["id"])
14
+ if (kind == "const_ref") {
15
+ // §54: разыменование предшествует исполнению; член перечисления §38 либо
16
+ // имя без объявления остаётся идентичностью, как в ключе атома оракула.
17
+ val c = Model.CONSTS[Json.str(a["id"])]
18
+ return if (c == null) RT.sym(Json.str(a["id"])) else c.second
19
+ }
20
+ if (kind != "value") throw Refused("аргумент вне среза раннера: $kind")
21
+ val t = Json.str(Json.obj(a["type"])["name"])
22
+ val v = Json.str(a["value"])
23
+ return when (t) {
24
+ "urn:law:std#Money" -> RT.money(v, Json.str(a["currency"]))
25
+ "urn:law:std#Decimal", "urn:law:std#Integer" -> RT.dec(v)
26
+ "urn:law:std#Text" -> v
27
+ "urn:law:std#Date" -> RT.date(v)
28
+ else -> throw Refused("аргумент вне среза раннера: $t")
29
+ }
30
+ }
31
+
32
+ // Тип аргумента, если известен: у value — из узла, у const_ref — из объявления
33
+ // константы; entity_ref типа не несёт (как у оракула).
34
+ fun argType(a: Map<String, Any?>): String? = when (Json.str(a["kind"])) {
35
+ "value" -> Json.str(Json.obj(a["type"])["name"])
36
+ "const_ref" -> Model.CONSTS[Json.str(a["id"])]?.first
37
+ else -> null
38
+ }
39
+
40
+ // Совпадение типа с наследованием сущностей вверх от фактического.
41
+ fun conforms(actual: String, expected: String): Boolean {
42
+ val seen = HashSet<String>()
43
+ var cur: String? = actual
44
+ while (cur != null && seen.add(cur)) {
45
+ if (cur == expected) return true
46
+ cur = Model.BASES[cur]
47
+ }
48
+ return false
49
+ }
50
+
51
+ fun args(lit: Map<String, Any?>) = Json.arr(lit["args"]).map { Json.obj(it) }
52
+
53
+ // Арность и типы аргументов утверждения против сигнатуры §189 (E-0046).
54
+ fun wellTyped(lit: Map<String, Any?>): Boolean {
55
+ val params = Model.PARAMS[Json.str(lit["predicate"])] ?: return true
56
+ val a = args(lit)
57
+ if (a.size != params.size) { ISSUES.add("TYPE_ERROR"); return false }
58
+ for (i in params.indices) {
59
+ val actual = argType(a[i])
60
+ if (actual != null && !conforms(actual, params[i])) { ISSUES.add("TYPE_ERROR"); return false }
61
+ }
62
+ return true
63
+ }
64
+
65
+ // Ключ атома у оракула §208 включает ТИП значения: несогласованный запрос
66
+ // атома не находит — NEITHER без issue.
67
+ fun queryTyped(lit: Map<String, Any?>): Boolean {
68
+ val params = Model.PARAMS[Json.str(lit["predicate"])] ?: return false
69
+ val a = args(lit)
70
+ if (a.size != params.size) return false
71
+ return params.indices.all { i -> val t = argType(a[i]); t == null || conforms(t, params[i]) }
72
+ }
73
+
74
+ fun keyConflictBlocks(predicate: String): Boolean {
75
+ val tainted = Model.keyConflictPredicates()
76
+ val waiting = ArrayList(tainted)
77
+ while (waiting.isNotEmpty()) {
78
+ val source = waiting.removeAt(waiting.lastIndex)
79
+ for (consumer in Model.KEY_READERS[source] ?: emptyList()) if (tainted.add(consumer)) waiting.add(consumer)
80
+ }
81
+ return predicate in tainted
82
+ }
83
+
84
+ fun run(casePath: String, queryPath: String) {
85
+ val kase = Json.obj(Json.parse(File(casePath).readText()))
86
+ val query = Json.obj(Json.parse(File(queryPath).readText()))
87
+ val ctx = if (kase["context"] == null) emptyMap() else Json.obj(kase["context"])
88
+ RT.setLegalTime(Json.str(ctx["legalTime"]))
89
+ Model.packageFacts()
90
+ for (a in Json.arr(kase["assertions"])) {
91
+ val lit = Json.obj(Json.obj(a)["literal"])
92
+ if (Json.str(lit["polarity"]) != "positive") throw Refused("§113: отрицательный факт вне L0-среза")
93
+ if (!wellTyped(lit)) continue
94
+ val pred = Json.str(lit["predicate"])
95
+ if (!Model.PARAMS.containsKey(pred)) continue
96
+ Model.register(pred, args(lit).map { arg(it) })
97
+ }
98
+ Model.saturate()
99
+ repeat(Model.keyConflicts()) { ISSUES.add("KEY_CONFLICT") }
100
+ if (Json.str(query["kind"]) != "truth") throw Refused("вид запроса вне среза раннера: " + Json.str(query["kind"]))
101
+ val lit = Json.obj(query["literal"])
102
+ var hit = false
103
+ if (queryTyped(lit)) {
104
+ val want = args(lit).map { arg(it) }
105
+ val rows = Model.TABLES[Json.str(lit["predicate"])] ?: emptyList()
106
+ hit = rows.any { r -> r.size == want.size && r.indices.all { RT.eq(r[it], want[it]) } }
107
+ }
108
+ // Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
109
+ val out = linkedMapOf<String, Any?>(
110
+ "queryId" to query["queryId"],
111
+ "truthStatus" to (if (hit) "TRUE_ONLY" else "NEITHER"),
112
+ "evaluationStatus" to (if (keyConflictBlocks(Json.str(lit["predicate"]))) "NON_EXECUTABLE" else "COMPUTED"),
113
+ "issues" to ISSUES,
114
+ )
115
+ println(Json.write(out))
116
+ }
117
+ }
118
+
119
+ fun main(argv: Array<String>) {
120
+ try {
121
+ Runner.run(argv[0], argv[1])
122
+ } catch (e: Refused) {
123
+ println(Json.write(linkedMapOf("refused" to e.message)))
124
+ exitProcess(3)
125
+ }
126
+ }