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.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- 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
|
+
}
|