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,105 @@
1
+ // Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
2
+ // что получилось. Ожидания сверяют ворота, харнесс их не читает.
3
+ // В проекте файл лежит как main.swift: только там Swift допускает код верхнего уровня.
4
+ #if canImport(Darwin)
5
+ import Darwin
6
+ #elseif canImport(Glibc)
7
+ import Glibc
8
+ #endif
9
+
10
+ enum Arg {
11
+ case s(String)
12
+ case v(V)
13
+ case row([V])
14
+ }
15
+
16
+ func lit(_ x: J) -> Arg {
17
+ if let s = x.string { return .s(s) }
18
+ if let d = x["dec"]?.string { return .v(RT.dec(d)) }
19
+ if let m = x["money"]?.string { return .v(RT.money(m, x["cur"]?.string ?? "")) }
20
+ if let t = x["text"]?.string { return .v(.text(t)) }
21
+ if let d = x["date"]?.string { return .v(RT.date(d)) }
22
+ if let s = x["sym"]?.string { return .v(RT.sym(s)) }
23
+ if let row = x["row"] {
24
+ return .row(row.array.map { cell -> V in
25
+ guard case let .v(v) = lit(cell) else { fatalError("ячейка фикстуры") }
26
+ return v
27
+ })
28
+ }
29
+ fatalError("литерал фикстуры")
30
+ }
31
+
32
+ // Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
33
+ func digits(_ u: BigI, _ s: Int) -> String {
34
+ var body = u.magnitude.description
35
+ if body.count < s + 1 { body = String(repeating: "0", count: s + 1 - body.count) + body }
36
+ if s > 0 { body = String(body.dropLast(s)) + "." + String(body.suffix(s)) }
37
+ return (u.neg ? "-" : "") + body
38
+ }
39
+
40
+ func render(_ v: V) -> String {
41
+ switch v {
42
+ case let .text(t): return jstr(t)
43
+ case let .money(u, s, cur): return jstr(digits(u, s) + " " + cur)
44
+ case let .dec(u, s): return jstr(digits(u, s))
45
+ case let .date(d): return jstr(d)
46
+ case let .sym(id): return jstr(id)
47
+ }
48
+ }
49
+
50
+ func val(_ a: Arg) -> V {
51
+ if case let .v(v) = a { return v }
52
+ if case let .s(s) = a { return .text(s) }
53
+ fatalError("ожидалось значение")
54
+ }
55
+
56
+ func str(_ a: Arg) -> String {
57
+ if case let .s(s) = a { return s }
58
+ fatalError("ожидалась строка")
59
+ }
60
+
61
+ func rowOf(_ a: Arg) -> [V] {
62
+ if case let .row(r) = a { return r }
63
+ fatalError("ожидалась строка таблицы")
64
+ }
65
+
66
+ func run(_ op: String, _ a: [Arg]) throws -> String {
67
+ switch op {
68
+ case "dec": return render(RT.dec(str(a[0])))
69
+ case "money": return render(RT.money(str(a[0]), str(a[1])))
70
+ case "date": return render(RT.date(str(a[0])))
71
+ case "sym": return render(RT.sym(str(a[0])))
72
+ case "mul": return render(try RT.mul(val(a[0]), val(a[1])))
73
+ case "add": return render(try RT.add(val(a[0]), val(a[1])))
74
+ case "sub": return render(try RT.sub(val(a[0]), val(a[1])))
75
+ case "div": return render(try RT.div(val(a[0]), val(a[1])))
76
+ case "mod": return render(try RT.mod(val(a[0]), val(a[1])))
77
+ case "min": return render(try RT.min(val(a[0]), val(a[1])))
78
+ case "max": return render(try RT.max(val(a[0]), val(a[1])))
79
+ case "abs": return render(try RT.abs(val(a[0])))
80
+ case "pow": return render(try RT.pow(val(a[0]), val(a[1])))
81
+ case "round": return render(try RT.round(val(a[0]), val(a[1]), str(a[2])))
82
+ case "cmp": return try RT.cmp(val(a[0]), str(a[1]), val(a[2])) ? "true" : "false"
83
+ case "eq": return RT.eq(val(a[0]), val(a[1])) ? "true" : "false"
84
+ case "keyEq": return RT.key(rowOf(a[0])) == RT.key(rowOf(a[1])) ? "true" : "false"
85
+ case "legalTime": return jstr(try RT.legalTime())
86
+ case "setLegalTime":
87
+ RT.setLegalTime(str(a[0]))
88
+ return "null"
89
+ default: fatalError("неизвестная операция фикстуры: \(op)")
90
+ }
91
+ }
92
+
93
+ let fixture = readJSON(CommandLine.arguments[1])
94
+ var out: [String] = []
95
+ for c in fixture["cases"]?.array ?? [] {
96
+ let args = (c["args"]?.array ?? []).map(lit)
97
+ var got: String
98
+ do {
99
+ got = try run(c["op"]?.string ?? "", args)
100
+ } catch let e as Refused {
101
+ got = "{\"refused\":" + jstr(e.message) + "}"
102
+ }
103
+ out.append("{\"id\":" + jstr(c["id"]?.string ?? "") + ",\"got\":" + got + "}")
104
+ }
105
+ print("[" + out.joined(separator: ",") + "]")
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env python3
2
+ """Эмиттер Swift. Тот же план и тот же контракт рантайма; модель — `enum Model`
3
+ со статическими таблицами, отказ — `throw Refused`, поэтому каждое выражение с
4
+ вызовом рантайма стоит под `try`."""
5
+ import json
6
+ import os
7
+ import shutil
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 ss(s: str) -> str:
23
+ """Строковый литерал Swift: экраны JSON вида `\\uXXXX` и `\\b` Swift не знает
24
+ (у него `\\u{…}`), а `\\(` — интерполяция; экранируются кавычка, экран и
25
+ управляющие символы."""
26
+ return '"' + "".join("\\\\" if c == "\\" else '\\"' if c == '"' else
27
+ f"\\u{{{ord(c):x}}}" if ord(c) < 0x20 or ord(c) == 0x7F else c
28
+ for c in s) + '"'
29
+
30
+
31
+ def key_readers(plan: dict) -> dict[str, list[str]]:
32
+ readers: dict[str, set[str]] = {}
33
+ for rule in plan["rules"]:
34
+ for atom in rule.get("atoms") or []:
35
+ readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
36
+ for check in rule.get("checks") or []:
37
+ readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
38
+ return {source: sorted(consumers) for source, consumers in sorted(readers.items())}
39
+
40
+
41
+ def term(t: dict) -> str:
42
+ k = t["k"]
43
+ if k == "var":
44
+ return f'r{t["row"]}[{t["col"]}]'
45
+ if k == "lit":
46
+ if t["t"] == "money":
47
+ return f'RT.money({ss(t["v"])}, {ss(t["cur"])})'
48
+ if t["t"] == "dec":
49
+ return f'RT.dec({ss(t["v"])})'
50
+ if t["t"] == "date":
51
+ return f'RT.date({ss(t["v"])})'
52
+ if t["t"] == "enum":
53
+ return f'RT.sym({ss(t["v"])})'
54
+ return f'V.text({ss(t["v"])})'
55
+ if k == "bin":
56
+ return f'RT.{t["op"]}({term(t["l"])}, {term(t["r"])})'
57
+ if k == "call":
58
+ args = t["args"]
59
+ if t["fn"] == "round":
60
+ return f'RT.round({term(args[0])}, {term(args[1])}, {ss(args[2]["v"])})'
61
+ return f'RT.{t["fn"]}(' + ", ".join(term(a) for a in args) + ')'
62
+ raise AssertionError(k)
63
+
64
+
65
+ def emit(plan: dict) -> str:
66
+ check_plan(plan)
67
+ L = ["enum Model {", " static var TABLES: [String: [[V]]] = ["]
68
+ for t in plan["tables"]:
69
+ L.append(f' {ss(t["pred"])}: [],')
70
+ if not plan["tables"]:
71
+ L.append(" :")
72
+ L.append(" ]")
73
+ L.append(" // Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
74
+ L.append(" static let PARAMS: [String: [String]] = [")
75
+ for t in plan["tables"]:
76
+ L.append(f' {ss(t["pred"])}: [' + ", ".join(ss(p) for p in t["params"]) + "],")
77
+ if not plan["tables"]:
78
+ L.append(" :")
79
+ L.append(" ]")
80
+ L.append(" // Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
81
+ L.append(" static func consts() throws -> [String: (String, V)] {")
82
+ L.append(" var c: [String: (String, V)] = [:]")
83
+ for cid, c in plan["consts"].items():
84
+ L.append(f' c[{ss(cid)}] = ({ss(c["type"])}, try {term(c["term"])})')
85
+ L.append(" return c")
86
+ L.append(" }")
87
+ L.append(" // Наследование сущностей: подтип подходит параметру базового типа.")
88
+ L.append(" static let BASES: [String: String] = [")
89
+ for sub, base in plan["bases"].items():
90
+ L.append(f" {ss(sub)}: {ss(base)},")
91
+ if not plan["bases"]:
92
+ L.append(" :")
93
+ L.append(" ]")
94
+ L.append(" // Ключи отношений §45.1: позиции столбцов каждого ключа.")
95
+ L.append(" static let KEYS: [(String, [[Int]])] = [")
96
+ for t in plan["tables"]:
97
+ if t["keys"]:
98
+ L.append(f' ({ss(t["pred"])}, {json.dumps(t["keys"])}),')
99
+ L.append(" ]")
100
+ readers = key_readers(plan)
101
+ L.append(" static let KEY_READERS: [String: [String]] = [")
102
+ for source, consumers in readers.items():
103
+ L.append(f' {ss(source)}: [' + ", ".join(ss(value) for value in consumers) + "],")
104
+ if not readers:
105
+ L.append(" :")
106
+ L.append(" ]")
107
+ L.append(" static var seen: [String: Set<String>] = [:]")
108
+ L.append("")
109
+ L.append(" static func addRow(_ urn: String, _ row: [V]) -> Bool {")
110
+ L.append(" if !seen[urn, default: []].insert(RT.key(row)).inserted { return false }")
111
+ L.append(" TABLES[urn]!.append(row)")
112
+ L.append(" return true")
113
+ L.append(" }")
114
+ L.append("")
115
+ for r in plan["rules"]:
116
+ L.append(f' // {r["id"]}')
117
+ L.append(f' static func rule_{r["name"]}() throws -> Bool {{')
118
+ L.append(" var grew = false")
119
+ if r["window"]:
120
+ if r["window"].get("start"):
121
+ L.append(f' if try RT.legalTime() < {ss(r["window"]["start"])} {{ return false }}')
122
+ if r["window"].get("end"):
123
+ L.append(f' if try RT.legalTime() >= {ss(r["window"]["end"])} {{ return false }}')
124
+ ind = " "
125
+ for i, a in enumerate(r["atoms"]):
126
+ # Массив Swift — значение: цикл идёт по снимку таблицы на входе.
127
+ L.append(f'{ind}for r{i} in TABLES[{ss(a["pred"])}]! {{')
128
+ ind += " "
129
+ for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
130
+ L.append(f'{ind}if !(try RT.cmp(r{ai}[{aj}], "eq", r{bi}[{bj}])) {{ continue }}')
131
+ for f in r["filters"]:
132
+ L.append(f'{ind}if !RT.eq(r{f["row"]}[{f["col"]}], {term(f["term"])}) {{ continue }}')
133
+ for g in r["guards"]:
134
+ L.append(f'{ind}if !(try RT.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 try addRow({ss(r["head"]["pred"])}, [{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
+ L.append(" static func saturate() throws {")
144
+ L.append(" var grew = true")
145
+ L.append(" while grew {")
146
+ L.append(" grew = false")
147
+ for r in plan["rules"]:
148
+ L.append(f' if try rule_{r["name"]}() {{ grew = true }}')
149
+ L.append(" }")
150
+ L.append(" }")
151
+ L.append("")
152
+ L.append(" static func register(_ urn: String, _ row: [V]) -> Bool { addRow(urn, row) }")
153
+ L.append("")
154
+ L.append(" // KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
155
+ L.append(" static func keyConflicts() -> Int {")
156
+ L.append(" var n = 0")
157
+ L.append(" for (urn, keys) in KEYS {")
158
+ L.append(" for key in keys {")
159
+ L.append(" var groups: [String: Int] = [:]")
160
+ L.append(" for row in TABLES[urn]! { groups[RT.key(key.map { row[$0] }), default: 0] += 1 }")
161
+ L.append(" n += groups.values.filter { $0 > 1 }.count")
162
+ L.append(" }")
163
+ L.append(" }")
164
+ L.append(" return n")
165
+ L.append(" }")
166
+ L.append(" static func keyConflictPredicates() -> Set<String> {")
167
+ L.append(" var out = Set<String>()")
168
+ L.append(" for (urn, keys) in KEYS { for key in keys { var groups: [String: Int] = [:]; for row in TABLES[urn]! { groups[RT.key(key.map { row[$0] }), default: 0] += 1 }; if groups.values.contains(where: { $0 > 1 }) { out.insert(urn) } } }")
169
+ L.append(" return out")
170
+ L.append(" }")
171
+ L.append("")
172
+ L.append(" // Собственные факты пакета: раннер кладёт их до фактов дела.")
173
+ L.append(" static func packageFacts() throws {")
174
+ for f in plan["facts"]:
175
+ L.append(f' _ = try addRow({ss(f["pred"])}, [' + ", ".join(term(a) for a in f["args"]) + "])")
176
+ L.append(" }")
177
+ L.append("}")
178
+ return "\n".join(L) + "\n"
179
+
180
+
181
+ # --- дескриптор цели ---------------------------------------------------------
182
+ HERE = Path(__file__).resolve().parent
183
+ EXT = "swift"
184
+ MODEL = "model.swift"
185
+ MAINS = {"runner": "runner.swift", "cert": "cert.swift"}
186
+ BINS = {"runner": "runner", "cert": "cert"}
187
+ FLAGS = ["-Onone", "-swift-version", "5"]
188
+ # Одна сборка проекта; main.swift — раннер либо харнесс, модель есть только у раннера.
189
+ COMPILE = ("if [ -f model.swift ]; then swiftc {flags} runtime.swift json.swift model.swift main.swift -o runner; "
190
+ "else swiftc {flags} runtime.swift json.swift main.swift -o cert; fi").format(flags=" ".join(FLAGS))
191
+
192
+
193
+ def available():
194
+ swiftc = shutil.which("swiftc")
195
+ return (True, swiftc) if swiftc else (False, "нет swiftc в PATH")
196
+
197
+
198
+ def layout(proj, main: str) -> list:
199
+ proj = Path(proj)
200
+ proj.mkdir(parents=True, exist_ok=True)
201
+ out = []
202
+ for name in ("runtime.swift", "json.swift"):
203
+ shutil.copyfile(HERE / name, proj / name)
204
+ out.append(proj / name)
205
+ shutil.copyfile(HERE / MAINS[main], proj / "main.swift")
206
+ out.append(proj / "main.swift")
207
+ return out
208
+
209
+
210
+ def build(proj):
211
+ return ["sh", "-c", COMPILE], str(proj)
212
+
213
+
214
+ def build_many(dirs, out):
215
+ """Сборка всех проектов ворот параллельно: swiftc на проект — секунды, а
216
+ проектов больше сорока. Одного бинаря на все модели не выходит: у каждой
217
+ модели тот же `enum Model`."""
218
+ jobs = str(max(2, min(8, (os.cpu_count() or 4) // 2)))
219
+ script = f"printf '%s\\0' \"$@\" | xargs -0 -n 1 -P {jobs} sh -c 'cd \"$1\" && {COMPILE}' sh"
220
+ return ["sh", "-c", script, "sh", *[str(d) for d in dirs]], None
221
+
222
+
223
+ def run(proj, main: str):
224
+ return [str(Path(proj) / BINS[main])], None
225
+
226
+
227
+ if __name__ == "__main__":
228
+ sys.path.insert(0, str(HERE.parents[1]))
229
+ import plan as P
230
+ ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
231
+ Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
@@ -0,0 +1,208 @@
1
+ // Разбор и печать JSON без Foundation: `JSONSerialization` отдаёт числа как
2
+ // NSNumber (двоичная плавающая точка у дробных, потеря цифр у длинных целых),
3
+ // поэтому число здесь хранится исходной записью.
4
+ #if canImport(Darwin)
5
+ import Darwin
6
+ #elseif canImport(Glibc)
7
+ import Glibc
8
+ #endif
9
+
10
+ indirect enum J {
11
+ case null
12
+ case bool(Bool)
13
+ case num(String)
14
+ case str(String)
15
+ case arr([J])
16
+ case obj([String: J])
17
+
18
+ subscript(_ key: String) -> J? {
19
+ if case let .obj(o) = self { return o[key] }
20
+ return nil
21
+ }
22
+
23
+ var array: [J] {
24
+ if case let .arr(a) = self { return a }
25
+ return []
26
+ }
27
+
28
+ var string: String? {
29
+ if case let .str(s) = self { return s }
30
+ return nil
31
+ }
32
+
33
+ /// Текст значения: строка как есть, число — его запись, иначе пусто.
34
+ var text: String {
35
+ switch self {
36
+ case let .str(s): return s
37
+ case let .num(t): return t
38
+ case let .bool(b): return b ? "true" : "false"
39
+ default: return ""
40
+ }
41
+ }
42
+ }
43
+
44
+ struct JParser {
45
+ let b: [UInt8]
46
+ var i = 0
47
+
48
+ init(_ bytes: [UInt8]) { b = bytes }
49
+
50
+ func fail(_ what: String) -> Never { fatalError("JSON: \(what) в позиции \(i)") }
51
+
52
+ mutating func ws() {
53
+ while i < b.count, b[i] == 0x20 || b[i] == 0x0A || b[i] == 0x0D || b[i] == 0x09 { i += 1 }
54
+ }
55
+
56
+ mutating func lit(_ word: String) -> Bool {
57
+ let w = Array(word.utf8)
58
+ if i + w.count <= b.count && Array(b[i..<(i + w.count)]) == w {
59
+ i += w.count
60
+ return true
61
+ }
62
+ return false
63
+ }
64
+
65
+ mutating func value() -> J {
66
+ ws()
67
+ guard i < b.count else { fail("конец входа") }
68
+ switch b[i] {
69
+ case UInt8(ascii: "{"):
70
+ i += 1
71
+ var o: [String: J] = [:]
72
+ ws()
73
+ if b[i] == UInt8(ascii: "}") {
74
+ i += 1
75
+ return .obj(o)
76
+ }
77
+ while true {
78
+ ws()
79
+ guard b[i] == UInt8(ascii: "\"") else { fail("ожидался ключ") }
80
+ let k = string()
81
+ ws()
82
+ guard b[i] == UInt8(ascii: ":") else { fail("ожидалось ':'") }
83
+ i += 1
84
+ o[k] = value()
85
+ ws()
86
+ if b[i] == UInt8(ascii: "}") {
87
+ i += 1
88
+ return .obj(o)
89
+ }
90
+ guard b[i] == UInt8(ascii: ",") else { fail("ожидалось ',' или '}'") }
91
+ i += 1
92
+ }
93
+ case UInt8(ascii: "["):
94
+ i += 1
95
+ var a: [J] = []
96
+ ws()
97
+ if b[i] == UInt8(ascii: "]") {
98
+ i += 1
99
+ return .arr(a)
100
+ }
101
+ while true {
102
+ a.append(value())
103
+ ws()
104
+ if b[i] == UInt8(ascii: "]") {
105
+ i += 1
106
+ return .arr(a)
107
+ }
108
+ guard b[i] == UInt8(ascii: ",") else { fail("ожидалось ',' или ']'") }
109
+ i += 1
110
+ }
111
+ case UInt8(ascii: "\""):
112
+ return .str(string())
113
+ default:
114
+ if lit("true") { return .bool(true) }
115
+ if lit("false") { return .bool(false) }
116
+ if lit("null") { return .null }
117
+ let start = i
118
+ while i < b.count, "+-.eE0123456789".utf8.contains(b[i]) { i += 1 }
119
+ if start == i { fail("неизвестное значение") }
120
+ return .num(String(decoding: b[start..<i], as: UTF8.self))
121
+ }
122
+ }
123
+
124
+ mutating func hex4() -> UInt32 {
125
+ guard i + 4 <= b.count, let v = UInt32(String(decoding: b[i..<(i + 4)], as: UTF8.self), radix: 16) else {
126
+ fail("плохой \\u")
127
+ }
128
+ i += 4
129
+ return v
130
+ }
131
+
132
+ mutating func string() -> String {
133
+ i += 1
134
+ var out: [UInt8] = []
135
+ while true {
136
+ guard i < b.count else { fail("строка не закрыта") }
137
+ let c = b[i]
138
+ if c == UInt8(ascii: "\"") {
139
+ i += 1
140
+ return String(decoding: out, as: UTF8.self)
141
+ }
142
+ if c != UInt8(ascii: "\\") {
143
+ out.append(c)
144
+ i += 1
145
+ continue
146
+ }
147
+ let e = b[i + 1]
148
+ i += 2
149
+ switch e {
150
+ case UInt8(ascii: "\""), UInt8(ascii: "\\"), UInt8(ascii: "/"): out.append(e)
151
+ case UInt8(ascii: "b"): out.append(0x08)
152
+ case UInt8(ascii: "f"): out.append(0x0C)
153
+ case UInt8(ascii: "n"): out.append(0x0A)
154
+ case UInt8(ascii: "r"): out.append(0x0D)
155
+ case UInt8(ascii: "t"): out.append(0x09)
156
+ case UInt8(ascii: "u"):
157
+ var cp = hex4()
158
+ if cp >= 0xD800 && cp <= 0xDBFF && i + 1 < b.count && b[i] == UInt8(ascii: "\\") && b[i + 1] == UInt8(ascii: "u") {
159
+ i += 2
160
+ let lo = hex4()
161
+ cp = 0x10000 + ((cp - 0xD800) << 10) + (lo &- 0xDC00)
162
+ }
163
+ out.append(contentsOf: String(Unicode.Scalar(cp) ?? "\u{FFFD}").utf8)
164
+ default: fail("плохой экран")
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ func readJSON(_ path: String) -> J {
171
+ guard let f = fopen(path, "rb") else { fatalError("не открыть \(path)") }
172
+ defer { fclose(f) }
173
+ var bytes: [UInt8] = []
174
+ var buf = [UInt8](repeating: 0, count: 65536)
175
+ while true {
176
+ let n = fread(&buf, 1, buf.count, f)
177
+ if n <= 0 { break }
178
+ bytes.append(contentsOf: buf[0..<n])
179
+ }
180
+ var p = JParser(bytes)
181
+ let v = p.value()
182
+ p.ws()
183
+ if p.i != bytes.count { p.fail("лишнее после значения") }
184
+ return v
185
+ }
186
+
187
+ /// Строка JSON: символы как есть (stdout получает UTF-8), экранируются
188
+ /// кавычка, экран и управляющие символы.
189
+ func jstr(_ s: String) -> String {
190
+ var out = "\""
191
+ for ch in s.unicodeScalars {
192
+ switch ch {
193
+ case "\"": out += "\\\""
194
+ case "\\": out += "\\\\"
195
+ case "\n": out += "\\n"
196
+ case "\r": out += "\\r"
197
+ case "\t": out += "\\t"
198
+ default:
199
+ if ch.value < 0x20 {
200
+ let h = String(ch.value, radix: 16)
201
+ out += "\\u" + String(repeating: "0", count: 4 - h.count) + h
202
+ } else {
203
+ out.unicodeScalars.append(ch)
204
+ }
205
+ }
206
+ }
207
+ return out + "\""
208
+ }
@@ -0,0 +1,133 @@
1
+ // Раннер Swift-бэкенда: тот же вход и тот же ответ, что у runner.rb.
2
+ // В проекте файл лежит как main.swift: только там Swift допускает код верхнего уровня.
3
+ //
4
+ // Протокол выхода общий для всех целей: ответ — JSON и код 0; названный отказ
5
+ // среза — JSON {"refused": текст} и код 3, текст побайтово одинаков между
6
+ // целями; любая другая ошибка — крах, который ворота валят.
7
+ #if canImport(Darwin)
8
+ import Darwin
9
+ #elseif canImport(Glibc)
10
+ import Glibc
11
+ #endif
12
+
13
+ var ISSUES: [String] = []
14
+ var CONSTS: [String: (String, V)] = [:]
15
+
16
+ func arg(_ a: J) throws -> V {
17
+ let kind = a["kind"]?.text ?? ""
18
+ if kind == "entity_ref" { return .text(a["id"]?.text ?? "") }
19
+ if kind == "const_ref" {
20
+ // §54: разыменование предшествует исполнению; член перечисления §38 либо
21
+ // имя без объявления остаётся идентичностью, как в ключе атома оракула.
22
+ let id = a["id"]?.text ?? ""
23
+ if let c = CONSTS[id] { return c.1 }
24
+ return RT.sym(id)
25
+ }
26
+ if kind != "value" { throw Refused("аргумент вне среза раннера: \(kind)") }
27
+ let t = a["type"]?["name"]?.text ?? ""
28
+ let v = a["value"]?.text ?? ""
29
+ switch t {
30
+ case "urn:law:std#Money": return RT.money(v, a["currency"]?.text ?? "")
31
+ case "urn:law:std#Decimal", "urn:law:std#Integer": return RT.dec(v)
32
+ case "urn:law:std#Text": return .text(v)
33
+ case "urn:law:std#Date": return RT.date(v)
34
+ default: throw Refused("аргумент вне среза раннера: \(t)")
35
+ }
36
+ }
37
+
38
+ // Тип аргумента, если известен: у value — из узла, у const_ref — из объявления
39
+ // константы; entity_ref типа не несёт (как у оракула).
40
+ func argType(_ a: J) -> String? {
41
+ switch a["kind"]?.text {
42
+ case "value": return a["type"]?["name"]?.string
43
+ case "const_ref": return CONSTS[a["id"]?.text ?? ""]?.0
44
+ default: return nil
45
+ }
46
+ }
47
+
48
+ // Совпадение типа с наследованием сущностей вверх от фактического.
49
+ func conforms(_ actual: String, _ expected: String) -> Bool {
50
+ var seen = Set<String>()
51
+ var cur: String? = actual
52
+ while let c = cur, !seen.contains(c) {
53
+ if c == expected { return true }
54
+ seen.insert(c)
55
+ cur = Model.BASES[c]
56
+ }
57
+ return false
58
+ }
59
+
60
+ // Арность и типы аргументов утверждения против сигнатуры §189 (E-0046).
61
+ func wellTyped(_ lit: J) -> Bool {
62
+ guard let params = Model.PARAMS[lit["predicate"]?.text ?? ""] else { return true }
63
+ let args = lit["args"]?.array ?? []
64
+ if args.count != params.count {
65
+ ISSUES.append("TYPE_ERROR")
66
+ return false
67
+ }
68
+ for (expected, a) in zip(params, args) {
69
+ if let actual = argType(a), !conforms(actual, expected) {
70
+ ISSUES.append("TYPE_ERROR")
71
+ return false
72
+ }
73
+ }
74
+ return true
75
+ }
76
+
77
+ // Ключ атома у оракула §208 включает ТИП значения: несогласованный запрос
78
+ // атома не находит — NEITHER без issue.
79
+ func queryTyped(_ lit: J) -> Bool {
80
+ guard let params = Model.PARAMS[lit["predicate"]?.text ?? ""] else { return false }
81
+ let args = lit["args"]?.array ?? []
82
+ if args.count != params.count { return false }
83
+ return zip(params, args).allSatisfy { expected, a in argType(a).map { conforms($0, expected) } ?? true }
84
+ }
85
+
86
+ func keyConflictBlocks(_ predicate: String) -> Bool {
87
+ var tainted = Model.keyConflictPredicates()
88
+ var waiting = Array(tainted)
89
+ while let source = waiting.popLast() {
90
+ for consumer in Model.KEY_READERS[source] ?? [] where tainted.insert(consumer).inserted { waiting.append(consumer) }
91
+ }
92
+ return tainted.contains(predicate)
93
+ }
94
+
95
+ func runMain() throws {
96
+ let kase = readJSON(CommandLine.arguments[1])
97
+ let query = readJSON(CommandLine.arguments[2])
98
+ RT.setLegalTime(kase["context"]?["legalTime"]?.text ?? "")
99
+ CONSTS = try Model.consts()
100
+ try Model.packageFacts()
101
+ for a in kase["assertions"]?.array ?? [] {
102
+ guard let lit = a["literal"] else { continue }
103
+ if lit["polarity"]?.text != "positive" { throw Refused("§113: отрицательный факт вне L0-среза") }
104
+ if !wellTyped(lit) { continue }
105
+ let pred = lit["predicate"]?.text ?? ""
106
+ if Model.PARAMS[pred] == nil { continue }
107
+ _ = Model.register(pred, try (lit["args"]?.array ?? []).map(arg))
108
+ }
109
+ try Model.saturate()
110
+ for _ in 0..<Model.keyConflicts() { ISSUES.append("KEY_CONFLICT") }
111
+ let kind = query["kind"]?.text ?? ""
112
+ if kind != "truth" { throw Refused("вид запроса вне среза раннера: \(kind)") }
113
+ var hit = false
114
+ if let ql = query["literal"], queryTyped(ql) {
115
+ let want = try (ql["args"]?.array ?? []).map(arg)
116
+ let rows = Model.TABLES[ql["predicate"]?.text ?? ""] ?? []
117
+ hit = rows.contains { r in r.count == want.count && zip(r, want).allSatisfy { RT.eq($0, $1) } }
118
+ }
119
+ // Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
120
+ print("{\"queryId\":" + jstr(query["queryId"]?.text ?? "") +
121
+ ",\"truthStatus\":" + jstr(hit ? "TRUE_ONLY" : "NEITHER") +
122
+ ",\"evaluationStatus\":" + jstr(keyConflictBlocks(query["literal"]?["predicate"]?.text ?? "") ? "NON_EXECUTABLE" : "COMPUTED") + ",\"issues\":[" + ISSUES.map(jstr).joined(separator: ",") + "]}")
123
+ }
124
+
125
+ do {
126
+ try runMain()
127
+ } catch let e as Refused {
128
+ print("{\"refused\":" + jstr(e.message) + "}")
129
+ exit(3)
130
+ } catch {
131
+ fputs("error: \(error)\n", stderr)
132
+ exit(1)
133
+ }