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,142 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "arxo-codegen-rust-dependencies"
7
+ version = "0.1.0"
8
+ dependencies = [
9
+ "num-bigint",
10
+ "num-integer",
11
+ "num-traits",
12
+ "serde_json",
13
+ ]
14
+
15
+ [[package]]
16
+ name = "autocfg"
17
+ version = "1.5.1"
18
+ source = "registry+https://github.com/rust-lang/crates.io-index"
19
+ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
20
+
21
+ [[package]]
22
+ name = "itoa"
23
+ version = "1.0.18"
24
+ source = "registry+https://github.com/rust-lang/crates.io-index"
25
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
26
+
27
+ [[package]]
28
+ name = "memchr"
29
+ version = "2.8.3"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
32
+
33
+ [[package]]
34
+ name = "num-bigint"
35
+ version = "0.4.8"
36
+ source = "registry+https://github.com/rust-lang/crates.io-index"
37
+ checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
38
+ dependencies = [
39
+ "num-integer",
40
+ "num-traits",
41
+ ]
42
+
43
+ [[package]]
44
+ name = "num-integer"
45
+ version = "0.1.47"
46
+ source = "registry+https://github.com/rust-lang/crates.io-index"
47
+ checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
48
+ dependencies = [
49
+ "num-traits",
50
+ ]
51
+
52
+ [[package]]
53
+ name = "num-traits"
54
+ version = "0.2.19"
55
+ source = "registry+https://github.com/rust-lang/crates.io-index"
56
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
57
+ dependencies = [
58
+ "autocfg",
59
+ ]
60
+
61
+ [[package]]
62
+ name = "proc-macro2"
63
+ version = "1.0.107"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
66
+ dependencies = [
67
+ "unicode-ident",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "quote"
72
+ version = "1.0.47"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
75
+ dependencies = [
76
+ "proc-macro2",
77
+ ]
78
+
79
+ [[package]]
80
+ name = "serde"
81
+ version = "1.0.229"
82
+ source = "registry+https://github.com/rust-lang/crates.io-index"
83
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
84
+ dependencies = [
85
+ "serde_core",
86
+ ]
87
+
88
+ [[package]]
89
+ name = "serde_core"
90
+ version = "1.0.229"
91
+ source = "registry+https://github.com/rust-lang/crates.io-index"
92
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
93
+ dependencies = [
94
+ "serde_derive",
95
+ ]
96
+
97
+ [[package]]
98
+ name = "serde_derive"
99
+ version = "1.0.229"
100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
101
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
102
+ dependencies = [
103
+ "proc-macro2",
104
+ "quote",
105
+ "syn",
106
+ ]
107
+
108
+ [[package]]
109
+ name = "serde_json"
110
+ version = "1.0.151"
111
+ source = "registry+https://github.com/rust-lang/crates.io-index"
112
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
113
+ dependencies = [
114
+ "itoa",
115
+ "memchr",
116
+ "serde",
117
+ "serde_core",
118
+ "zmij",
119
+ ]
120
+
121
+ [[package]]
122
+ name = "syn"
123
+ version = "3.0.5"
124
+ source = "registry+https://github.com/rust-lang/crates.io-index"
125
+ checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
126
+ dependencies = [
127
+ "proc-macro2",
128
+ "quote",
129
+ "unicode-ident",
130
+ ]
131
+
132
+ [[package]]
133
+ name = "unicode-ident"
134
+ version = "1.0.24"
135
+ source = "registry+https://github.com/rust-lang/crates.io-index"
136
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
137
+
138
+ [[package]]
139
+ name = "zmij"
140
+ version = "1.0.23"
141
+ source = "registry+https://github.com/rust-lang/crates.io-index"
142
+ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "arxo-codegen-rust-dependencies"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ autobins = false
6
+
7
+ [lib]
8
+ path = "prefetch.rs"
9
+
10
+ # This manifest exists solely to prefetch the exact crates used by generated
11
+ # projects before their descriptors build with `cargo --offline` in CI.
12
+ [dependencies]
13
+ num-bigint = "0.4"
14
+ num-integer = "0.1"
15
+ num-traits = "0.2"
16
+ serde_json = { version = "1", features = ["arbitrary_precision"] }
@@ -0,0 +1,106 @@
1
+ //! Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
2
+ //! что получилось. Ожидания сверяют ворота, харнесс их не читает.
3
+ mod rt;
4
+
5
+ use num_bigint::BigInt;
6
+ use num_traits::Signed;
7
+ use rt::{Refused, Val, R};
8
+ use serde_json::{json, Value};
9
+
10
+ fn lit(x: &Value) -> Val {
11
+ if let Some(s) = x.as_str() {
12
+ return rt::text(s);
13
+ }
14
+ if let Some(d) = x.get("dec") {
15
+ return rt::dec(d.as_str().unwrap());
16
+ }
17
+ if let Some(m) = x.get("money") {
18
+ return rt::money(m.as_str().unwrap(), x["cur"].as_str().unwrap());
19
+ }
20
+ if let Some(t) = x.get("text") {
21
+ return rt::text(t.as_str().unwrap());
22
+ }
23
+ if let Some(d) = x.get("date") {
24
+ return rt::date(d.as_str().unwrap());
25
+ }
26
+ if let Some(s) = x.get("sym") {
27
+ return rt::sym(s.as_str().unwrap());
28
+ }
29
+ panic!("литерал фикстуры: {x}");
30
+ }
31
+
32
+ fn row(x: &Value) -> Vec<Val> {
33
+ x["row"].as_array().unwrap().iter().map(lit).collect()
34
+ }
35
+
36
+ /// Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
37
+ fn digits(u: &BigInt, s: usize) -> String {
38
+ let neg = u.is_negative();
39
+ let mut body = u.abs().to_string();
40
+ while body.len() < s + 1 {
41
+ body.insert(0, '0');
42
+ }
43
+ let text = if s == 0 {
44
+ body
45
+ } else {
46
+ let cut = body.len() - s;
47
+ format!("{}.{}", &body[..cut], &body[cut..])
48
+ };
49
+ if neg { format!("-{text}") } else { text }
50
+ }
51
+
52
+ fn render(v: &Val) -> Value {
53
+ match v {
54
+ Val::Text(t) => json!(t),
55
+ Val::Money { u, s, cur } => json!(format!("{} {}", digits(u, *s), cur)),
56
+ Val::Dec { u, s } => json!(digits(u, *s)),
57
+ Val::Date(d) => json!(d),
58
+ Val::Sym(s) => json!(s),
59
+ }
60
+ }
61
+
62
+ fn run(db: &mut rt::Db, c: &Value) -> R<Value> {
63
+ let a: Vec<Value> = c["args"].as_array().unwrap().clone();
64
+ let s = |i: usize| a[i].as_str().unwrap();
65
+ let v = |i: usize| lit(&a[i]);
66
+ Ok(match c["op"].as_str().unwrap() {
67
+ "dec" => render(&rt::dec(s(0))),
68
+ "money" => render(&rt::money(s(0), s(1))),
69
+ "date" => render(&rt::date(s(0))),
70
+ "sym" => render(&rt::sym(s(0))),
71
+ "add" => render(&rt::add(&v(0), &v(1))?),
72
+ "sub" => render(&rt::sub(&v(0), &v(1))?),
73
+ "mul" => render(&rt::mul(&v(0), &v(1))?),
74
+ "div" => render(&rt::div(&v(0), &v(1))?),
75
+ "mod" => render(&rt::modulo(&v(0), &v(1))?),
76
+ "min" => render(&rt::min(&v(0), &v(1))?),
77
+ "max" => render(&rt::max(&v(0), &v(1))?),
78
+ "abs" => render(&rt::abs(&v(0))?),
79
+ "pow" => render(&rt::pow(&v(0), &v(1))?),
80
+ "round" => render(&rt::round(&v(0), &v(1), s(2))?),
81
+ "cmp" => json!(rt::cmp(&v(0), s(1), &v(2))?),
82
+ "eq" => json!(rt::eq(&v(0), &v(1))),
83
+ "keyEq" => json!(rt::key(&row(&a[0])) == rt::key(&row(&a[1]))),
84
+ "legalTime" => json!(db.legal_time()?),
85
+ "setLegalTime" => {
86
+ db.set_legal_time(s(0));
87
+ Value::Null
88
+ }
89
+ op => panic!("неизвестная операция фикстуры: {op}"),
90
+ })
91
+ }
92
+
93
+ fn main() {
94
+ let argv: Vec<String> = std::env::args().collect();
95
+ let fixture: Value = serde_json::from_str(&std::fs::read_to_string(&argv[1]).unwrap()).unwrap();
96
+ let mut db = rt::Db::new(&[]);
97
+ let mut out = Vec::new();
98
+ for c in fixture["cases"].as_array().unwrap() {
99
+ let got = match run(&mut db, c) {
100
+ Ok(v) => v,
101
+ Err(Refused(msg)) => json!({ "refused": msg }),
102
+ };
103
+ out.push(json!({ "id": c["id"], "got": got }));
104
+ }
105
+ println!("{}", serde_json::to_string(&out).unwrap());
106
+ }
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env python3
2
+ """Эмиттер Rust. Тот же план; модель — модуль `model.rs` с таблицами,
3
+ правилами над `rt::Db` и `Result` вместо panic."""
4
+ import json
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ SUPPORTED_PLAN = "law-backend-plan/0.7"
10
+ OPS = {"add": "add", "sub": "sub", "mul": "mul", "div": "div", "mod": "modulo"}
11
+
12
+
13
+ def key_readers(plan: dict) -> dict[str, list[str]]:
14
+ readers: dict[str, set[str]] = {}
15
+ for rule in plan["rules"]:
16
+ for atom in rule.get("atoms") or []:
17
+ readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
18
+ for check in rule.get("checks") or []:
19
+ readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
20
+ return {source: sorted(consumers) for source, consumers in sorted(readers.items())}
21
+
22
+
23
+ def check_plan(plan: dict) -> None:
24
+ v = plan.get("planVersion")
25
+ if v != SUPPORTED_PLAN:
26
+ raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержана {SUPPORTED_PLAN})")
27
+
28
+
29
+ def term(t: dict) -> str:
30
+ k = t["k"]
31
+ if k == "var":
32
+ return f'r{t["row"]}[{t["col"]}].clone()'
33
+ if k == "lit":
34
+ if t["t"] == "money":
35
+ return f'rt::money("{t["v"]}", "{t["cur"]}")'
36
+ if t["t"] == "dec":
37
+ return f'rt::dec("{t["v"]}")'
38
+ if t["t"] == "date":
39
+ return f'rt::date("{t["v"]}")'
40
+ if t["t"] == "enum":
41
+ return f'rt::sym("{t["v"]}")'
42
+ return f'rt::text({json.dumps(t["v"], ensure_ascii=False)})'
43
+ if k == "bin":
44
+ return f'rt::{OPS[t["op"]]}(&{term(t["l"])}, &{term(t["r"])})?'
45
+ if k == "call":
46
+ args = t["args"]
47
+ if t["fn"] == "round":
48
+ return f'rt::round(&{term(args[0])}, &{term(args[1])}, {json.dumps(args[2]["v"])})?'
49
+ return f'rt::{t["fn"]}(' + ", ".join(f"&{term(a)}" for a in args) + ')?'
50
+ raise AssertionError(k)
51
+
52
+
53
+ def emit(plan: dict) -> str:
54
+ check_plan(plan)
55
+ L = ["#![allow(unused_variables, unused_imports, clippy::all)]",
56
+ "use crate::rt::{self, Val, R};", ""]
57
+ L.append("pub const PREDS: &[&str] = &[")
58
+ for t in plan["tables"]:
59
+ L.append(f' {json.dumps(t["pred"])},')
60
+ L.append("];")
61
+ L.append("")
62
+ L.append("/// Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
63
+ L.append("pub fn params(pred: &str) -> Option<&'static [&'static str]> {")
64
+ L.append(" match pred {")
65
+ for t in plan["tables"]:
66
+ L.append(f' {json.dumps(t["pred"])} => Some(&[' + ", ".join(json.dumps(p) for p in t["params"]) + "]),")
67
+ L.append(" _ => None,")
68
+ L.append(" }")
69
+ L.append("}")
70
+ L.append("")
71
+ L.append("/// Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
72
+ L.append("pub fn consts(id: &str) -> Option<(&'static str, Val)> {")
73
+ L.append(" match id {")
74
+ for cid, c in plan["consts"].items():
75
+ L.append(f' {json.dumps(cid)} => Some(({json.dumps(c["type"])}, {term(c["term"])})),')
76
+ L.append(" _ => None,")
77
+ L.append(" }")
78
+ L.append("}")
79
+ L.append("")
80
+ L.append("/// Ключи отношений §45.1: позиции столбцов каждого ключа.")
81
+ L.append("pub const KEYS: &[(&str, &[&[usize]])] = &[")
82
+ for t in plan["tables"]:
83
+ if t["keys"]:
84
+ L.append(f' ({json.dumps(t["pred"])}, &[' + ", ".join("&[" + ", ".join(f"{i}usize" for i in k) + "]" for k in t["keys"]) + "]),")
85
+ L.append("];")
86
+ L.append("pub const KEY_READERS: &[(&str, &[&str])] = &[")
87
+ for source, consumers in key_readers(plan).items():
88
+ L.append(f' ({json.dumps(source)}, &[' + ", ".join(json.dumps(value) for value in consumers) + "]),")
89
+ L.append("];")
90
+ L.append("")
91
+ L.append("/// KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
92
+ L.append("pub fn key_conflicts(db: &rt::Db) -> usize {")
93
+ L.append(" let mut n = 0;")
94
+ L.append(" for (pred, keys) in KEYS {")
95
+ L.append(" for key in keys.iter() {")
96
+ L.append(" let mut groups: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();")
97
+ L.append(" for row in db.rows(pred) {")
98
+ L.append(" let proj: Vec<Val> = key.iter().map(|&i| row[i].clone()).collect();")
99
+ L.append(" *groups.entry(rt::key(&proj)).or_insert(0) += 1;")
100
+ L.append(" }")
101
+ L.append(" n += groups.values().filter(|&&c| c > 1).count();")
102
+ L.append(" }")
103
+ L.append(" }")
104
+ L.append(" n")
105
+ L.append("}")
106
+ L.append("pub fn key_conflict_predicates(db: &rt::Db) -> std::collections::BTreeSet<String> {")
107
+ L.append(" let mut out = std::collections::BTreeSet::new();")
108
+ L.append(" for (pred, keys) in KEYS { for key in keys.iter() { let mut groups: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new(); for row in db.rows(pred) { let proj: Vec<Val> = key.iter().map(|&i| row[i].clone()).collect(); *groups.entry(rt::key(&proj)).or_insert(0) += 1; } if groups.values().any(|&c| c > 1) { out.insert((*pred).to_string()); } } }")
109
+ L.append(" out")
110
+ L.append("}")
111
+ L.append("")
112
+ L.append("/// Наследование сущностей: подтип подходит параметру базового типа.")
113
+ L.append("pub fn base(t: &str) -> Option<&'static str> {")
114
+ L.append(" match t {")
115
+ for sub, base in plan["bases"].items():
116
+ L.append(f' {json.dumps(sub)} => Some({json.dumps(base)}),')
117
+ L.append(" _ => None,")
118
+ L.append(" }")
119
+ L.append("}")
120
+ L.append("")
121
+ for r in plan["rules"]:
122
+ L.append(f'// {r["id"]}')
123
+ L.append(f'fn rule_{r["name"]}(db: &mut rt::Db) -> R<bool> {{')
124
+ L.append(" let mut grew = false;")
125
+ if r["window"]:
126
+ if r["window"].get("start"):
127
+ L.append(f' if db.legal_time()?.as_str() < "{r["window"]["start"]}" {{ return Ok(false); }}')
128
+ if r["window"].get("end"):
129
+ L.append(f' if db.legal_time()?.as_str() >= "{r["window"]["end"]}" {{ return Ok(false); }}')
130
+ ind = " "
131
+ for i, a in enumerate(r["atoms"]):
132
+ L.append(f'{ind}let t{i} = db.rows({json.dumps(a["pred"])}).to_vec();')
133
+ L.append(f'{ind}for r{i} in &t{i} {{')
134
+ ind += " "
135
+ for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
136
+ L.append(f'{ind}if !rt::cmp(&r{ai}[{aj}], "eq", &r{bi}[{bj}])? {{ continue; }}')
137
+ for f in r["filters"]:
138
+ L.append(f'{ind}if !rt::eq(&r{f["row"]}[{f["col"]}], &{term(f["term"])}) {{ continue; }}')
139
+ for g in r["guards"]:
140
+ L.append(f'{ind}if !rt::cmp(&{term(g["l"])}, "{g["op"]}", &{term(g["r"])})? {{ continue; }}')
141
+ args = ", ".join(term(a) for a in r["head"]["args"])
142
+ L.append(f'{ind}if db.add({json.dumps(r["head"]["pred"])}, vec![{args}]) {{ grew = true; }}')
143
+ for _ in r["atoms"]:
144
+ ind = ind[:-4]
145
+ L.append(ind + "}")
146
+ L.append(" Ok(grew)")
147
+ L.append("}")
148
+ L.append("")
149
+ names = [r["name"] for r in plan["rules"]]
150
+ L.append("pub fn saturate(db: &mut rt::Db) -> R<()> {")
151
+ L.append(" loop {")
152
+ L.append(" let mut grew = false;")
153
+ for n in names:
154
+ L.append(f" if rule_{n}(db)? {{ grew = true; }}")
155
+ L.append(" if !grew { break; }")
156
+ L.append(" }")
157
+ L.append(" Ok(())")
158
+ L.append("}")
159
+ L.append("")
160
+ L.append("pub fn package_facts(db: &mut rt::Db) -> R<()> {")
161
+ for f in plan["facts"]:
162
+ L.append(f' db.add({json.dumps(f["pred"])}, vec![' + ", ".join(term(a) for a in f["args"]) + "]);")
163
+ L.append(" Ok(())")
164
+ L.append("}")
165
+ return "\n".join(L) + "\n"
166
+
167
+
168
+ # --- дескриптор цели ---------------------------------------------------------
169
+ HERE = Path(__file__).resolve().parent
170
+ EXT = "rs"
171
+ MODEL = "src/model.rs"
172
+ MAINS = {"runner": "runner.rs", "cert": "cert.rs"}
173
+ CARGO = '''[package]
174
+ name = "{name}"
175
+ version = "0.1.0"
176
+ edition = "2021"
177
+
178
+ [[bin]]
179
+ name = "{name}"
180
+ path = "src/main.rs"
181
+
182
+ [dependencies]
183
+ num-bigint = "0.4"
184
+ num-integer = "0.1"
185
+ num-traits = "0.2"
186
+ serde_json = { version = "1", features = ["arbitrary_precision"] }
187
+
188
+ [profile.dev]
189
+ opt-level = 1
190
+ '''
191
+
192
+
193
+ def available():
194
+ cargo = shutil.which("cargo")
195
+ return (True, cargo) if cargo else (False, "нет cargo в PATH")
196
+
197
+
198
+ def crate_name(proj) -> str:
199
+ """Имя крейта и бинаря — по каталогу вектора: общий `--target-dir` иначе
200
+ складывал бы все модели в один бинарь и запускал последнюю собранную."""
201
+ import re as _re
202
+ return "law_" + _re.sub(r"[^a-z0-9]", "_", Path(proj).parent.name.lower())
203
+
204
+
205
+ def layout(proj, main: str) -> list:
206
+ proj = Path(proj)
207
+ (proj / "src").mkdir(parents=True, exist_ok=True)
208
+ out = []
209
+ shutil.copyfile(HERE / "runtime.rs", proj / "src" / "rt.rs")
210
+ shutil.copyfile(HERE / MAINS[main], proj / "src" / "main.rs")
211
+ (proj / "Cargo.toml").write_text(CARGO.replace("{name}", crate_name(proj)), encoding="utf-8")
212
+ out += [proj / "src" / "rt.rs", proj / "src" / "main.rs", proj / "Cargo.toml"]
213
+ return out
214
+
215
+
216
+ def build(proj):
217
+ """Общий `--target-dir` на все проекты рабочего каталога: зависимости
218
+ компилируются один раз, модели — по отдельности."""
219
+ proj = Path(proj)
220
+ target = proj.parents[1] / "rust-target"
221
+ return ["cargo", "build", "-q", "--offline", "--target-dir", str(target)], str(proj)
222
+
223
+
224
+ def run(proj, main: str):
225
+ proj = Path(proj)
226
+ return [str(proj.parents[1] / "rust-target" / "debug" / crate_name(proj))], None
227
+
228
+
229
+ if __name__ == "__main__":
230
+ sys.path.insert(0, str(HERE.parents[1]))
231
+ import plan as P
232
+ ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
233
+ Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
@@ -0,0 +1 @@
1
+ //! Empty target for the locked CI dependency-prefetch manifest.
@@ -0,0 +1,181 @@
1
+ //! Раннер Rust-бэкенда: тот же вход и тот же ответ, что у runner.py.
2
+ //!
3
+ //! Протокол выхода общий для всех целей: ответ — JSON и код 0; названный отказ
4
+ //! среза — JSON {"refused": текст} и код 3; любой другой код — крах.
5
+ mod model;
6
+ mod rt;
7
+
8
+ use rt::{Refused, Val, R};
9
+ use serde_json::{json, Value};
10
+
11
+ fn lit_text(v: &Value) -> String {
12
+ match v {
13
+ Value::String(s) => s.clone(),
14
+ Value::Number(n) => n.to_string(),
15
+ other => other.to_string(),
16
+ }
17
+ }
18
+
19
+ fn arg(a: &Value) -> R<Val> {
20
+ let kind = a["kind"].as_str().unwrap_or("");
21
+ if kind == "entity_ref" {
22
+ return Ok(rt::text(a["id"].as_str().unwrap_or("")));
23
+ }
24
+ if kind == "const_ref" {
25
+ // §54: разыменование предшествует исполнению; член перечисления §38 либо
26
+ // имя без объявления остаётся идентичностью, как в ключе атома оракула.
27
+ let id = a["id"].as_str().unwrap_or("");
28
+ return Ok(match model::consts(id) {
29
+ Some((_, v)) => v,
30
+ None => rt::sym(id),
31
+ });
32
+ }
33
+ if kind != "value" {
34
+ return Err(Refused(format!("аргумент вне среза раннера: {kind}")));
35
+ }
36
+ let t = a["type"]["name"].as_str().unwrap_or("");
37
+ match t {
38
+ "urn:law:std#Money" => Ok(rt::money(&lit_text(&a["value"]), a["currency"].as_str().unwrap_or(""))),
39
+ "urn:law:std#Decimal" | "urn:law:std#Integer" => Ok(rt::dec(&lit_text(&a["value"]))),
40
+ "urn:law:std#Text" => Ok(rt::text(&lit_text(&a["value"]))),
41
+ "urn:law:std#Date" => Ok(rt::date(&lit_text(&a["value"]))),
42
+ _ => Err(Refused(format!("аргумент вне среза раннера: {t}"))),
43
+ }
44
+ }
45
+
46
+ /// Тип аргумента, если известен: у value — из узла, у const_ref — из объявления
47
+ /// константы; entity_ref типа не несёт (как у оракула).
48
+ fn arg_type(a: &Value) -> Option<String> {
49
+ match a["kind"].as_str() {
50
+ Some("value") => a["type"]["name"].as_str().map(str::to_string),
51
+ Some("const_ref") => model::consts(a["id"].as_str().unwrap_or("")).map(|(t, _)| t.to_string()),
52
+ _ => None,
53
+ }
54
+ }
55
+
56
+ /// Совпадение типа с наследованием сущностей вверх от фактического.
57
+ fn conforms(actual: &str, expected: &str) -> bool {
58
+ let mut seen = std::collections::BTreeSet::new();
59
+ let mut cur = Some(actual.to_string());
60
+ while let Some(c) = cur {
61
+ if c == expected {
62
+ return true;
63
+ }
64
+ if !seen.insert(c.clone()) {
65
+ break;
66
+ }
67
+ cur = model::base(&c).map(str::to_string);
68
+ }
69
+ false
70
+ }
71
+
72
+ fn args_of(lit: &Value) -> Vec<Value> {
73
+ lit["args"].as_array().cloned().unwrap_or_default()
74
+ }
75
+
76
+ /// Арность и типы аргументов утверждения против сигнатуры §189 (E-0046).
77
+ fn well_typed(lit: &Value, issues: &mut Vec<String>) -> bool {
78
+ let Some(params) = model::params(lit["predicate"].as_str().unwrap_or("")) else { return true };
79
+ let args = args_of(lit);
80
+ if args.len() != params.len() {
81
+ issues.push("TYPE_ERROR".into());
82
+ return false;
83
+ }
84
+ for (expected, a) in params.iter().zip(args.iter()) {
85
+ if let Some(actual) = arg_type(a) {
86
+ if !conforms(&actual, expected) {
87
+ issues.push("TYPE_ERROR".into());
88
+ return false;
89
+ }
90
+ }
91
+ }
92
+ true
93
+ }
94
+
95
+ /// Ключ атома у оракула §208 включает ТИП значения: несогласованный запрос
96
+ /// атома не находит — NEITHER без issue.
97
+ fn query_typed(lit: &Value) -> bool {
98
+ let Some(params) = model::params(lit["predicate"].as_str().unwrap_or("")) else { return false };
99
+ let args = args_of(lit);
100
+ if args.len() != params.len() {
101
+ return false;
102
+ }
103
+ params.iter().zip(args.iter()).all(|(expected, a)| match arg_type(a) {
104
+ Some(actual) => conforms(&actual, expected),
105
+ None => true,
106
+ })
107
+ }
108
+
109
+ fn key_conflict_blocks(db: &rt::Db, predicate: &str) -> bool {
110
+ let mut tainted = model::key_conflict_predicates(db);
111
+ let mut waiting: Vec<String> = tainted.iter().cloned().collect();
112
+ while let Some(source) = waiting.pop() {
113
+ for (reader, consumers) in model::KEY_READERS {
114
+ if *reader == source {
115
+ for consumer in *consumers {
116
+ if tainted.insert((*consumer).to_string()) { waiting.push((*consumer).to_string()); }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ tainted.contains(predicate)
122
+ }
123
+
124
+ fn run(case: Value, query: Value) -> R<Value> {
125
+ let mut db = rt::Db::new(model::PREDS);
126
+ let mut issues: Vec<String> = Vec::new();
127
+ db.set_legal_time(case["context"]["legalTime"].as_str().unwrap_or(""));
128
+ model::package_facts(&mut db)?;
129
+ for a in case["assertions"].as_array().cloned().unwrap_or_default() {
130
+ let lit = &a["literal"];
131
+ if lit["polarity"].as_str() != Some("positive") {
132
+ return Err(Refused("§113: отрицательный факт вне L0-среза".into()));
133
+ }
134
+ if !well_typed(lit, &mut issues) {
135
+ continue;
136
+ }
137
+ let pred = lit["predicate"].as_str().unwrap_or("");
138
+ if !db.has(pred) {
139
+ continue;
140
+ }
141
+ let row = args_of(lit).iter().map(arg).collect::<R<Vec<Val>>>()?;
142
+ db.add(pred, row);
143
+ }
144
+ model::saturate(&mut db)?;
145
+ for _ in 0..model::key_conflicts(&db) {
146
+ issues.push("KEY_CONFLICT".into());
147
+ }
148
+ if query["kind"].as_str() != Some("truth") {
149
+ return Err(Refused(format!("вид запроса вне среза раннера: {}", lit_text(&query["kind"]))));
150
+ }
151
+ let lit = &query["literal"];
152
+ let mut hit = false;
153
+ if query_typed(lit) {
154
+ let want = args_of(lit).iter().map(arg).collect::<R<Vec<Val>>>()?;
155
+ let pred = lit["predicate"].as_str().unwrap_or("");
156
+ hit = db
157
+ .rows(pred)
158
+ .iter()
159
+ .any(|r| r.len() == want.len() && r.iter().zip(want.iter()).all(|(v, w)| rt::eq(v, w)));
160
+ }
161
+ // Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
162
+ Ok(json!({
163
+ "queryId": query["queryId"],
164
+ "truthStatus": if hit { "TRUE_ONLY" } else { "NEITHER" },
165
+ "evaluationStatus": if key_conflict_blocks(&db, lit["predicate"].as_str().unwrap_or("")) { "NON_EXECUTABLE" } else { "COMPUTED" },
166
+ "issues": issues,
167
+ }))
168
+ }
169
+
170
+ fn main() {
171
+ let argv: Vec<String> = std::env::args().collect();
172
+ let case: Value = serde_json::from_str(&std::fs::read_to_string(&argv[1]).expect("case")).expect("case json");
173
+ let query: Value = serde_json::from_str(&std::fs::read_to_string(&argv[2]).expect("query")).expect("query json");
174
+ match run(case, query) {
175
+ Ok(out) => println!("{}", serde_json::to_string(&out).unwrap()),
176
+ Err(Refused(msg)) => {
177
+ println!("{}", serde_json::to_string(&json!({ "refused": msg })).unwrap());
178
+ std::process::exit(3);
179
+ }
180
+ }
181
+ }