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,250 @@
1
+ #!/usr/bin/env python3
2
+ """Эмиттер Lua. Тот же план и тот же контракт рантайма; строки таблиц — массивы
3
+ Lua с индексом от единицы, правила — функции в массиве (у функции Lua не больше
4
+ 200 локальных имён, пакет с сотней правил в локалы бы не влез)."""
5
+ import json
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ # Версия плана, которую этот эмиттер умеет печатать. Другая версия — отказ:
12
+ # незнакомое поле плана печаталось бы молча, а это другой ответ, не меньший.
13
+ SUPPORTED_PLAN = "law-backend-plan/0.7"
14
+
15
+
16
+ def check_plan(plan: dict) -> None:
17
+ v = plan.get("planVersion")
18
+ if v != SUPPORTED_PLAN:
19
+ raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержана {SUPPORTED_PLAN})")
20
+
21
+
22
+ def lua_str(s: str) -> str:
23
+ """Строковый литерал Lua: символы UTF-8 как есть, кавычка, экран и
24
+ управляющие символы — десятичным экраном `\\ddd`. Экраны JSON вида
25
+ `\\uXXXX` Lua не знает, поэтому `json.dumps` здесь не годится."""
26
+ return '"' + "".join(f"\\{ord(c):03d}" if c in '"\\' or ord(c) < 0x20 or ord(c) == 0x7F else c
27
+ for c in s) + '"'
28
+
29
+
30
+ def term(t: dict) -> str:
31
+ k = t["k"]
32
+ if k == "var":
33
+ return f'r{t["row"]}[{t["col"] + 1}]'
34
+ if k == "lit":
35
+ if t["t"] == "money":
36
+ return f'RT.money({lua_str(t["v"])}, {lua_str(t["cur"])})'
37
+ if t["t"] == "dec":
38
+ return f'RT.dec({lua_str(t["v"])})'
39
+ if t["t"] == "date":
40
+ return f'RT.date({lua_str(t["v"])})'
41
+ if t["t"] == "enum":
42
+ return f'RT.sym({lua_str(t["v"])})'
43
+ return lua_str(t["v"])
44
+ if k == "bin":
45
+ return f'RT.{t["op"]}({term(t["l"])}, {term(t["r"])})'
46
+ if k == "call":
47
+ return f'RT.{t["fn"]}(' + ", ".join(term(a) for a in t["args"]) + ')'
48
+ raise AssertionError(k)
49
+
50
+
51
+ def emit(plan: dict) -> str:
52
+ check_plan(plan)
53
+ L = ['local RT = require("runtime")', "", "local M = {}", "M.TABLES = {"]
54
+ for t in plan["tables"]:
55
+ L.append(f' [{lua_str(t["pred"])}] = {{}},')
56
+ L.append("}")
57
+ L.append("-- Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
58
+ L.append("M.PARAMS = {")
59
+ for t in plan["tables"]:
60
+ L.append(f' [{lua_str(t["pred"])}] = {{' + ", ".join(lua_str(p) for p in t["params"]) + "},")
61
+ L.append("}")
62
+ L.append("-- Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
63
+ L.append("M.CONSTS = {")
64
+ for cid, c in plan["consts"].items():
65
+ L.append(f' [{lua_str(cid)}] = {{{lua_str(c["type"])}, {term(c["term"])}}},')
66
+ L.append("}")
67
+ L.append("-- Наследование сущностей: подтип подходит параметру базового типа.")
68
+ L.append("M.BASES = {")
69
+ for sub, base in plan["bases"].items():
70
+ L.append(f" [{lua_str(sub)}] = {lua_str(base)},")
71
+ L.append("}")
72
+ L.append("-- Ключи отношений §45.1: позиции столбцов каждого ключа (от единицы).")
73
+ L.append("M.KEYS = {")
74
+ for t in plan["tables"]:
75
+ if t["keys"]:
76
+ keys = ", ".join("{" + ", ".join(str(i + 1) for i in k) + "}" for k in t["keys"])
77
+ L.append(f' {{{lua_str(t["pred"])}, {{{keys}}}}},')
78
+ L.append("}")
79
+ L.append("local seen = {}")
80
+ L.append("")
81
+ L.append("local function add_row(urn, row)")
82
+ L.append(" local s = seen[urn]")
83
+ L.append(" if s == nil then")
84
+ L.append(" s = {}")
85
+ L.append(" seen[urn] = s")
86
+ L.append(" end")
87
+ L.append(" local k = RT.key(row)")
88
+ L.append(" if s[k] then return false end")
89
+ L.append(" s[k] = true")
90
+ L.append(" local t = M.TABLES[urn]")
91
+ L.append(" t[#t + 1] = row")
92
+ L.append(" return true")
93
+ L.append("end")
94
+ L.append("")
95
+ L.append("local RULES = {}")
96
+ for r in plan["rules"]:
97
+ L.append(f'-- {r["id"]}')
98
+ L.append(f'RULES[#RULES + 1] = function() -- {r["name"]}')
99
+ L.append(" local grew = false")
100
+ if r["window"]:
101
+ if r["window"].get("start"):
102
+ L.append(f' if RT.legalTime() < {lua_str(r["window"]["start"])} then return false end')
103
+ if r["window"].get("end"):
104
+ L.append(f' if RT.legalTime() >= {lua_str(r["window"]["end"])} then return false end')
105
+ ind = " "
106
+ for i, a in enumerate(r["atoms"]):
107
+ # Длина берётся один раз на входе в цикл: строки, добавленные этим
108
+ # же проходом, не перебираются — как у копии таблицы в других целях.
109
+ L.append(f'{ind}local t{i} = M.TABLES[{lua_str(a["pred"])}]')
110
+ L.append(f"{ind}for i{i} = 1, #t{i} do")
111
+ ind += " "
112
+ L.append(f"{ind}local r{i} = t{i}[i{i}]")
113
+ conds = []
114
+ for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
115
+ conds.append(f'RT.cmp(r{ai}[{aj + 1}], "eq", r{bi}[{bj + 1}])')
116
+ for f in r["filters"]:
117
+ conds.append(f'RT.eq(r{f["row"]}[{f["col"] + 1}], {term(f["term"])})')
118
+ for g in r["guards"]:
119
+ conds.append(f'RT.cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])})')
120
+ args = ", ".join(term(a) for a in r["head"]["args"])
121
+ add = f'if add_row({lua_str(r["head"]["pred"])}, {{{args}}}) then grew = true end'
122
+ if conds:
123
+ # `and` вычисляется по порядку и обрывается на первом ложном — тот же
124
+ # порядок отсева и тех же отказов, что у `next unless` в Ruby.
125
+ L.append(f"{ind}if " + " and ".join(conds) + " then")
126
+ L.append(f"{ind} {add}")
127
+ L.append(f"{ind}end")
128
+ else:
129
+ L.append(f"{ind}{add}")
130
+ for _ in r["atoms"]:
131
+ ind = ind[:-2]
132
+ L.append(ind + "end")
133
+ L.append(" return grew")
134
+ L.append("end")
135
+ L.append("")
136
+ L.append("function M.saturate()")
137
+ L.append(" local grew = true")
138
+ L.append(" while grew do")
139
+ L.append(" grew = false")
140
+ L.append(" for i = 1, #RULES do")
141
+ L.append(" if RULES[i]() then grew = true end")
142
+ L.append(" end")
143
+ L.append(" end")
144
+ L.append("end")
145
+ L.append("")
146
+ L.append("function M.register(urn, row)")
147
+ L.append(" return add_row(urn, row)")
148
+ L.append("end")
149
+ L.append("")
150
+ L.append("-- KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
151
+ L.append("function M.key_conflicts()")
152
+ L.append(" local n = 0")
153
+ L.append(" for _, entry in ipairs(M.KEYS) do")
154
+ L.append(" local rows = M.TABLES[entry[1]]")
155
+ L.append(" for _, key in ipairs(entry[2]) do")
156
+ L.append(" local groups = {}")
157
+ L.append(" for _, row in ipairs(rows) do")
158
+ L.append(" local proj = {}")
159
+ L.append(" for j, i in ipairs(key) do proj[j] = row[i] end")
160
+ L.append(" local g = RT.key(proj)")
161
+ L.append(" groups[g] = (groups[g] or 0) + 1")
162
+ L.append(" end")
163
+ L.append(" for _, c in pairs(groups) do")
164
+ L.append(" if c > 1 then n = n + 1 end")
165
+ L.append(" end")
166
+ L.append(" end")
167
+ L.append(" end")
168
+ L.append(" return n")
169
+ L.append("end")
170
+ L.append("")
171
+ readers = {}
172
+ for rule in plan["rules"]:
173
+ for atom in rule.get("atoms") or []:
174
+ readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
175
+ for check in rule.get("checks") or []:
176
+ readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
177
+ L.append("M.KEY_READERS = {" + ", ".join(
178
+ f"[{lua_str(key)}] = {{" + ", ".join(lua_str(head) for head in sorted(value)) + "}"
179
+ for key, value in sorted(readers.items())) + "}")
180
+ L.append("function M.key_conflict_predicates()")
181
+ L.append(" local out = {}")
182
+ L.append(" for _, entry in ipairs(M.KEYS) do")
183
+ L.append(" local rows = M.TABLES[entry[1]]")
184
+ L.append(" for _, key in ipairs(entry[2]) do")
185
+ L.append(" local groups = {}; for _, row in ipairs(rows) do local p = {}; for j, i in ipairs(key) do p[j] = row[i] end; local g = RT.key(p); groups[g] = (groups[g] or 0) + 1 end")
186
+ L.append(" for _, c in pairs(groups) do if c > 1 then out[entry[1]] = true end end")
187
+ L.append(" end")
188
+ L.append(" end")
189
+ L.append(" return out")
190
+ L.append("end")
191
+ L.append("")
192
+ for f in plan["facts"]:
193
+ L.append(f'add_row({lua_str(f["pred"])}, {{' + ", ".join(term(a) for a in f["args"]) + "})")
194
+ L.append("")
195
+ L.append("return M")
196
+ return "\n".join(L) + "\n"
197
+
198
+
199
+ # --- дескриптор цели ---------------------------------------------------------
200
+ HERE = Path(__file__).resolve().parent
201
+ EXT = "lua"
202
+ MODEL = "model.lua"
203
+ MAINS = {"runner": "runner.lua", "cert": "cert.lua"}
204
+
205
+
206
+ def _lua():
207
+ """Интерпретатор Lua 5.3+ (целые 64 бита, `//`, `utf8`); LuaJIT называет
208
+ себя 5.1 и не подходит: без целых точной арифметики на конечностях нет."""
209
+ for name in ("lua", "lua5.5", "lua5.4", "lua5.3"):
210
+ path = shutil.which(name)
211
+ if not path:
212
+ continue
213
+ try:
214
+ res = subprocess.run([path, "-e", "print(math.type and _VERSION or '')"],
215
+ capture_output=True, text=True, timeout=30)
216
+ except (OSError, subprocess.TimeoutExpired):
217
+ continue
218
+ if res.returncode == 0 and res.stdout.strip() >= "Lua 5.3":
219
+ return path
220
+ return None
221
+
222
+
223
+ def available():
224
+ lua = _lua()
225
+ return (True, lua) if lua else (False, "нет lua 5.3+ в PATH")
226
+
227
+
228
+ def layout(proj, main: str) -> list:
229
+ proj = Path(proj)
230
+ proj.mkdir(parents=True, exist_ok=True)
231
+ out = []
232
+ for name in ("runtime.lua", "json.lua", MAINS[main]):
233
+ shutil.copyfile(HERE / name, proj / name)
234
+ out.append(proj / name)
235
+ return out
236
+
237
+
238
+ def build(proj):
239
+ return None
240
+
241
+
242
+ def run(proj, main: str):
243
+ return [_lua() or "lua", str(Path(proj) / MAINS[main])], None
244
+
245
+
246
+ if __name__ == "__main__":
247
+ sys.path.insert(0, str(HERE.parents[1]))
248
+ import plan as P
249
+ ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
250
+ Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
@@ -0,0 +1,133 @@
1
+ -- Разбор и печать JSON для Lua: в стандартной библиотеке их нет. Число
2
+ -- остаётся ТЕКСТОМ ({num = запись}) — двоичная плавающая точка не видит ни
3
+ -- одной записи дела; null — отдельное значение JSON.null, а не nil.
4
+ local J = {}
5
+ J.null = setmetatable({}, {__tostring = function() return "null" end})
6
+
7
+ local function fail(s, i, what)
8
+ error("JSON: " .. what .. " в позиции " .. i .. ": " .. s:sub(i, i + 20))
9
+ end
10
+
11
+ local function skip(s, i)
12
+ local _, e = s:find("^[ \t\r\n]*", i)
13
+ return e + 1
14
+ end
15
+
16
+ local parse_value
17
+
18
+ local function parse_string(s, i)
19
+ -- s:sub(i, i) == '"'
20
+ local out = {}
21
+ i = i + 1
22
+ while true do
23
+ local c = s:sub(i, i)
24
+ if c == "" then fail(s, i, "строка не закрыта") end
25
+ if c == '"' then return table.concat(out), i + 1 end
26
+ if c == "\\" then
27
+ local e = s:sub(i + 1, i + 1)
28
+ local simple = {['"'] = '"', ["\\"] = "\\", ["/"] = "/", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t"}
29
+ if simple[e] then
30
+ out[#out + 1] = simple[e]
31
+ i = i + 2
32
+ elseif e == "u" then
33
+ local cp = tonumber(s:sub(i + 2, i + 5), 16)
34
+ if cp == nil then fail(s, i, "плохой \\u") end
35
+ i = i + 6
36
+ if cp >= 0xD800 and cp <= 0xDBFF and s:sub(i, i + 1) == "\\u" then
37
+ local lo = tonumber(s:sub(i + 2, i + 5), 16)
38
+ if lo and lo >= 0xDC00 and lo <= 0xDFFF then
39
+ cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
40
+ i = i + 6
41
+ end
42
+ end
43
+ out[#out + 1] = utf8.char(cp)
44
+ else
45
+ fail(s, i, "плохой экран")
46
+ end
47
+ else
48
+ local j = s:find('["\\]', i)
49
+ if j == nil then fail(s, i, "строка не закрыта") end
50
+ out[#out + 1] = s:sub(i, j - 1)
51
+ i = j
52
+ end
53
+ end
54
+ end
55
+
56
+ function parse_value(s, i)
57
+ i = skip(s, i)
58
+ local c = s:sub(i, i)
59
+ if c == "{" then
60
+ local obj = {}
61
+ i = skip(s, i + 1)
62
+ if s:sub(i, i) == "}" then return obj, i + 1 end
63
+ while true do
64
+ i = skip(s, i)
65
+ if s:sub(i, i) ~= '"' then fail(s, i, "ожидался ключ") end
66
+ local k
67
+ k, i = parse_string(s, i)
68
+ i = skip(s, i)
69
+ if s:sub(i, i) ~= ":" then fail(s, i, "ожидалось ':'") end
70
+ obj[k], i = parse_value(s, i + 1)
71
+ i = skip(s, i)
72
+ local d = s:sub(i, i)
73
+ if d == "}" then return obj, i + 1 end
74
+ if d ~= "," then fail(s, i, "ожидалось ',' или '}'") end
75
+ i = i + 1
76
+ end
77
+ elseif c == "[" then
78
+ local arr = {}
79
+ i = skip(s, i + 1)
80
+ if s:sub(i, i) == "]" then return arr, i + 1 end
81
+ while true do
82
+ arr[#arr + 1], i = parse_value(s, i)
83
+ i = skip(s, i)
84
+ local d = s:sub(i, i)
85
+ if d == "]" then return arr, i + 1 end
86
+ if d ~= "," then fail(s, i, "ожидалось ',' или ']'") end
87
+ i = i + 1
88
+ end
89
+ elseif c == '"' then
90
+ return parse_string(s, i)
91
+ elseif s:sub(i, i + 3) == "true" then
92
+ return true, i + 4
93
+ elseif s:sub(i, i + 4) == "false" then
94
+ return false, i + 5
95
+ elseif s:sub(i, i + 3) == "null" then
96
+ return J.null, i + 4
97
+ end
98
+ local _, e = s:find("^-?%d+%.?%d*[eE]?[-+]?%d*", i)
99
+ if e == nil then fail(s, i, "неизвестное значение") end
100
+ return {num = s:sub(i, e)}, e + 1
101
+ end
102
+
103
+ function J.decode(s)
104
+ local v, i = parse_value(s, 1)
105
+ if skip(s, i) <= #s then fail(s, i, "лишнее после значения") end
106
+ return v
107
+ end
108
+
109
+ -- Текст значения JSON: строка как есть, число — его запись.
110
+ function J.text(v)
111
+ if type(v) == "table" and v.num then return v.num end
112
+ if v == nil or v == J.null then return "" end
113
+ return tostring(v)
114
+ end
115
+
116
+ function J.read(path)
117
+ local f = assert(io.open(path, "rb"))
118
+ local s = f:read("a")
119
+ f:close()
120
+ return J.decode(s)
121
+ end
122
+
123
+ local ESC = {['"'] = '\\"', ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"}
124
+
125
+ -- Строка печатается байтами UTF-8 как есть, экранируются кавычка, экран и
126
+ -- управляющие символы.
127
+ function J.str(s)
128
+ return '"' .. s:gsub('[%c"\\]', function(c)
129
+ return ESC[c] or string.format("\\u%04x", c:byte())
130
+ end) .. '"'
131
+ end
132
+
133
+ return J
@@ -0,0 +1,167 @@
1
+ -- Раннер Lua-бэкенда: тот же вход и тот же ответ, что у runner.rb.
2
+ --
3
+ -- Протокол выхода общий для всех целей: ответ — JSON и код 0; названный отказ
4
+ -- среза — JSON {"refused": текст} и код 3, текст побайтово одинаков между
5
+ -- целями; любая другая ошибка — крах с кодом 1, который ворота валят.
6
+ local dir = arg[0]:match("^(.*)/") or "."
7
+ package.path = dir .. "/?.lua;" .. package.path
8
+ local RT = require("runtime")
9
+ local J = require("json")
10
+ local M = require("model")
11
+
12
+ local ISSUES = {}
13
+
14
+ local function field(t, k)
15
+ if type(t) ~= "table" or t == J.null then return nil end
16
+ local v = t[k]
17
+ if v == J.null then return nil end
18
+ return v
19
+ end
20
+
21
+ local function arg_value(a)
22
+ local kind = field(a, "kind")
23
+ if kind == "entity_ref" then return field(a, "id") end
24
+ if kind == "const_ref" then
25
+ -- §54: разыменование предшествует исполнению; член перечисления §38 либо
26
+ -- имя без объявления остаётся идентичностью, как в ключе атома оракула.
27
+ local c = M.CONSTS[field(a, "id")]
28
+ if c == nil then return RT.sym(J.text(field(a, "id"))) end
29
+ return c[2]
30
+ end
31
+ if kind ~= "value" then RT.refuse("аргумент вне среза раннера: " .. J.text(kind)) end
32
+ local t = field(field(a, "type"), "name")
33
+ local v = J.text(field(a, "value"))
34
+ if t == "urn:law:std#Money" then return RT.money(v, field(a, "currency")) end
35
+ if t == "urn:law:std#Decimal" or t == "urn:law:std#Integer" then return RT.dec(v) end
36
+ if t == "urn:law:std#Text" then return v end
37
+ if t == "urn:law:std#Date" then return RT.date(v) end
38
+ RT.refuse("аргумент вне среза раннера: " .. J.text(t))
39
+ end
40
+
41
+ -- Тип аргумента, если известен: у value — из узла, у const_ref — из объявления
42
+ -- константы; entity_ref типа не несёт (как у оракула).
43
+ local function arg_type(a)
44
+ if field(a, "kind") == "value" then return field(field(a, "type"), "name") end
45
+ if field(a, "kind") == "const_ref" then
46
+ local c = M.CONSTS[field(a, "id")]
47
+ return c and c[1]
48
+ end
49
+ return nil
50
+ end
51
+
52
+ -- Совпадение типа с наследованием сущностей вверх от фактического.
53
+ local function conforms(actual, expected)
54
+ local seen = {}
55
+ local cur = actual
56
+ while cur ~= nil and not seen[cur] do
57
+ if cur == expected then return true end
58
+ seen[cur] = true
59
+ cur = M.BASES[cur]
60
+ end
61
+ return false
62
+ end
63
+
64
+ -- Арность и типы аргументов утверждения против сигнатуры §189 (E-0046).
65
+ local function well_typed(lit)
66
+ local params = M.PARAMS[lit.predicate]
67
+ if params == nil then return true end
68
+ local args = field(lit, "args") or {}
69
+ if #args ~= #params then
70
+ ISSUES[#ISSUES + 1] = "TYPE_ERROR"
71
+ return false
72
+ end
73
+ for i, expected in ipairs(params) do
74
+ local actual = arg_type(args[i])
75
+ if actual ~= nil and not conforms(actual, expected) then
76
+ ISSUES[#ISSUES + 1] = "TYPE_ERROR"
77
+ return false
78
+ end
79
+ end
80
+ return true
81
+ end
82
+
83
+ -- Ключ атома у оракула §208 включает ТИП значения: несогласованный запрос
84
+ -- атома не находит — NEITHER без issue.
85
+ local function query_typed(lit)
86
+ local params = M.PARAMS[lit.predicate]
87
+ if params == nil then return false end
88
+ local args = field(lit, "args") or {}
89
+ if #args ~= #params then return false end
90
+ for i, expected in ipairs(params) do
91
+ local actual = arg_type(args[i])
92
+ if actual ~= nil and not conforms(actual, expected) then return false end
93
+ end
94
+ return true
95
+ end
96
+
97
+ local function key_conflict_blocks(predicate)
98
+ local tainted, waiting = M.key_conflict_predicates(), {}
99
+ for pred, _ in pairs(tainted) do waiting[#waiting + 1] = pred end
100
+ local at = 1
101
+ while at <= #waiting do
102
+ local source = waiting[at]; at = at + 1
103
+ for _, consumer in ipairs(M.KEY_READERS[source] or {}) do
104
+ if not tainted[consumer] then tainted[consumer] = true; waiting[#waiting + 1] = consumer end
105
+ end
106
+ end
107
+ return tainted[predicate] == true
108
+ end
109
+
110
+ local function main()
111
+ local case = J.read(arg[1])
112
+ local query = J.read(arg[2])
113
+ RT.setLegalTime(J.text(field(field(case, "context"), "legalTime")))
114
+ for _, a in ipairs(case.assertions) do
115
+ local lit = a.literal
116
+ if field(lit, "polarity") ~= "positive" then RT.refuse("§113: отрицательный факт вне L0-среза") end
117
+ if well_typed(lit) and M.PARAMS[lit.predicate] ~= nil then
118
+ local row = {}
119
+ for i, x in ipairs(lit.args) do row[i] = arg_value(x) end
120
+ M.register(lit.predicate, row)
121
+ end
122
+ end
123
+ M.saturate()
124
+ for _ = 1, M.key_conflicts() do ISSUES[#ISSUES + 1] = "KEY_CONFLICT" end
125
+ if field(query, "kind") ~= "truth" then
126
+ RT.refuse("вид запроса вне среза раннера: " .. J.text(field(query, "kind")))
127
+ end
128
+ local hit = false
129
+ if query_typed(query.literal) then
130
+ local want = {}
131
+ for i, x in ipairs(query.literal.args) do want[i] = arg_value(x) end
132
+ for _, r in ipairs(M.TABLES[query.literal.predicate] or {}) do
133
+ if #r == #want then
134
+ local all = true
135
+ for i = 1, #r do
136
+ if not RT.eq(r[i], want[i]) then
137
+ all = false
138
+ break
139
+ end
140
+ end
141
+ if all then
142
+ hit = true
143
+ break
144
+ end
145
+ end
146
+ end
147
+ end
148
+ local issues = {}
149
+ for i, c in ipairs(ISSUES) do issues[i] = J.str(c) end
150
+ -- Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
151
+ print('{"queryId":' .. J.str(J.text(field(query, "queryId"))) ..
152
+ ',"truthStatus":' .. J.str(hit and "TRUE_ONLY" or "NEITHER") ..
153
+ ',"evaluationStatus":' .. J.str(key_conflict_blocks(query.literal.predicate) and "NON_EXECUTABLE" or "COMPUTED") .. ',"issues":[' .. table.concat(issues, ",") .. "]}")
154
+ end
155
+
156
+ local ok, err = xpcall(main, function(e)
157
+ if RT.is_refused(e) then return e end
158
+ return debug.traceback(tostring(e), 2)
159
+ end)
160
+ if not ok then
161
+ if RT.is_refused(err) then
162
+ print('{"refused":' .. J.str(err.msg) .. "}")
163
+ os.exit(3)
164
+ end
165
+ io.stderr:write("error: " .. tostring(err) .. "\n")
166
+ os.exit(1)
167
+ end