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,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
|