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,73 @@
1
+ #!/usr/bin/env ruby
2
+ # Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
3
+ # что получилось. Ожидания сверяют ворота, харнесс их не читает.
4
+ require "json"
5
+ require_relative "runtime"
6
+
7
+ def lit(x)
8
+ return x if x.is_a?(String)
9
+ return RT.dec(x["dec"]) if x.key?("dec")
10
+ return RT.money(x["money"], x["cur"]) if x.key?("money")
11
+ return x["text"] if x.key?("text")
12
+ return RT.date(x["date"]) if x.key?("date")
13
+ return RT.sym(x["sym"]) if x.key?("sym")
14
+ return x["row"].map { |v| lit(v) } if x.key?("row")
15
+ raise "литерал фикстуры: #{x}"
16
+ end
17
+
18
+ # Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
19
+ def digits(u, s)
20
+ neg = u.negative?
21
+ body = u.abs.to_s.rjust(s + 1, "0")
22
+ text = s.zero? ? body : body[0...-s] + "." + body[-s..]
23
+ (neg ? "-" : "") + text
24
+ end
25
+
26
+ def render(v)
27
+ case v
28
+ when nil, true, false, String then v
29
+ when RT::Money then digits(v.u, v.s) + " " + v.cur
30
+ when RT::Date then v.v
31
+ when RT::Sym then v.id
32
+ else digits(v.u, v.s)
33
+ end
34
+ end
35
+
36
+ def run(c)
37
+ a = c["args"].map { |x| lit(x) }
38
+ case c["op"]
39
+ when "dec" then RT.dec(a[0])
40
+ when "money" then RT.money(a[0], a[1])
41
+ when "date" then RT.date(a[0])
42
+ when "sym" then RT.sym(a[0])
43
+ when "mul" then RT.mul(a[0], a[1])
44
+ when "add" then RT.add(a[0], a[1])
45
+ when "sub" then RT.sub(a[0], a[1])
46
+ when "div" then RT.div(a[0], a[1])
47
+ when "mod" then RT.mod(a[0], a[1])
48
+ when "min" then RT.min(a[0], a[1])
49
+ when "max" then RT.max(a[0], a[1])
50
+ when "abs" then RT.abs(a[0])
51
+ when "pow" then RT.pow(a[0], a[1])
52
+ when "round" then RT.round(a[0], a[1], a[2])
53
+ when "cmp" then RT.cmp(a[0], a[1], a[2])
54
+ when "eq" then RT.eq(a[0], a[1])
55
+ when "keyEq" then RT.key(a[0]) == RT.key(a[1])
56
+ when "legalTime" then RT.legal_time
57
+ when "setLegalTime"
58
+ RT.set_legal_time(a[0])
59
+ nil
60
+ else raise "неизвестная операция фикстуры: #{c['op']}"
61
+ end
62
+ end
63
+
64
+ fixture = JSON.parse(File.read(ARGV[0], encoding: "utf-8"))
65
+ out = fixture["cases"].map do |c|
66
+ got = begin
67
+ render(run(c))
68
+ rescue RT::Refused => e
69
+ {"refused" => e.message}
70
+ end
71
+ {"id" => c["id"], "got" => got}
72
+ end
73
+ puts JSON.generate(out)
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """Эмиттер Ruby. Тот же план, другой синтаксис и тот же контракт рантайма."""
3
+ import json
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ # Версия плана, которую этот эмиттер умеет печатать. Другая версия — отказ:
11
+ # незнакомое поле плана печаталось бы молча, а это другой ответ, не меньший.
12
+ SUPPORTED_PLAN = "law-backend-plan/0.7"
13
+
14
+
15
+ def check_plan(plan: dict) -> None:
16
+ v = plan.get("planVersion")
17
+ if v != SUPPORTED_PLAN:
18
+ raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержана {SUPPORTED_PLAN})")
19
+
20
+
21
+ def term(t: dict) -> str:
22
+ k = t["k"]
23
+ if k == "var":
24
+ return f'r{t["row"]}[{t["col"]}]'
25
+ if k == "lit":
26
+ if t["t"] == "money":
27
+ return f'RT.money("{t["v"]}", "{t["cur"]}")'
28
+ if t["t"] == "dec":
29
+ return f'RT.dec("{t["v"]}")'
30
+ if t["t"] == "date":
31
+ return f'RT.date("{t["v"]}")'
32
+ if t["t"] == "enum":
33
+ return f'RT.sym("{t["v"]}")'
34
+ return json.dumps(t["v"], ensure_ascii=False)
35
+ if k == "bin":
36
+ return f'RT.{t["op"]}({term(t["l"])}, {term(t["r"])})'
37
+ if k == "call":
38
+ return f'RT.{t["fn"]}(' + ", ".join(term(a) for a in t["args"]) + ')'
39
+ raise AssertionError(k)
40
+
41
+
42
+ def emit(plan: dict) -> str:
43
+ check_plan(plan)
44
+ L = ['require_relative "runtime"', "", "module Model", " TABLES = {"]
45
+ for t in plan["tables"]:
46
+ L.append(f' {json.dumps(t["pred"])} => [],')
47
+ L.append(" }.freeze")
48
+ readers: dict[str, set[str]] = {}
49
+ for rule in plan["rules"]:
50
+ for atom in rule.get("atoms") or []:
51
+ readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
52
+ for check in rule.get("checks") or []:
53
+ readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
54
+ L.append(" # E-0210/E-0242: forward readers of a keyed relation.")
55
+ L.append(" KEY_READERS = " + repr({key: sorted(value) for key, value in sorted(readers.items())}).replace(": ", " => ") + ".freeze")
56
+ L.append(" # Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
57
+ L.append(" PARAMS = {")
58
+ for t in plan["tables"]:
59
+ L.append(f' {json.dumps(t["pred"])} => {json.dumps(t["params"], ensure_ascii=False)},')
60
+ L.append(" }.freeze")
61
+ L.append(" # Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
62
+ L.append(" CONSTS = {")
63
+ for cid, c in plan["consts"].items():
64
+ L.append(f' {json.dumps(cid)} => [{json.dumps(c["type"])}, {term(c["term"])}],')
65
+ L.append(" }.freeze")
66
+ L.append(" # Наследование сущностей: подтип подходит параметру базового типа.")
67
+ L.append(" BASES = " + json.dumps(plan["bases"], ensure_ascii=False).replace(": ", " => ") + ".freeze")
68
+ L.append(" # Ключи отношений §45.1: позиции столбцов каждого ключа.")
69
+ L.append(" KEYS = {")
70
+ for t in plan["tables"]:
71
+ if t["keys"]:
72
+ L.append(f' {json.dumps(t["pred"])} => {json.dumps(t["keys"])},')
73
+ L.append(" }.freeze")
74
+ L.append(" @seen = {}")
75
+ L.append("")
76
+ L.append(" def self.add_row(urn, row)")
77
+ L.append(" k = [urn, RT.key(row)]")
78
+ L.append(" return false if @seen[k]")
79
+ L.append(" @seen[k] = true")
80
+ L.append(" TABLES[urn] << row")
81
+ L.append(" true")
82
+ L.append(" end")
83
+ L.append("")
84
+ for r in plan["rules"]:
85
+ L.append(f' # {r["id"]}')
86
+ L.append(f' def self.rule_{r["name"]}')
87
+ L.append(" grew = false")
88
+ if r["window"]:
89
+ if r["window"].get("start"):
90
+ L.append(f' return false if RT.legal_time < "{r["window"]["start"]}"')
91
+ if r["window"].get("end"):
92
+ L.append(f' return false if RT.legal_time >= "{r["window"]["end"]}"')
93
+ ind = " "
94
+ for i, a in enumerate(r["atoms"]):
95
+ L.append(f'{ind}TABLES[{json.dumps(a["pred"])}].dup.each do |r{i}|')
96
+ ind += " "
97
+ for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
98
+ L.append(f'{ind}next unless RT.cmp(r{ai}[{aj}], "eq", r{bi}[{bj}])')
99
+ for f in r["filters"]:
100
+ L.append(f'{ind}next unless RT.eq(r{f["row"]}[{f["col"]}], {term(f["term"])})')
101
+ for g in r["guards"]:
102
+ L.append(f'{ind}next unless RT.cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])})')
103
+ args = ", ".join(term(a) for a in r["head"]["args"])
104
+ L.append(f'{ind}grew = true if add_row({json.dumps(r["head"]["pred"])}, [{args}])')
105
+ for _ in r["atoms"]:
106
+ ind = ind[:-2]
107
+ L.append(ind + "end")
108
+ L.append(" grew")
109
+ L.append(" end")
110
+ L.append("")
111
+ names = [r["name"] for r in plan["rules"]]
112
+ L.append(" def self.saturate")
113
+ L.append(" grew = true")
114
+ L.append(" while grew")
115
+ if names:
116
+ L.append(" grew = [" + ", ".join(f"rule_{n}" for n in names) + "].any?")
117
+ else:
118
+ L.append(" grew = false")
119
+ L.append(" end")
120
+ L.append(" end")
121
+ L.append("")
122
+ L.append(" def self.register(urn, row)")
123
+ L.append(" add_row(urn, row)")
124
+ L.append(" end")
125
+ L.append("")
126
+ L.append(" # KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
127
+ L.append(" def self.key_conflicts")
128
+ L.append(" n = 0")
129
+ L.append(" KEYS.each do |urn, keys|")
130
+ L.append(" keys.each do |key|")
131
+ L.append(" groups = Hash.new(0)")
132
+ L.append(" TABLES[urn].each { |row| groups[RT.key(key.map { |i| row[i] })] += 1 }")
133
+ L.append(" n += groups.values.count { |c| c > 1 }")
134
+ L.append(" end")
135
+ L.append(" end")
136
+ L.append(" n")
137
+ L.append(" end")
138
+ L.append("")
139
+ L.append(" def self.key_conflict_predicates")
140
+ L.append(" out = {}")
141
+ L.append(" KEYS.each do |urn, keys|")
142
+ L.append(" keys.each do |key|")
143
+ L.append(" groups = Hash.new(0)")
144
+ L.append(" TABLES[urn].each { |row| groups[RT.key(key.map { |i| row[i] })] += 1 }")
145
+ L.append(" out[urn] = true if groups.values.any? { |c| c > 1 }")
146
+ L.append(" end")
147
+ L.append(" end")
148
+ L.append(" out.keys")
149
+ L.append(" end")
150
+ L.append("")
151
+ for f in plan["facts"]:
152
+ L.append(f' add_row({json.dumps(f["pred"])}, [' + ", ".join(term(a) for a in f["args"]) + '])')
153
+ L.append("end")
154
+ return "\n".join(L) + "\n"
155
+
156
+
157
+ # --- дескриптор цели ---------------------------------------------------------
158
+ HERE = Path(__file__).resolve().parent
159
+ EXT = "rb"
160
+ MODEL = "model.rb"
161
+
162
+
163
+ def available():
164
+ rb = shutil.which("ruby")
165
+ if not rb:
166
+ return False, "нет ruby в PATH"
167
+ try:
168
+ version = subprocess.check_output([rb, "--version"], text=True, stderr=subprocess.STDOUT).strip()
169
+ except (OSError, subprocess.CalledProcessError) as exc:
170
+ return False, f"ruby не запускается: {exc}"
171
+ match = re.search(r"\bruby\s+(\d+)\.(\d+)", version, flags=re.IGNORECASE)
172
+ if match is None:
173
+ return False, f"не прочитана версия ruby: {version[:120]}"
174
+ if tuple(map(int, match.groups())) < (3, 0):
175
+ return False, f"Ruby >= 3.0 нужен для синтаксиса рантайма (найден {match.group(0)})"
176
+ return True, f"{rb} ({match.group(0)})"
177
+
178
+ MAINS = {"runner": "runner.rb", "cert": "cert.rb"}
179
+
180
+
181
+ def layout(proj, main: str) -> list:
182
+ proj = Path(proj)
183
+ proj.mkdir(parents=True, exist_ok=True)
184
+ out = []
185
+ for name in ("runtime.rb", MAINS[main]):
186
+ shutil.copyfile(HERE / name, proj / name)
187
+ out.append(proj / name)
188
+ return out
189
+
190
+
191
+ def build(proj):
192
+ return None
193
+
194
+
195
+ def run(proj, main: str):
196
+ return ["ruby", str(Path(proj) / MAINS[main])], None
197
+
198
+
199
+ if __name__ == "__main__":
200
+ sys.path.insert(0, str(HERE.parents[1]))
201
+ import plan as P
202
+ ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
203
+ Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env ruby
2
+ # Раннер Ruby-бэкенда: тот же вход и тот же ответ, что у runner.py.
3
+ #
4
+ # Протокол выхода общий для всех целей: ответ — JSON и код 0; названный отказ
5
+ # среза — JSON {"refused": текст} и код 3, текст побайтово одинаков между
6
+ # целями; любой другой ненулевой код — крах, который ворота валят.
7
+ require "json"
8
+ require_relative "runtime"
9
+ require_relative "model"
10
+
11
+ ISSUES = []
12
+
13
+ def arg(a)
14
+ kind = a["kind"]
15
+ return a["id"] if kind == "entity_ref"
16
+ if kind == "const_ref"
17
+ # §54: разыменование предшествует исполнению; член перечисления §38 либо
18
+ # имя без объявления остаётся идентичностью, как в ключе атома оракула.
19
+ c = Model::CONSTS[a["id"]]
20
+ return c.nil? ? RT.sym(a["id"].to_s) : c[1]
21
+ end
22
+ raise RT::Refused, "аргумент вне среза раннера: #{kind}" unless kind == "value"
23
+ t = (a["type"] || {})["name"]
24
+ case t
25
+ when "urn:law:std#Money" then RT.money(a["value"].to_s, a["currency"])
26
+ when "urn:law:std#Decimal", "urn:law:std#Integer" then RT.dec(a["value"].to_s)
27
+ when "urn:law:std#Text" then a["value"].to_s
28
+ when "urn:law:std#Date" then RT.date(a["value"].to_s)
29
+ else raise RT::Refused, "аргумент вне среза раннера: #{t}"
30
+ end
31
+ end
32
+
33
+ # Тип аргумента, если известен: у value — из узла, у const_ref — из объявления
34
+ # константы; entity_ref типа не несёт (как у оракула).
35
+ def arg_type(a)
36
+ return (a["type"] || {})["name"] if a["kind"] == "value"
37
+ if a["kind"] == "const_ref"
38
+ c = Model::CONSTS[a["id"]]
39
+ return c.nil? ? nil : c[0]
40
+ end
41
+ nil
42
+ end
43
+
44
+ # Совпадение типа с наследованием сущностей вверх от фактического.
45
+ def conforms(actual, expected)
46
+ seen = {}
47
+ cur = actual
48
+ while !cur.nil? && !seen[cur]
49
+ return true if cur == expected
50
+ seen[cur] = true
51
+ cur = Model::BASES[cur]
52
+ end
53
+ false
54
+ end
55
+
56
+ # Арность и типы аргументов утверждения против сигнатуры §189 (E-0046).
57
+ def well_typed(lit)
58
+ params = Model::PARAMS[lit["predicate"]]
59
+ return true if params.nil?
60
+ args = lit["args"] || []
61
+ if args.length != params.length
62
+ ISSUES << "TYPE_ERROR"
63
+ return false
64
+ end
65
+ params.zip(args).each do |expected, a|
66
+ actual = arg_type(a)
67
+ next if actual.nil? || conforms(actual, expected)
68
+ ISSUES << "TYPE_ERROR"
69
+ return false
70
+ end
71
+ true
72
+ end
73
+
74
+ # Ключ атома у оракула §208 включает ТИП значения: несогласованный запрос
75
+ # атома не находит — NEITHER без issue.
76
+ def query_typed(lit)
77
+ params = Model::PARAMS[lit["predicate"]]
78
+ return false if params.nil?
79
+ args = lit["args"] || []
80
+ return false if args.length != params.length
81
+ params.zip(args).all? { |expected, a| arg_type(a).nil? || conforms(arg_type(a), expected) }
82
+ end
83
+
84
+ def key_conflict_blocks(predicate)
85
+ # E-0210/E-0242: only the forward closure of a conflicted relation blocks.
86
+ tainted = {}
87
+ Model.key_conflict_predicates.each { |source| tainted[source] = true }
88
+ waiting = tainted.keys
89
+ until waiting.empty?
90
+ source = waiting.pop
91
+ (Model::KEY_READERS[source] || []).each do |consumer|
92
+ next if tainted[consumer]
93
+ tainted[consumer] = true
94
+ waiting << consumer
95
+ end
96
+ end
97
+ tainted.key?(predicate)
98
+ end
99
+
100
+ def main
101
+ kase = JSON.parse(File.read(ARGV[0], encoding: "utf-8"))
102
+ query = JSON.parse(File.read(ARGV[1], encoding: "utf-8"))
103
+ RT.set_legal_time(((kase["context"] || {})["legalTime"] || "").to_s)
104
+ kase["assertions"].each do |a|
105
+ lit = a["literal"]
106
+ raise RT::Refused, "§113: отрицательный факт вне L0-среза" unless lit["polarity"] == "positive"
107
+ next unless well_typed(lit)
108
+ next unless Model::PARAMS.key?(lit["predicate"])
109
+ Model.register(lit["predicate"], lit["args"].map { |x| arg(x) })
110
+ end
111
+ Model.saturate
112
+ Model.key_conflicts.times { ISSUES << "KEY_CONFLICT" }
113
+ raise RT::Refused, "вид запроса вне среза раннера: #{query['kind']}" unless query["kind"] == "truth"
114
+ hit = false
115
+ if query_typed(query["literal"])
116
+ want = query["literal"]["args"].map { |x| arg(x) }
117
+ rows = Model::TABLES[query["literal"]["predicate"]] || []
118
+ hit = rows.any? { |r| r.length == want.length && r.each_with_index.all? { |v, i| RT.eq(v, want[i]) } }
119
+ end
120
+ # Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
121
+ puts JSON.generate({"queryId" => query["queryId"], "truthStatus" => hit ? "TRUE_ONLY" : "NEITHER",
122
+ "evaluationStatus" => key_conflict_blocks(query["literal"]["predicate"]) ? "NON_EXECUTABLE" : "COMPUTED", "issues" => ISSUES})
123
+ end
124
+
125
+ begin
126
+ main
127
+ rescue RT::Refused => e
128
+ puts JSON.generate({"refused" => e.message})
129
+ exit 3
130
+ end
@@ -0,0 +1,255 @@
1
+ # Рантайм L0 для Ruby. Тот же контракт, что у runtime.py: точная десятичная
2
+ # арифметика §58 на встроенных целых, деньги с валютой, названный режим
3
+ # округления, календарный день, член перечисления.
4
+ module RT
5
+ # Названный отказ рантайма: то, что срез не считает, а не сбой. Раннер
6
+ # печатает его как JSON с кодом выхода 3; любое другое исключение — крах.
7
+ class Refused < StandardError; end
8
+
9
+ Dec = Struct.new(:u, :s)
10
+ Money = Struct.new(:u, :s, :cur)
11
+ # Календарный день §2.15 в канонической записи ISO YYYY-MM-DD: порядок
12
+ # записи и есть порядок дня. Не String: у ключа атома и у сравнения свой вид.
13
+ Date = Struct.new(:v)
14
+ # Член перечисления §38 либо иная ссылка const_ref без объявления: значение —
15
+ # stable qualified identity, равенство по ней (§57), порядок не определён.
16
+ Sym = Struct.new(:id)
17
+
18
+ module_function
19
+
20
+ def dec(lit)
21
+ lit = lit.to_s
22
+ i, f = lit.split(".", 2)
23
+ f ||= ""
24
+ neg = i.start_with?("-")
25
+ digits = (neg ? i[1..] : i) + f
26
+ Dec.new(Integer((neg ? "-" : "") + digits, 10), f.length)
27
+ end
28
+
29
+ def money(lit, cur)
30
+ d = dec(lit)
31
+ Money.new(d.u, d.s, cur)
32
+ end
33
+
34
+ def date(lit) = Date.new(lit.to_s)
35
+ def sym(id) = Sym.new(id.to_s)
36
+
37
+ def num?(v) = v.is_a?(Dec) || v.is_a?(Money)
38
+ def money?(v) = v.is_a?(Money)
39
+
40
+ # §48/§58: сложение, вычитание, min/max требуют совпадения видов; у денег — и валюты.
41
+ def same!(a, b, op)
42
+ raise Refused, "§58: виды операндов не совпадают: #{op}" unless num?(a) && num?(b) && money?(a) == money?(b)
43
+ raise Refused, "§58: разные валюты: #{op}" if money?(a) && a.cur != b.cur
44
+ end
45
+
46
+ def pair(a, b)
47
+ s = [a.s, b.s].max
48
+ [a.u * 10**(s - a.s), b.u * 10**(s - b.s), s]
49
+ end
50
+
51
+ def make(u, s, like) = money?(like) ? Money.new(u, s, like.cur) : Dec.new(u, s)
52
+
53
+ # Целое значение: дробная часть нулевая (защита в глубину под статикой плана).
54
+ def integral?(v) = v.is_a?(Dec) && (v.u % 10**v.s).zero?
55
+ def to_int(v) = v.u / 10**v.s
56
+
57
+ def add(a, b)
58
+ same!(a, b, "add")
59
+ x, y, s = pair(a, b)
60
+ make(x + y, s, a)
61
+ end
62
+
63
+ def sub(a, b)
64
+ same!(a, b, "sub")
65
+ x, y, s = pair(a, b)
66
+ make(x - y, s, a)
67
+ end
68
+
69
+ # Правило скаляра §49: безразмерное масштабирует размерное; масштаб результата —
70
+ # сумма масштабов. Два размерных не перемножаются.
71
+ def mul(a, b)
72
+ raise Refused, "§58: виды операндов не совпадают: mul" unless num?(a) && num?(b)
73
+ raise Refused, "§58: произведение двух размерных величин не определено" if money?(a) && money?(b)
74
+ make(a.u * b.u, a.s + b.s, money?(a) ? a : b)
75
+ end
76
+
77
+ # Точное десятичное частное num/den при 10-гладком знаменателе (§18), иначе nil.
78
+ def exact(num, den)
79
+ g = num.gcd(den)
80
+ g = 1 if g.zero?
81
+ num /= g
82
+ den /= g
83
+ if den.negative?
84
+ num = -num
85
+ den = -den
86
+ end
87
+ twos = fives = 0
88
+ while (den % 2).zero?
89
+ den /= 2
90
+ twos += 1
91
+ end
92
+ while (den % 5).zero?
93
+ den /= 5
94
+ fives += 1
95
+ end
96
+ return nil unless den == 1
97
+ k = [twos, fives].max
98
+ [num * 2**(k - twos) * 5**(k - fives), k]
99
+ end
100
+
101
+ # Таблица делимых §58 в срезе: Decimal/Decimal, Money/Decimal, Money/Money одной
102
+ # валюты. Integer/Integer даёт Rational — вне среза, отвергается планом статически.
103
+ def div(a, b)
104
+ raise Refused, "§58: деление вне таблицы делимых" unless num?(a) && num?(b)
105
+ raise Refused, "деление на ноль — evaluation error (§58)" if b.u.zero?
106
+ like = nil
107
+ if money?(a) && money?(b)
108
+ raise Refused, "CURRENCY_MISMATCH: §58 делит Money на Money только при тождественной валюте" if a.cur != b.cur
109
+ elsif money?(a)
110
+ like = a
111
+ elsif money?(b)
112
+ raise Refused, "§58: деление вне таблицы делимых"
113
+ end
114
+ q = exact(a.u * 10**b.s, b.u * 10**a.s)
115
+ if q.nil?
116
+ raise Refused, "INEXACT_DIVISION: округление только явной политикой (§50/§18)" unless like.nil?
117
+ raise Refused, "§58: частное не 10-гладкое — Rational вне среза"
118
+ end
119
+ like.nil? ? Dec.new(q[0], q[1]) : Money.new(q[0], q[1], like.cur)
120
+ end
121
+
122
+ # Евклидов остаток §58: 0 <= r < |b| при любых знаках операндов.
123
+ def mod(a, b)
124
+ raise Refused, "§58: mod определён только для Integer" unless integral?(a) && integral?(b)
125
+ y = to_int(b)
126
+ raise Refused, "остаток от деления на ноль — evaluation error (§58)" if y.zero?
127
+ Dec.new(to_int(a) % y.abs, 0)
128
+ end
129
+
130
+ def min(a, b)
131
+ same!(a, b, "min")
132
+ x, y, = pair(a, b)
133
+ x <= y ? a : b
134
+ end
135
+
136
+ def max(a, b)
137
+ same!(a, b, "max")
138
+ x, y, = pair(a, b)
139
+ x >= y ? a : b
140
+ end
141
+
142
+ def abs(v)
143
+ raise Refused, "§58: abs — не числовой вид" unless num?(v)
144
+ make(v.u.abs, v.s, v)
145
+ end
146
+
147
+ POW_EXP_LIMIT = 4096
148
+ POW_MIN = -(2**63)
149
+ POW_MAX = 2**63 - 1
150
+
151
+ # §58 (E-0075): только Integer pow Integer; порядок проверок — знак, предел,
152
+ # вычисление, диапазон — часть контракта.
153
+ def pow(base, exp)
154
+ raise Refused, "§58: pow определён только для Integer" unless integral?(base) && integral?(exp)
155
+ e = to_int(exp)
156
+ raise Refused, "§58: pow — показатель отрицателен" if e.negative?
157
+ raise Refused, "RESOURCE_LIMIT: pow — показатель превышает 4096" if e > POW_EXP_LIMIT
158
+ r = to_int(base)**e
159
+ raise Refused, "RESOURCE_LIMIT: pow — результат вне 64-битного диапазона" if r < POW_MIN || r > POW_MAX
160
+ Dec.new(r, 0)
161
+ end
162
+
163
+ def round(m, digits, mode)
164
+ raise Refused, "§50: режим округления не реализован: #{mode}" unless mode == "HALF_UP"
165
+ raise Refused, "§50: round — не числовой вид" unless num?(m)
166
+ n = digits.is_a?(Integer) ? digits : digits.u / 10**digits.s
167
+ return make(m.u * 10**(n - m.s), n, m) if m.s <= n
168
+ drop = 10**(m.s - n)
169
+ neg = m.u.negative?
170
+ a = m.u.abs
171
+ q, r = a.divmod(drop)
172
+ up = r * 2 >= drop ? q + 1 : q # HALF_UP — от нуля
173
+ make(neg ? -up : up, n, m)
174
+ end
175
+
176
+ ORDER = {"lt" => ->(x, y) { x < y }, "le" => ->(x, y) { x <= y }, "gt" => ->(x, y) { x > y },
177
+ "ge" => ->(x, y) { x >= y }, "eq" => ->(x, y) { x == y }, "ne" => ->(x, y) { x != y }}.freeze
178
+
179
+ def cmp(a, op, b)
180
+ if a.is_a?(Sym) || b.is_a?(Sym)
181
+ same_sym = a.is_a?(Sym) && b.is_a?(Sym) && a.id == b.id
182
+ return same_sym if op == "eq"
183
+ return !same_sym if op == "ne"
184
+ raise Refused, "§57: вид enum не упорядочен"
185
+ end
186
+ if a.is_a?(Date) || b.is_a?(Date)
187
+ if a.is_a?(Date) && b.is_a?(Date)
188
+ f = ORDER[op]
189
+ raise Refused, "сравнение вне среза рантайма: #{op}" if f.nil?
190
+ return f.call(a.v, b.v)
191
+ end
192
+ return false if op == "eq"
193
+ return true if op == "ne"
194
+ raise Refused, "§57: Date сравнивается только с Date"
195
+ end
196
+ if a.is_a?(String) || b.is_a?(String)
197
+ return a == b if op == "eq"
198
+ return a != b if op == "ne"
199
+ raise Refused, "порядок на Text вне среза рантайма: #{op}"
200
+ end
201
+ raise Refused, "§49: сравнение денег с безразмерным" if money?(a) != money?(b)
202
+ raise Refused, "§49: разные валюты" if money?(a) && a.cur != b.cur
203
+ x, y, = pair(a, b)
204
+ f = ORDER[op]
205
+ raise Refused, "сравнение вне среза рантайма: #{op}" if f.nil?
206
+ f.call(x, y)
207
+ end
208
+
209
+ def eq(a, b)
210
+ return a.is_a?(Sym) && b.is_a?(Sym) && a.id == b.id if a.is_a?(Sym) || b.is_a?(Sym)
211
+ return a.is_a?(Date) && b.is_a?(Date) && a.v == b.v if a.is_a?(Date) || b.is_a?(Date)
212
+ return a == b if a.is_a?(String) || b.is_a?(String)
213
+ return false if money?(a) != money?(b)
214
+ return false if money?(a) && a.cur != b.cur
215
+ x, y, = pair(a, b)
216
+ x == y
217
+ end
218
+
219
+ def norm(d)
220
+ u = d.u
221
+ s = d.s
222
+ while s.positive? && (u % 10).zero?
223
+ u /= 10
224
+ s -= 1
225
+ end
226
+ "#{u}e-#{s}"
227
+ end
228
+
229
+ # Разделитель и экран внутри текста экранируются: иначе ["a|e:b","c"] и
230
+ # ["a","b|e:c"] склеивались в один ключ, и второй факт терялся.
231
+ def esc(text) = text.gsub("\\", "\\\\\\\\").gsub("|", "\\\\|")
232
+
233
+ def key(row)
234
+ row.map do |v|
235
+ case v
236
+ when String then "e:" + esc(v)
237
+ when Date then "t:" + v.v
238
+ when Sym then "c:" + esc(v.id)
239
+ when Money then "m:" + v.cur + ":" + norm(v)
240
+ else "d:" + norm(v)
241
+ end
242
+ end.join("|")
243
+ end
244
+
245
+ @legal_time = ""
246
+
247
+ def set_legal_time(t)
248
+ @legal_time = t.to_s
249
+ end
250
+
251
+ def legal_time
252
+ raise Refused, "legalTime не подан делом" if @legal_time.empty?
253
+ @legal_time
254
+ end
255
+ end