arxo-codegen 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// Рантайм L0 для Kotlin (JVM). Тот же контракт, что у runtime.py: точная
|
|
2
|
+
// десятичная арифметика §58 на BigInteger, деньги с валютой, названный режим
|
|
3
|
+
// округления, календарный день, член перечисления. Отказ — исключение Refused;
|
|
4
|
+
// любое другое исключение — крах.
|
|
5
|
+
import java.math.BigInteger
|
|
6
|
+
|
|
7
|
+
class Refused(m: String) : RuntimeException(m)
|
|
8
|
+
|
|
9
|
+
open class Dec(val u: BigInteger, val s: Int)
|
|
10
|
+
class Money(u: BigInteger, s: Int, val cur: String) : Dec(u, s)
|
|
11
|
+
// Календарный день §2.15 в канонической записи ISO YYYY-MM-DD.
|
|
12
|
+
class Date(val v: String)
|
|
13
|
+
// Член перечисления §38 либо ссылка const_ref без объявления.
|
|
14
|
+
class Sym(val id: String)
|
|
15
|
+
|
|
16
|
+
object RT {
|
|
17
|
+
private fun p10(n: Int): BigInteger = BigInteger.TEN.pow(n)
|
|
18
|
+
|
|
19
|
+
fun dec(lit: String): Dec {
|
|
20
|
+
val dot = lit.indexOf('.')
|
|
21
|
+
val i = if (dot < 0) lit else lit.substring(0, dot)
|
|
22
|
+
val f = if (dot < 0) "" else lit.substring(dot + 1)
|
|
23
|
+
val neg = i.startsWith("-")
|
|
24
|
+
val u = BigInteger((if (neg) i.substring(1) else i) + f)
|
|
25
|
+
return Dec(if (neg) u.negate() else u, f.length)
|
|
26
|
+
}
|
|
27
|
+
fun money(lit: String, cur: String): Money { val d = dec(lit); return Money(d.u, d.s, cur) }
|
|
28
|
+
fun date(lit: String) = Date(lit)
|
|
29
|
+
fun sym(id: String) = Sym(id)
|
|
30
|
+
|
|
31
|
+
// --- виды ---------------------------------------------------------------
|
|
32
|
+
// §48/§58: сложение, вычитание, min/max требуют совпадения видов; у денег — и валюты.
|
|
33
|
+
private fun same(a: Any?, b: Any?, op: String) {
|
|
34
|
+
if (a !is Dec || b !is Dec || (a is Money) != (b is Money)) throw Refused("§58: виды операндов не совпадают: $op")
|
|
35
|
+
if (a is Money && b is Money && a.cur != b.cur) throw Refused("§58: разные валюты: $op")
|
|
36
|
+
}
|
|
37
|
+
private fun pair(a: Dec, b: Dec): Triple<BigInteger, BigInteger, Int> {
|
|
38
|
+
val s = maxOf(a.s, b.s)
|
|
39
|
+
return Triple(a.u * p10(s - a.s), b.u * p10(s - b.s), s)
|
|
40
|
+
}
|
|
41
|
+
private fun mk(u: BigInteger, s: Int, like: Any?): Dec = if (like is Money) Money(u, s, like.cur) else Dec(u, s)
|
|
42
|
+
// Целое значение: дробная часть нулевая (защита в глубину под статикой плана).
|
|
43
|
+
private fun integral(v: Any?): BigInteger? {
|
|
44
|
+
if (v !is Dec || v is Money) return null
|
|
45
|
+
val qr = v.u.divideAndRemainder(p10(v.s))
|
|
46
|
+
return if (qr[1].signum() == 0) qr[0] else null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- арифметика §58 -----------------------------------------------------
|
|
50
|
+
fun add(a: Any?, b: Any?): Dec { same(a, b, "add"); val (x, y, s) = pair(a as Dec, b as Dec); return mk(x + y, s, a) }
|
|
51
|
+
fun sub(a: Any?, b: Any?): Dec { same(a, b, "sub"); val (x, y, s) = pair(a as Dec, b as Dec); return mk(x - y, s, a) }
|
|
52
|
+
|
|
53
|
+
// Правило скаляра §49: безразмерное масштабирует размерное; масштаб результата —
|
|
54
|
+
// сумма масштабов. Два размерных не перемножаются.
|
|
55
|
+
fun mul(a: Any?, b: Any?): Dec {
|
|
56
|
+
if (a !is Dec || b !is Dec) throw Refused("§58: виды операндов не совпадают: mul")
|
|
57
|
+
if (a is Money && b is Money) throw Refused("§58: произведение двух размерных величин не определено")
|
|
58
|
+
return mk(a.u * b.u, a.s + b.s, if (a is Money) a else b)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Точное десятичное частное num/den при 10-гладком знаменателе (§18), иначе null.
|
|
62
|
+
private fun exact(num0: BigInteger, den0: BigInteger): Pair<BigInteger, Int>? {
|
|
63
|
+
var g = num0.gcd(den0)
|
|
64
|
+
if (g.signum() == 0) g = BigInteger.ONE
|
|
65
|
+
var num = num0 / g
|
|
66
|
+
var den = den0 / g
|
|
67
|
+
if (den.signum() < 0) { num = num.negate(); den = den.negate() }
|
|
68
|
+
val two = BigInteger.TWO; val five = BigInteger.valueOf(5)
|
|
69
|
+
var twos = 0; var fives = 0
|
|
70
|
+
while (den.mod(two).signum() == 0) { den /= two; twos++ }
|
|
71
|
+
while (den.mod(five).signum() == 0) { den /= five; fives++ }
|
|
72
|
+
if (den != BigInteger.ONE) return null
|
|
73
|
+
val k = maxOf(twos, fives)
|
|
74
|
+
return Pair(num * two.pow(k - twos) * five.pow(k - fives), k)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Таблица делимых §58 в срезе: Decimal/Decimal, Money/Decimal, Money/Money одной
|
|
78
|
+
// валюты. Integer/Integer даёт Rational — вне среза, отвергается планом статически.
|
|
79
|
+
fun div(a: Any?, b: Any?): Dec {
|
|
80
|
+
if (a !is Dec || b !is Dec) throw Refused("§58: деление вне таблицы делимых")
|
|
81
|
+
if (b.u.signum() == 0) throw Refused("деление на ноль — evaluation error (§58)")
|
|
82
|
+
var like: Money? = null
|
|
83
|
+
if (a is Money && b is Money) {
|
|
84
|
+
if (a.cur != b.cur) throw Refused("CURRENCY_MISMATCH: §58 делит Money на Money только при тождественной валюте")
|
|
85
|
+
} else if (a is Money) like = a
|
|
86
|
+
else if (b is Money) throw Refused("§58: деление вне таблицы делимых")
|
|
87
|
+
val q = exact(a.u * p10(b.s), b.u * p10(a.s))
|
|
88
|
+
?: throw Refused(if (like != null) "INEXACT_DIVISION: округление только явной политикой (§50/§18)" else "§58: частное не 10-гладкое — Rational вне среза")
|
|
89
|
+
return if (like != null) Money(q.first, q.second, like.cur) else Dec(q.first, q.second)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Евклидов остаток §58: 0 <= r < |b| при любых знаках операндов.
|
|
93
|
+
fun mod(a: Any?, b: Any?): Dec {
|
|
94
|
+
val x = integral(a); val y = integral(b)
|
|
95
|
+
if (x == null || y == null) throw Refused("§58: mod определён только для Integer")
|
|
96
|
+
if (y.signum() == 0) throw Refused("остаток от деления на ноль — evaluation error (§58)")
|
|
97
|
+
return Dec(x.mod(y.abs()), 0)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fun min(a: Any?, b: Any?): Dec { same(a, b, "min"); val (x, y, _) = pair(a as Dec, b as Dec); return if (x <= y) a else b as Dec }
|
|
101
|
+
fun max(a: Any?, b: Any?): Dec { same(a, b, "max"); val (x, y, _) = pair(a as Dec, b as Dec); return if (x >= y) a else b as Dec }
|
|
102
|
+
|
|
103
|
+
fun abs(v: Any?): Dec {
|
|
104
|
+
if (v !is Dec) throw Refused("§58: abs — не числовой вид")
|
|
105
|
+
return mk(v.u.abs(), v.s, v)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private val POW_MIN = BigInteger.valueOf(Long.MIN_VALUE)
|
|
109
|
+
private val POW_MAX = BigInteger.valueOf(Long.MAX_VALUE)
|
|
110
|
+
|
|
111
|
+
// §58 (E-0075): только Integer pow Integer; порядок проверок — знак, предел,
|
|
112
|
+
// вычисление, диапазон — часть контракта.
|
|
113
|
+
fun pow(base: Any?, exp: Any?): Dec {
|
|
114
|
+
val x = integral(base); val e = integral(exp)
|
|
115
|
+
if (x == null || e == null) throw Refused("§58: pow определён только для Integer")
|
|
116
|
+
if (e.signum() < 0) throw Refused("§58: pow — показатель отрицателен")
|
|
117
|
+
if (e > BigInteger.valueOf(4096)) throw Refused("RESOURCE_LIMIT: pow — показатель превышает 4096")
|
|
118
|
+
val r = x.pow(e.toInt())
|
|
119
|
+
if (r < POW_MIN || r > POW_MAX) throw Refused("RESOURCE_LIMIT: pow — результат вне 64-битного диапазона")
|
|
120
|
+
return Dec(r, 0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fun round(m: Any?, digits: Any?, mode: String): Dec {
|
|
124
|
+
if (mode != "HALF_UP") throw Refused("§50: режим округления не реализован: $mode")
|
|
125
|
+
if (m !is Dec) throw Refused("§50: round — не числовой вид")
|
|
126
|
+
val n = if (digits is Dec) (digits.u / p10(digits.s)).toInt() else 0
|
|
127
|
+
if (m.s <= n) return mk(m.u * p10(n - m.s), n, m)
|
|
128
|
+
val drop = p10(m.s - n)
|
|
129
|
+
val neg = m.u.signum() < 0
|
|
130
|
+
val qr = m.u.abs().divideAndRemainder(drop)
|
|
131
|
+
val up = if (qr[1].shiftLeft(1) >= drop) qr[0] + BigInteger.ONE else qr[0] // HALF_UP — от нуля
|
|
132
|
+
return mk(if (neg) up.negate() else up, n, m)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- сравнение и равенство -----------------------------------------------
|
|
136
|
+
private fun ordered(op: String, c: Int): Boolean = when (op) {
|
|
137
|
+
"lt" -> c < 0; "le" -> c <= 0; "gt" -> c > 0; "ge" -> c >= 0; "eq" -> c == 0; "ne" -> c != 0
|
|
138
|
+
else -> throw Refused("сравнение вне среза рантайма: $op")
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fun cmp(a: Any?, op: String, b: Any?): Boolean {
|
|
142
|
+
if (a is Sym || b is Sym) {
|
|
143
|
+
val same = a is Sym && b is Sym && a.id == b.id
|
|
144
|
+
return when (op) { "eq" -> same; "ne" -> !same; else -> throw Refused("§57: вид enum не упорядочен") }
|
|
145
|
+
}
|
|
146
|
+
if (a is Date || b is Date) {
|
|
147
|
+
if (a is Date && b is Date) return ordered(op, a.v.compareTo(b.v))
|
|
148
|
+
return when (op) { "eq" -> false; "ne" -> true; else -> throw Refused("§57: Date сравнивается только с Date") }
|
|
149
|
+
}
|
|
150
|
+
if (a is String || b is String) {
|
|
151
|
+
val same = a is String && b is String && a == b
|
|
152
|
+
return when (op) { "eq" -> same; "ne" -> !same; else -> throw Refused("порядок на Text вне среза рантайма: $op") }
|
|
153
|
+
}
|
|
154
|
+
if ((a is Money) != (b is Money)) throw Refused("§49: сравнение денег с безразмерным")
|
|
155
|
+
if (a is Money && b is Money && a.cur != b.cur) throw Refused("§49: разные валюты")
|
|
156
|
+
val (x, y, _) = pair(a as Dec, b as Dec)
|
|
157
|
+
return ordered(op, x.compareTo(y))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fun eq(a: Any?, b: Any?): Boolean {
|
|
161
|
+
if (a is Sym || b is Sym) return a is Sym && b is Sym && a.id == b.id
|
|
162
|
+
if (a is Date || b is Date) return a is Date && b is Date && a.v == b.v
|
|
163
|
+
if (a is String || b is String) return a is String && b is String && a == b
|
|
164
|
+
if ((a is Money) != (b is Money)) return false
|
|
165
|
+
if (a is Money && b is Money && a.cur != b.cur) return false
|
|
166
|
+
val (x, y, _) = pair(a as Dec, b as Dec)
|
|
167
|
+
return x == y
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun norm(d: Dec): String {
|
|
171
|
+
var u = d.u; var s = d.s
|
|
172
|
+
while (s > 0 && u.mod(BigInteger.TEN).signum() == 0) { u /= BigInteger.TEN; s-- }
|
|
173
|
+
return "${u}e-$s"
|
|
174
|
+
}
|
|
175
|
+
// Разделитель и экран внутри текста экранируются: иначе ["a|e:b","c"] и
|
|
176
|
+
// ["a","b|e:c"] склеивались в один ключ, и второй факт терялся.
|
|
177
|
+
private fun esc(t: String) = t.replace("\\", "\\\\").replace("|", "\\|")
|
|
178
|
+
|
|
179
|
+
fun key(row: List<Any?>): String = row.joinToString("|") { v ->
|
|
180
|
+
when (v) {
|
|
181
|
+
is String -> "e:" + esc(v)
|
|
182
|
+
is Date -> "t:" + v.v
|
|
183
|
+
is Sym -> "c:" + esc(v.id)
|
|
184
|
+
is Money -> "m:" + v.cur + ":" + norm(v)
|
|
185
|
+
is Dec -> "d:" + norm(v)
|
|
186
|
+
else -> throw IllegalStateException("ключ: вид $v")
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private var legalTimeValue = ""
|
|
191
|
+
fun setLegalTime(t: String) { legalTimeValue = t }
|
|
192
|
+
fun legalTime(): String {
|
|
193
|
+
if (legalTimeValue.isEmpty()) throw Refused("legalTime не подан делом")
|
|
194
|
+
return legalTimeValue
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Эмиттер Kotlin (JVM). Модель — объект `Model`; сборка `kotlinc` в jar с
|
|
3
|
+
рантаймом Kotlin внутри, запуск `java -jar`."""
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
SUPPORTED_PLAN = "law-backend-plan/0.7"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_plan(plan: dict) -> None:
|
|
13
|
+
v = plan.get("planVersion")
|
|
14
|
+
if v != SUPPORTED_PLAN:
|
|
15
|
+
raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержана {SUPPORTED_PLAN})")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def jstr(s: str) -> str:
|
|
19
|
+
return json.dumps(s, ensure_ascii=False).replace("$", "\\$")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def key_readers(plan: dict) -> dict[str, list[str]]:
|
|
23
|
+
readers: dict[str, set[str]] = {}
|
|
24
|
+
for rule in plan["rules"]:
|
|
25
|
+
for atom in rule.get("atoms") or []:
|
|
26
|
+
readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
|
|
27
|
+
for check in rule.get("checks") or []:
|
|
28
|
+
readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
|
|
29
|
+
return {source: sorted(consumers) for source, consumers in sorted(readers.items())}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def term(t: dict) -> str:
|
|
33
|
+
k = t["k"]
|
|
34
|
+
if k == "var":
|
|
35
|
+
return f'r{t["row"]}[{t["col"]}]'
|
|
36
|
+
if k == "lit":
|
|
37
|
+
if t["t"] == "money":
|
|
38
|
+
return f'RT.money({jstr(t["v"])}, {jstr(t["cur"])})'
|
|
39
|
+
if t["t"] == "dec":
|
|
40
|
+
return f'RT.dec({jstr(t["v"])})'
|
|
41
|
+
if t["t"] == "date":
|
|
42
|
+
return f'RT.date({jstr(t["v"])})'
|
|
43
|
+
if t["t"] == "enum":
|
|
44
|
+
return f'RT.sym({jstr(t["v"])})'
|
|
45
|
+
return jstr(t["v"])
|
|
46
|
+
if k == "bin":
|
|
47
|
+
return f'RT.{t["op"]}({term(t["l"])}, {term(t["r"])})'
|
|
48
|
+
if k == "call":
|
|
49
|
+
args = t["args"]
|
|
50
|
+
if t["fn"] == "round":
|
|
51
|
+
return f'RT.round({term(args[0])}, {term(args[1])}, {jstr(args[2]["v"])})'
|
|
52
|
+
return f'RT.{t["fn"]}(' + ", ".join(term(a) for a in args) + ')'
|
|
53
|
+
raise AssertionError(k)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def emit(plan: dict) -> str:
|
|
57
|
+
check_plan(plan)
|
|
58
|
+
L = ["object Model {",
|
|
59
|
+
" val TABLES = LinkedHashMap<String, ArrayList<List<Any?>>>()",
|
|
60
|
+
" // Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).",
|
|
61
|
+
" val PARAMS = LinkedHashMap<String, List<String>>()",
|
|
62
|
+
" // Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.",
|
|
63
|
+
" val CONSTS = LinkedHashMap<String, Pair<String, Any>>()",
|
|
64
|
+
" // Наследование сущностей: подтип подходит параметру базового типа.",
|
|
65
|
+
" val BASES = LinkedHashMap<String, String>()",
|
|
66
|
+
" // Ключи отношений §45.1: позиции столбцов каждого ключа.",
|
|
67
|
+
" val KEYS = LinkedHashMap<String, List<List<Int>>>()",
|
|
68
|
+
" val KEY_READERS = LinkedHashMap<String, List<String>>()",
|
|
69
|
+
" private val seen = HashSet<String>()",
|
|
70
|
+
"",
|
|
71
|
+
" init {"]
|
|
72
|
+
for t in plan["tables"]:
|
|
73
|
+
L.append(f' TABLES[{jstr(t["pred"])}] = ArrayList()')
|
|
74
|
+
L.append(f' PARAMS[{jstr(t["pred"])}] = listOf(' + ", ".join(jstr(p) for p in t["params"]) + ")")
|
|
75
|
+
for t in plan["tables"]:
|
|
76
|
+
if t["keys"]:
|
|
77
|
+
L.append(f' KEYS[{jstr(t["pred"])}] = listOf(' + ", ".join("listOf(" + ", ".join(str(i) for i in k) + ")" for k in t["keys"]) + ")")
|
|
78
|
+
for source, consumers in key_readers(plan).items():
|
|
79
|
+
L.append(f' KEY_READERS[{jstr(source)}] = listOf(' + ", ".join(jstr(value) for value in consumers) + ")")
|
|
80
|
+
for cid, c in plan["consts"].items():
|
|
81
|
+
L.append(f' CONSTS[{jstr(cid)}] = Pair({jstr(c["type"])}, {term(c["term"])})')
|
|
82
|
+
for sub, base in plan["bases"].items():
|
|
83
|
+
L.append(f' BASES[{jstr(sub)}] = {jstr(base)}')
|
|
84
|
+
L.append(" }")
|
|
85
|
+
L.append("")
|
|
86
|
+
L.append(" // KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
|
|
87
|
+
L.append(" fun keyConflicts(): Int {")
|
|
88
|
+
L.append(" var n = 0")
|
|
89
|
+
L.append(" for ((urn, keys) in KEYS) {")
|
|
90
|
+
L.append(" for (key in keys) {")
|
|
91
|
+
L.append(" val groups = HashMap<String, Int>()")
|
|
92
|
+
L.append(" for (row in TABLES[urn]!!) {")
|
|
93
|
+
L.append(" val p = RT.key(key.map { row[it] })")
|
|
94
|
+
L.append(" groups[p] = (groups[p] ?: 0) + 1")
|
|
95
|
+
L.append(" }")
|
|
96
|
+
L.append(" n += groups.values.count { it > 1 }")
|
|
97
|
+
L.append(" }")
|
|
98
|
+
L.append(" }")
|
|
99
|
+
L.append(" return n")
|
|
100
|
+
L.append(" }")
|
|
101
|
+
L.append(" fun keyConflictPredicates(): MutableSet<String> {")
|
|
102
|
+
L.append(" val out = HashSet<String>()")
|
|
103
|
+
L.append(" for ((urn, keys) in KEYS) for (key in keys) { val groups = HashMap<String, Int>(); for (row in TABLES[urn]!!) { val p = RT.key(key.map { row[it] }); groups[p] = (groups[p] ?: 0) + 1 }; if (groups.values.any { it > 1 }) out.add(urn) }")
|
|
104
|
+
L.append(" return out")
|
|
105
|
+
L.append(" }")
|
|
106
|
+
L.append("")
|
|
107
|
+
L.append(" private fun addRow(urn: String, row: List<Any?>): Boolean {")
|
|
108
|
+
L.append(' if (!seen.add(urn + "#" + RT.key(row))) return false')
|
|
109
|
+
L.append(" TABLES[urn]!!.add(row)")
|
|
110
|
+
L.append(" return true")
|
|
111
|
+
L.append(" }")
|
|
112
|
+
L.append("")
|
|
113
|
+
for r in plan["rules"]:
|
|
114
|
+
L.append(f' // {r["id"]}')
|
|
115
|
+
L.append(f' private fun rule_{r["name"]}(): Boolean {{')
|
|
116
|
+
L.append(" var grew = false")
|
|
117
|
+
if r["window"]:
|
|
118
|
+
if r["window"].get("start"):
|
|
119
|
+
L.append(f' if (RT.legalTime() < {jstr(r["window"]["start"])}) return false')
|
|
120
|
+
if r["window"].get("end"):
|
|
121
|
+
L.append(f' if (RT.legalTime() >= {jstr(r["window"]["end"])}) return false')
|
|
122
|
+
ind = " "
|
|
123
|
+
for i, a in enumerate(r["atoms"]):
|
|
124
|
+
L.append(f'{ind}for (r{i} in ArrayList(TABLES[{jstr(a["pred"])}]!!)) {{')
|
|
125
|
+
ind += " "
|
|
126
|
+
for (ai, aj, _at), (bi, bj, _bt) in r["joins"]:
|
|
127
|
+
L.append(f'{ind}if (!RT.cmp(r{ai}[{aj}], "eq", r{bi}[{bj}])) continue')
|
|
128
|
+
for f in r["filters"]:
|
|
129
|
+
L.append(f'{ind}if (!RT.eq(r{f["row"]}[{f["col"]}], {term(f["term"])})) continue')
|
|
130
|
+
for g in r["guards"]:
|
|
131
|
+
L.append(f'{ind}if (!RT.cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])})) continue')
|
|
132
|
+
args = ", ".join(term(a) for a in r["head"]["args"])
|
|
133
|
+
L.append(f'{ind}if (addRow({jstr(r["head"]["pred"])}, listOf({args}))) grew = true')
|
|
134
|
+
for _ in r["atoms"]:
|
|
135
|
+
ind = ind[:-4]
|
|
136
|
+
L.append(ind + "}")
|
|
137
|
+
L.append(" return grew")
|
|
138
|
+
L.append(" }")
|
|
139
|
+
L.append("")
|
|
140
|
+
names = [r["name"] for r in plan["rules"]]
|
|
141
|
+
L.append(" fun saturate() {")
|
|
142
|
+
L.append(" var grew = true")
|
|
143
|
+
L.append(" while (grew) {")
|
|
144
|
+
L.append(" grew = false")
|
|
145
|
+
for n in names:
|
|
146
|
+
L.append(f" if (rule_{n}()) grew = true")
|
|
147
|
+
L.append(" }")
|
|
148
|
+
L.append(" }")
|
|
149
|
+
L.append("")
|
|
150
|
+
L.append(" fun register(urn: String, row: List<Any?>) { addRow(urn, row) }")
|
|
151
|
+
L.append("")
|
|
152
|
+
L.append(" fun packageFacts() {")
|
|
153
|
+
for f in plan["facts"]:
|
|
154
|
+
L.append(f' addRow({jstr(f["pred"])}, listOf(' + ", ".join(term(a) for a in f["args"]) + "))")
|
|
155
|
+
L.append(" }")
|
|
156
|
+
L.append("}")
|
|
157
|
+
return "\n".join(L) + "\n"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# --- дескриптор цели ---------------------------------------------------------
|
|
161
|
+
HERE = Path(__file__).resolve().parent
|
|
162
|
+
EXT = "kt"
|
|
163
|
+
MODEL = "Model.kt"
|
|
164
|
+
MAINS = {"runner": "Runner.kt", "cert": "Cert.kt"}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def available():
|
|
168
|
+
kotlinc, java = shutil.which("kotlinc"), shutil.which("java")
|
|
169
|
+
return (True, kotlinc) if kotlinc and java else (False, "нет kotlinc/java в PATH")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def layout(proj, main: str) -> list:
|
|
173
|
+
proj = Path(proj)
|
|
174
|
+
proj.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
out = []
|
|
176
|
+
for name in ("Runtime.kt", "Json.kt", MAINS[main]):
|
|
177
|
+
shutil.copyfile(HERE / name, proj / name)
|
|
178
|
+
out.append(proj / name)
|
|
179
|
+
return out
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def build(proj):
|
|
183
|
+
proj = Path(proj)
|
|
184
|
+
return ["kotlinc", "-nowarn", "-include-runtime", "-d", "app.jar"] + sorted(p.name for p in proj.glob("*.kt")), str(proj)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def run(proj, main: str):
|
|
188
|
+
return ["java", "-Dfile.encoding=UTF-8", "-jar", str(Path(proj) / "app.jar")], None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
sys.path.insert(0, str(HERE.parents[1]))
|
|
193
|
+
import plan as P
|
|
194
|
+
ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
195
|
+
Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
-- Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
|
|
2
|
+
-- что получилось. Ожидания сверяют ворота, харнесс их не читает.
|
|
3
|
+
local dir = arg[0]:match("^(.*)/") or "."
|
|
4
|
+
package.path = dir .. "/?.lua;" .. package.path
|
|
5
|
+
local RT = require("runtime")
|
|
6
|
+
local J = require("json")
|
|
7
|
+
|
|
8
|
+
local function lit(x)
|
|
9
|
+
if type(x) == "string" then return x end
|
|
10
|
+
if x.dec ~= nil then return RT.dec(x.dec) end
|
|
11
|
+
if x.money ~= nil then return RT.money(x.money, x.cur) end
|
|
12
|
+
if x.text ~= nil then return x.text end
|
|
13
|
+
if x.date ~= nil then return RT.date(x.date) end
|
|
14
|
+
if x.sym ~= nil then return RT.sym(x.sym) end
|
|
15
|
+
if x.row ~= nil then
|
|
16
|
+
local row = {}
|
|
17
|
+
for i, v in ipairs(x.row) do row[i] = lit(v) end
|
|
18
|
+
return row
|
|
19
|
+
end
|
|
20
|
+
error("литерал фикстуры")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
-- Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
|
|
24
|
+
local function digits(u, s)
|
|
25
|
+
local body = RT.I.str(RT.I.absv(u))
|
|
26
|
+
if #body < s + 1 then body = string.rep("0", s + 1 - #body) .. body end
|
|
27
|
+
local text = body
|
|
28
|
+
if s > 0 then text = body:sub(1, #body - s) .. "." .. body:sub(#body - s + 1) end
|
|
29
|
+
return (u.neg and "-" or "") .. text
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
local function render(v)
|
|
33
|
+
if v == nil then return "null" end
|
|
34
|
+
if type(v) == "boolean" then return tostring(v) end
|
|
35
|
+
if type(v) == "string" then return J.str(v) end
|
|
36
|
+
if v.kind == "money" then return J.str(digits(v.u, v.s) .. " " .. v.cur) end
|
|
37
|
+
if v.kind == "date" then return J.str(v.v) end
|
|
38
|
+
if v.kind == "sym" then return J.str(v.id) end
|
|
39
|
+
return J.str(digits(v.u, v.s))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
local OPS = {
|
|
43
|
+
dec = function(a) return RT.dec(a[1]) end,
|
|
44
|
+
money = function(a) return RT.money(a[1], a[2]) end,
|
|
45
|
+
date = function(a) return RT.date(a[1]) end,
|
|
46
|
+
sym = function(a) return RT.sym(a[1]) end,
|
|
47
|
+
mul = function(a) return RT.mul(a[1], a[2]) end,
|
|
48
|
+
add = function(a) return RT.add(a[1], a[2]) end,
|
|
49
|
+
sub = function(a) return RT.sub(a[1], a[2]) end,
|
|
50
|
+
div = function(a) return RT.div(a[1], a[2]) end,
|
|
51
|
+
mod = function(a) return RT.mod(a[1], a[2]) end,
|
|
52
|
+
min = function(a) return RT.min(a[1], a[2]) end,
|
|
53
|
+
max = function(a) return RT.max(a[1], a[2]) end,
|
|
54
|
+
abs = function(a) return RT.abs(a[1]) end,
|
|
55
|
+
pow = function(a) return RT.pow(a[1], a[2]) end,
|
|
56
|
+
round = function(a) return RT.round(a[1], a[2], a[3]) end,
|
|
57
|
+
cmp = function(a) return RT.cmp(a[1], a[2], a[3]) end,
|
|
58
|
+
eq = function(a) return RT.eq(a[1], a[2]) end,
|
|
59
|
+
keyEq = function(a) return RT.key(a[1]) == RT.key(a[2]) end,
|
|
60
|
+
legalTime = function() return RT.legalTime() end,
|
|
61
|
+
setLegalTime = function(a) RT.setLegalTime(a[1]) return nil end,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
local fixture = J.read(arg[1])
|
|
65
|
+
local out = {}
|
|
66
|
+
for _, c in ipairs(fixture.cases) do
|
|
67
|
+
local f = OPS[c.op]
|
|
68
|
+
if f == nil then error("неизвестная операция фикстуры: " .. c.op) end
|
|
69
|
+
local args = {}
|
|
70
|
+
for i, x in ipairs(c.args) do args[i] = lit(x) end
|
|
71
|
+
local ok, res = pcall(f, args)
|
|
72
|
+
local got
|
|
73
|
+
if ok then
|
|
74
|
+
got = render(res)
|
|
75
|
+
elseif RT.is_refused(res) then
|
|
76
|
+
got = '{"refused":' .. J.str(res.msg) .. "}"
|
|
77
|
+
else
|
|
78
|
+
error(res, 0)
|
|
79
|
+
end
|
|
80
|
+
out[#out + 1] = '{"id":' .. J.str(c.id) .. ',"got":' .. got .. "}"
|
|
81
|
+
end
|
|
82
|
+
print("[" .. table.concat(out, ",") .. "]")
|