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,541 @@
|
|
|
1
|
+
-- Рантайм L0 для Lua 5.3+. Тот же контракт, что у runtime.rb: точная
|
|
2
|
+
-- десятичная арифметика §58, деньги с валютой, названный режим округления,
|
|
3
|
+
-- календарный день, член перечисления. Целых произвольной длины в Lua нет —
|
|
4
|
+
-- свои: модуль числа по основанию 10^7 и знак; двоичной плавающей точки нет
|
|
5
|
+
-- ни в одном пути.
|
|
6
|
+
local RT = {}
|
|
7
|
+
|
|
8
|
+
-- Названный отказ рантайма: то, что срез не считает, а не сбой. Раннер
|
|
9
|
+
-- печатает его как JSON с кодом выхода 3; любая другая ошибка — крах.
|
|
10
|
+
local Refused = {}
|
|
11
|
+
Refused.__index = Refused
|
|
12
|
+
RT.Refused = Refused
|
|
13
|
+
|
|
14
|
+
local function refuse(msg)
|
|
15
|
+
error(setmetatable({msg = msg}, Refused), 0)
|
|
16
|
+
end
|
|
17
|
+
RT.refuse = refuse
|
|
18
|
+
|
|
19
|
+
function RT.is_refused(e)
|
|
20
|
+
return getmetatable(e) == Refused
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
-- --- целые произвольной длины ------------------------------------------------
|
|
24
|
+
-- {neg = bool, m = {младшая конечность, ...}}; ноль — пустой m и neg = false.
|
|
25
|
+
local BASE = 10000000
|
|
26
|
+
local WIDTH = 7
|
|
27
|
+
|
|
28
|
+
local function trim(m)
|
|
29
|
+
local n = #m
|
|
30
|
+
while n > 0 and m[n] == 0 do
|
|
31
|
+
m[n] = nil
|
|
32
|
+
n = n - 1
|
|
33
|
+
end
|
|
34
|
+
return m
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
local function big(neg, m)
|
|
38
|
+
trim(m)
|
|
39
|
+
if #m == 0 then neg = false end
|
|
40
|
+
return {neg = neg, m = m}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
local function parse_int(s)
|
|
44
|
+
local neg = false
|
|
45
|
+
if s:sub(1, 1) == "-" then
|
|
46
|
+
neg = true
|
|
47
|
+
s = s:sub(2)
|
|
48
|
+
end
|
|
49
|
+
if not s:match("^%d+$") then error("целое: не десятичная запись: " .. s) end
|
|
50
|
+
local m = {}
|
|
51
|
+
local i = #s
|
|
52
|
+
while i >= 1 do
|
|
53
|
+
local j = i - WIDTH + 1
|
|
54
|
+
if j < 1 then j = 1 end
|
|
55
|
+
m[#m + 1] = math.tointeger(tonumber(s:sub(j, i)))
|
|
56
|
+
i = j - 1
|
|
57
|
+
end
|
|
58
|
+
return big(neg, m)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local function from_int(n)
|
|
62
|
+
local neg = n < 0
|
|
63
|
+
if neg then n = -n end
|
|
64
|
+
local m = {}
|
|
65
|
+
while n > 0 do
|
|
66
|
+
m[#m + 1] = n % BASE
|
|
67
|
+
n = n // BASE
|
|
68
|
+
end
|
|
69
|
+
return big(neg, m)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
local function mag_str(m)
|
|
73
|
+
if #m == 0 then return "0" end
|
|
74
|
+
local parts = {tostring(m[#m])}
|
|
75
|
+
for i = #m - 1, 1, -1 do
|
|
76
|
+
parts[#parts + 1] = string.format("%07d", m[i])
|
|
77
|
+
end
|
|
78
|
+
return table.concat(parts)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
local function int_str(x)
|
|
82
|
+
return (x.neg and "-" or "") .. mag_str(x.m)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
local function mag_cmp(a, b)
|
|
86
|
+
if #a ~= #b then return #a < #b and -1 or 1 end
|
|
87
|
+
for i = #a, 1, -1 do
|
|
88
|
+
if a[i] ~= b[i] then return a[i] < b[i] and -1 or 1 end
|
|
89
|
+
end
|
|
90
|
+
return 0
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
local function mag_add(a, b)
|
|
94
|
+
local r, carry = {}, 0
|
|
95
|
+
local n = #a > #b and #a or #b
|
|
96
|
+
for i = 1, n do
|
|
97
|
+
local t = (a[i] or 0) + (b[i] or 0) + carry
|
|
98
|
+
r[i] = t % BASE
|
|
99
|
+
carry = t // BASE
|
|
100
|
+
end
|
|
101
|
+
if carry > 0 then r[n + 1] = carry end
|
|
102
|
+
return r
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
-- a >= b по модулю.
|
|
106
|
+
local function mag_sub(a, b)
|
|
107
|
+
local r, borrow = {}, 0
|
|
108
|
+
for i = 1, #a do
|
|
109
|
+
local t = a[i] - (b[i] or 0) - borrow
|
|
110
|
+
if t < 0 then
|
|
111
|
+
t = t + BASE
|
|
112
|
+
borrow = 1
|
|
113
|
+
else
|
|
114
|
+
borrow = 0
|
|
115
|
+
end
|
|
116
|
+
r[i] = t
|
|
117
|
+
end
|
|
118
|
+
return trim(r)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
local function mag_mul(a, b)
|
|
122
|
+
if #a == 0 or #b == 0 then return {} end
|
|
123
|
+
local r = {}
|
|
124
|
+
for i = 1, #a + #b do r[i] = 0 end
|
|
125
|
+
for i = 1, #a do
|
|
126
|
+
local carry = 0
|
|
127
|
+
local ai = a[i]
|
|
128
|
+
for j = 1, #b do
|
|
129
|
+
local t = r[i + j - 1] + ai * b[j] + carry
|
|
130
|
+
r[i + j - 1] = t % BASE
|
|
131
|
+
carry = t // BASE
|
|
132
|
+
end
|
|
133
|
+
local k = i + #b
|
|
134
|
+
while carry > 0 do
|
|
135
|
+
local t = r[k] + carry
|
|
136
|
+
r[k] = t % BASE
|
|
137
|
+
carry = t // BASE
|
|
138
|
+
k = k + 1
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
return trim(r)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
local function mag_mul_small(a, s)
|
|
145
|
+
local r, carry = {}, 0
|
|
146
|
+
for i = 1, #a do
|
|
147
|
+
local t = a[i] * s + carry
|
|
148
|
+
r[i] = t % BASE
|
|
149
|
+
carry = t // BASE
|
|
150
|
+
end
|
|
151
|
+
local k = #a + 1
|
|
152
|
+
while carry > 0 do
|
|
153
|
+
r[k] = carry % BASE
|
|
154
|
+
carry = carry // BASE
|
|
155
|
+
k = k + 1
|
|
156
|
+
end
|
|
157
|
+
return trim(r)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
-- Деление модулей в столбик: цифра частного по основанию 10^7 подбирается
|
|
161
|
+
-- двоичным поиском. Делитель ненулевой.
|
|
162
|
+
local function mag_divmod(a, b)
|
|
163
|
+
if mag_cmp(a, b) < 0 then
|
|
164
|
+
local r = {}
|
|
165
|
+
for i = 1, #a do r[i] = a[i] end
|
|
166
|
+
return {}, r
|
|
167
|
+
end
|
|
168
|
+
local q, rem = {}, {}
|
|
169
|
+
for i = #a, 1, -1 do
|
|
170
|
+
table.insert(rem, 1, a[i])
|
|
171
|
+
trim(rem)
|
|
172
|
+
local lo, hi = 0, BASE - 1
|
|
173
|
+
while lo < hi do
|
|
174
|
+
local mid = (lo + hi + 1) // 2
|
|
175
|
+
if mag_cmp(mag_mul_small(b, mid), rem) <= 0 then lo = mid else hi = mid - 1 end
|
|
176
|
+
end
|
|
177
|
+
if lo > 0 then rem = mag_sub(rem, mag_mul_small(b, lo)) end
|
|
178
|
+
q[i] = lo
|
|
179
|
+
end
|
|
180
|
+
return trim(q), rem
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
local I = {}
|
|
184
|
+
function I.add(x, y)
|
|
185
|
+
if x.neg == y.neg then return big(x.neg, mag_add(x.m, y.m)) end
|
|
186
|
+
local c = mag_cmp(x.m, y.m)
|
|
187
|
+
if c == 0 then return big(false, {}) end
|
|
188
|
+
if c > 0 then return big(x.neg, mag_sub(x.m, y.m)) end
|
|
189
|
+
return big(y.neg, mag_sub(y.m, x.m))
|
|
190
|
+
end
|
|
191
|
+
function I.neg(x) return big(not x.neg, x.m) end
|
|
192
|
+
function I.sub(x, y) return I.add(x, big(not y.neg, y.m)) end
|
|
193
|
+
function I.mul(x, y) return big(x.neg ~= y.neg, mag_mul(x.m, y.m)) end
|
|
194
|
+
function I.zero(x) return #x.m == 0 end
|
|
195
|
+
function I.cmp(x, y)
|
|
196
|
+
if x.neg ~= y.neg then return x.neg and -1 or 1 end
|
|
197
|
+
local c = mag_cmp(x.m, y.m)
|
|
198
|
+
return x.neg and -c or c
|
|
199
|
+
end
|
|
200
|
+
function I.absv(x) return big(false, x.m) end
|
|
201
|
+
-- Частное и остаток с усечением к нулю: знак остатка — знак делимого.
|
|
202
|
+
function I.divmod(x, y)
|
|
203
|
+
local q, r = mag_divmod(x.m, y.m)
|
|
204
|
+
return big(x.neg ~= y.neg, q), big(x.neg, r)
|
|
205
|
+
end
|
|
206
|
+
function I.pow10(k)
|
|
207
|
+
local r = from_int(1)
|
|
208
|
+
local ten = from_int(10)
|
|
209
|
+
for _ = 1, k do r = I.mul(r, ten) end
|
|
210
|
+
return r
|
|
211
|
+
end
|
|
212
|
+
function I.gcd(x, y)
|
|
213
|
+
local a, b = I.absv(x), I.absv(y)
|
|
214
|
+
while not I.zero(b) do
|
|
215
|
+
local _, r = I.divmod(a, b)
|
|
216
|
+
a, b = b, r
|
|
217
|
+
end
|
|
218
|
+
return a
|
|
219
|
+
end
|
|
220
|
+
-- Малое целое: вызывающий обязан сперва проверить диапазон.
|
|
221
|
+
function I.to_small(x)
|
|
222
|
+
local n = 0
|
|
223
|
+
for i = #x.m, 1, -1 do n = n * BASE + x.m[i] end
|
|
224
|
+
return x.neg and -n or n
|
|
225
|
+
end
|
|
226
|
+
I.parse = parse_int
|
|
227
|
+
I.from = from_int
|
|
228
|
+
I.str = int_str
|
|
229
|
+
RT.I = I
|
|
230
|
+
|
|
231
|
+
-- --- значения ----------------------------------------------------------------
|
|
232
|
+
-- Dec {kind="dec", u, s}, Money {kind="money", u, s, cur}; календарный день
|
|
233
|
+
-- §2.15 в канонической записи ISO — {kind="date", v}: порядок записи и есть
|
|
234
|
+
-- порядок дня; член перечисления §38 или ссылка без объявления — {kind="sym",
|
|
235
|
+
-- id}. Text и сущность — строки Lua.
|
|
236
|
+
local function dec_of(u, s) return {kind = "dec", u = u, s = s} end
|
|
237
|
+
local function money_of(u, s, cur) return {kind = "money", u = u, s = s, cur = cur} end
|
|
238
|
+
|
|
239
|
+
function RT.dec(lit)
|
|
240
|
+
lit = tostring(lit)
|
|
241
|
+
local ip, fp = lit:match("^([^.]*)%.(.*)$")
|
|
242
|
+
if ip == nil then
|
|
243
|
+
ip, fp = lit, ""
|
|
244
|
+
end
|
|
245
|
+
local neg = ip:sub(1, 1) == "-"
|
|
246
|
+
local digits = (neg and ip:sub(2) or ip) .. fp
|
|
247
|
+
return dec_of(parse_int((neg and "-" or "") .. digits), #fp)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
function RT.money(lit, cur)
|
|
251
|
+
local d = RT.dec(lit)
|
|
252
|
+
return money_of(d.u, d.s, cur)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
function RT.date(lit) return {kind = "date", v = tostring(lit)} end
|
|
256
|
+
function RT.sym(id) return {kind = "sym", id = tostring(id)} end
|
|
257
|
+
|
|
258
|
+
local function kind(v)
|
|
259
|
+
if type(v) == "table" then return v.kind end
|
|
260
|
+
return type(v)
|
|
261
|
+
end
|
|
262
|
+
local function is_num(v) local k = kind(v) return k == "dec" or k == "money" end
|
|
263
|
+
local function is_money(v) return kind(v) == "money" end
|
|
264
|
+
|
|
265
|
+
-- §48/§58: сложение, вычитание, min/max требуют совпадения видов; у денег — и валюты.
|
|
266
|
+
local function same(a, b, op)
|
|
267
|
+
if not (is_num(a) and is_num(b) and is_money(a) == is_money(b)) then
|
|
268
|
+
refuse("§58: виды операндов не совпадают: " .. op)
|
|
269
|
+
end
|
|
270
|
+
if is_money(a) and a.cur ~= b.cur then refuse("§58: разные валюты: " .. op) end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
local function imax(x, y) if x > y then return x end return y end
|
|
274
|
+
|
|
275
|
+
local function pair(a, b)
|
|
276
|
+
local s = imax(a.s, b.s)
|
|
277
|
+
return I.mul(a.u, I.pow10(s - a.s)), I.mul(b.u, I.pow10(s - b.s)), s
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
local function make(u, s, like)
|
|
281
|
+
if is_money(like) then return money_of(u, s, like.cur) end
|
|
282
|
+
return dec_of(u, s)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
-- Целое значение: дробная часть нулевая (защита в глубину под статикой плана).
|
|
286
|
+
local function integral(v)
|
|
287
|
+
if kind(v) ~= "dec" then return false end
|
|
288
|
+
local _, r = I.divmod(v.u, I.pow10(v.s))
|
|
289
|
+
return I.zero(r)
|
|
290
|
+
end
|
|
291
|
+
local function to_int(v)
|
|
292
|
+
local q = I.divmod(v.u, I.pow10(v.s))
|
|
293
|
+
return q
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
function RT.add(a, b)
|
|
297
|
+
same(a, b, "add")
|
|
298
|
+
local x, y, s = pair(a, b)
|
|
299
|
+
return make(I.add(x, y), s, a)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
function RT.sub(a, b)
|
|
303
|
+
same(a, b, "sub")
|
|
304
|
+
local x, y, s = pair(a, b)
|
|
305
|
+
return make(I.sub(x, y), s, a)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
-- Правило скаляра §49: безразмерное масштабирует размерное; масштаб
|
|
309
|
+
-- результата — сумма масштабов. Два размерных не перемножаются.
|
|
310
|
+
function RT.mul(a, b)
|
|
311
|
+
if not (is_num(a) and is_num(b)) then refuse("§58: виды операндов не совпадают: mul") end
|
|
312
|
+
if is_money(a) and is_money(b) then refuse("§58: произведение двух размерных величин не определено") end
|
|
313
|
+
return make(I.mul(a.u, b.u), a.s + b.s, is_money(a) and a or b)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
local TWO, FIVE = from_int(2), from_int(5)
|
|
317
|
+
|
|
318
|
+
-- Точное десятичное частное num/den при 10-гладком знаменателе (§18), иначе nil.
|
|
319
|
+
local function exact(num, den)
|
|
320
|
+
local g = I.gcd(num, den)
|
|
321
|
+
if I.zero(g) then g = from_int(1) end
|
|
322
|
+
num = I.divmod(num, g)
|
|
323
|
+
den = I.divmod(den, g)
|
|
324
|
+
if den.neg then
|
|
325
|
+
num = I.neg(num)
|
|
326
|
+
den = I.neg(den)
|
|
327
|
+
end
|
|
328
|
+
local twos, fives = 0, 0
|
|
329
|
+
while true do
|
|
330
|
+
local q, r = I.divmod(den, TWO)
|
|
331
|
+
if not I.zero(r) then break end
|
|
332
|
+
den = q
|
|
333
|
+
twos = twos + 1
|
|
334
|
+
end
|
|
335
|
+
while true do
|
|
336
|
+
local q, r = I.divmod(den, FIVE)
|
|
337
|
+
if not I.zero(r) then break end
|
|
338
|
+
den = q
|
|
339
|
+
fives = fives + 1
|
|
340
|
+
end
|
|
341
|
+
if I.cmp(den, from_int(1)) ~= 0 then return nil end
|
|
342
|
+
local k = imax(twos, fives)
|
|
343
|
+
local f = from_int(1)
|
|
344
|
+
for _ = 1, k - twos do f = I.mul(f, TWO) end
|
|
345
|
+
for _ = 1, k - fives do f = I.mul(f, FIVE) end
|
|
346
|
+
return I.mul(num, f), k
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
-- Таблица делимых §58 в срезе: Decimal/Decimal, Money/Decimal, Money/Money
|
|
350
|
+
-- одной валюты. Integer/Integer даёт Rational — вне среза, отвергается планом.
|
|
351
|
+
function RT.div(a, b)
|
|
352
|
+
if not (is_num(a) and is_num(b)) then refuse("§58: деление вне таблицы делимых") end
|
|
353
|
+
if I.zero(b.u) then refuse("деление на ноль — evaluation error (§58)") end
|
|
354
|
+
local like = nil
|
|
355
|
+
if is_money(a) and is_money(b) then
|
|
356
|
+
if a.cur ~= b.cur then
|
|
357
|
+
refuse("CURRENCY_MISMATCH: §58 делит Money на Money только при тождественной валюте")
|
|
358
|
+
end
|
|
359
|
+
elseif is_money(a) then
|
|
360
|
+
like = a
|
|
361
|
+
elseif is_money(b) then
|
|
362
|
+
refuse("§58: деление вне таблицы делимых")
|
|
363
|
+
end
|
|
364
|
+
local qu, qs = exact(I.mul(a.u, I.pow10(b.s)), I.mul(b.u, I.pow10(a.s)))
|
|
365
|
+
if qu == nil then
|
|
366
|
+
if like ~= nil then refuse("INEXACT_DIVISION: округление только явной политикой (§50/§18)") end
|
|
367
|
+
refuse("§58: частное не 10-гладкое — Rational вне среза")
|
|
368
|
+
end
|
|
369
|
+
if like == nil then return dec_of(qu, qs) end
|
|
370
|
+
return money_of(qu, qs, like.cur)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
-- Евклидов остаток §58: 0 <= r < |b| при любых знаках операндов.
|
|
374
|
+
function RT.mod(a, b)
|
|
375
|
+
if not (integral(a) and integral(b)) then refuse("§58: mod определён только для Integer") end
|
|
376
|
+
local y = to_int(b)
|
|
377
|
+
if I.zero(y) then refuse("остаток от деления на ноль — evaluation error (§58)") end
|
|
378
|
+
local ya = I.absv(y)
|
|
379
|
+
local _, r = I.divmod(to_int(a), ya)
|
|
380
|
+
if r.neg then r = I.add(r, ya) end
|
|
381
|
+
return dec_of(r, 0)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
function RT.min(a, b)
|
|
385
|
+
same(a, b, "min")
|
|
386
|
+
local x, y = pair(a, b)
|
|
387
|
+
if I.cmp(x, y) <= 0 then return a end
|
|
388
|
+
return b
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
function RT.max(a, b)
|
|
392
|
+
same(a, b, "max")
|
|
393
|
+
local x, y = pair(a, b)
|
|
394
|
+
if I.cmp(x, y) >= 0 then return a end
|
|
395
|
+
return b
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
function RT.abs(v)
|
|
399
|
+
if not is_num(v) then refuse("§58: abs — не числовой вид") end
|
|
400
|
+
return make(I.absv(v.u), v.s, v)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
local POW_MIN = parse_int("-9223372036854775808")
|
|
404
|
+
local POW_MAX = parse_int("9223372036854775807")
|
|
405
|
+
|
|
406
|
+
-- §58 (E-0075): только Integer pow Integer; порядок проверок — знак, предел,
|
|
407
|
+
-- вычисление, диапазон — часть контракта. Умножение останавливается, как
|
|
408
|
+
-- только модуль превысил 2^63: при |основание| >= 1 он дальше не убывает, а
|
|
409
|
+
-- при нулевом основании не растёт, — исход тот же, что у полного вычисления.
|
|
410
|
+
function RT.pow(base, exp)
|
|
411
|
+
if not (integral(base) and integral(exp)) then refuse("§58: pow определён только для Integer") end
|
|
412
|
+
local e = to_int(exp)
|
|
413
|
+
if e.neg then refuse("§58: pow — показатель отрицателен") end
|
|
414
|
+
if I.cmp(e, from_int(4096)) > 0 then refuse("RESOURCE_LIMIT: pow — показатель превышает 4096") end
|
|
415
|
+
local b = to_int(base)
|
|
416
|
+
local r = from_int(1)
|
|
417
|
+
local bound = I.absv(POW_MIN)
|
|
418
|
+
for _ = 1, I.to_small(e) do
|
|
419
|
+
r = I.mul(r, b)
|
|
420
|
+
if I.cmp(I.absv(r), bound) > 0 then break end
|
|
421
|
+
end
|
|
422
|
+
if I.cmp(r, POW_MIN) < 0 or I.cmp(r, POW_MAX) > 0 then
|
|
423
|
+
refuse("RESOURCE_LIMIT: pow — результат вне 64-битного диапазона")
|
|
424
|
+
end
|
|
425
|
+
return dec_of(r, 0)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
function RT.round(m, digits, mode)
|
|
429
|
+
if mode ~= "HALF_UP" then refuse("§50: режим округления не реализован: " .. tostring(mode)) end
|
|
430
|
+
if not is_num(m) then refuse("§50: round — не числовой вид") end
|
|
431
|
+
local n
|
|
432
|
+
if math.type(digits) == "integer" then n = digits else n = I.to_small(to_int(digits)) end
|
|
433
|
+
if m.s <= n then return make(I.mul(m.u, I.pow10(n - m.s)), n, m) end
|
|
434
|
+
local drop = I.pow10(m.s - n)
|
|
435
|
+
local q, r = I.divmod(I.absv(m.u), drop)
|
|
436
|
+
local up = q
|
|
437
|
+
if I.cmp(I.add(r, r), drop) >= 0 then up = I.add(q, from_int(1)) end -- HALF_UP — от нуля
|
|
438
|
+
if m.u.neg then up = I.neg(up) end
|
|
439
|
+
return make(up, n, m)
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
local ORDER = {
|
|
443
|
+
lt = function(c) return c < 0 end, le = function(c) return c <= 0 end,
|
|
444
|
+
gt = function(c) return c > 0 end, ge = function(c) return c >= 0 end,
|
|
445
|
+
eq = function(c) return c == 0 end, ne = function(c) return c ~= 0 end,
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
local function str_order(x, y)
|
|
449
|
+
if x < y then return -1 elseif x > y then return 1 end
|
|
450
|
+
return 0
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
function RT.cmp(a, op, b)
|
|
454
|
+
if kind(a) == "sym" or kind(b) == "sym" then
|
|
455
|
+
local same_sym = kind(a) == "sym" and kind(b) == "sym" and a.id == b.id
|
|
456
|
+
if op == "eq" then return same_sym end
|
|
457
|
+
if op == "ne" then return not same_sym end
|
|
458
|
+
refuse("§57: вид enum не упорядочен")
|
|
459
|
+
end
|
|
460
|
+
if kind(a) == "date" or kind(b) == "date" then
|
|
461
|
+
if kind(a) == "date" and kind(b) == "date" then
|
|
462
|
+
local f = ORDER[op]
|
|
463
|
+
if f == nil then refuse("сравнение вне среза рантайма: " .. tostring(op)) end
|
|
464
|
+
return f(str_order(a.v, b.v))
|
|
465
|
+
end
|
|
466
|
+
if op == "eq" then return false end
|
|
467
|
+
if op == "ne" then return true end
|
|
468
|
+
refuse("§57: Date сравнивается только с Date")
|
|
469
|
+
end
|
|
470
|
+
if type(a) == "string" or type(b) == "string" then
|
|
471
|
+
if op == "eq" then return a == b end
|
|
472
|
+
if op == "ne" then return a ~= b end
|
|
473
|
+
refuse("порядок на Text вне среза рантайма: " .. tostring(op))
|
|
474
|
+
end
|
|
475
|
+
if is_money(a) ~= is_money(b) then refuse("§49: сравнение денег с безразмерным") end
|
|
476
|
+
if is_money(a) and a.cur ~= b.cur then refuse("§49: разные валюты") end
|
|
477
|
+
local x, y = pair(a, b)
|
|
478
|
+
local f = ORDER[op]
|
|
479
|
+
if f == nil then refuse("сравнение вне среза рантайма: " .. tostring(op)) end
|
|
480
|
+
return f(I.cmp(x, y))
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
function RT.eq(a, b)
|
|
484
|
+
if kind(a) == "sym" or kind(b) == "sym" then
|
|
485
|
+
return kind(a) == "sym" and kind(b) == "sym" and a.id == b.id
|
|
486
|
+
end
|
|
487
|
+
if kind(a) == "date" or kind(b) == "date" then
|
|
488
|
+
return kind(a) == "date" and kind(b) == "date" and a.v == b.v
|
|
489
|
+
end
|
|
490
|
+
if type(a) == "string" or type(b) == "string" then return a == b end
|
|
491
|
+
if is_money(a) ~= is_money(b) then return false end
|
|
492
|
+
if is_money(a) and a.cur ~= b.cur then return false end
|
|
493
|
+
local x, y = pair(a, b)
|
|
494
|
+
return I.cmp(x, y) == 0
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
local function norm(d)
|
|
498
|
+
local u, s = d.u, d.s
|
|
499
|
+
local ten = from_int(10)
|
|
500
|
+
while s > 0 do
|
|
501
|
+
local q, r = I.divmod(u, ten)
|
|
502
|
+
if not I.zero(r) then break end
|
|
503
|
+
u = q
|
|
504
|
+
s = s - 1
|
|
505
|
+
end
|
|
506
|
+
return int_str(u) .. "e-" .. s
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
-- Разделитель и экран внутри текста экранируются: иначе ["a|e:b","c"] и
|
|
510
|
+
-- ["a","b|e:c"] склеивались в один ключ, и второй факт терялся.
|
|
511
|
+
local function esc(text)
|
|
512
|
+
return (text:gsub("\\", "\\\\"):gsub("|", "\\|"))
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
function RT.key(row)
|
|
516
|
+
local parts = {}
|
|
517
|
+
for i = 1, #row do
|
|
518
|
+
local v = row[i]
|
|
519
|
+
local k = kind(v)
|
|
520
|
+
if k == "string" then parts[i] = "e:" .. esc(v)
|
|
521
|
+
elseif k == "date" then parts[i] = "t:" .. v.v
|
|
522
|
+
elseif k == "sym" then parts[i] = "c:" .. esc(v.id)
|
|
523
|
+
elseif k == "money" then parts[i] = "m:" .. v.cur .. ":" .. norm(v)
|
|
524
|
+
else parts[i] = "d:" .. norm(v)
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
return table.concat(parts, "|")
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
local legal_time = ""
|
|
531
|
+
|
|
532
|
+
function RT.setLegalTime(t)
|
|
533
|
+
legal_time = tostring(t)
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
function RT.legalTime()
|
|
537
|
+
if legal_time == "" then refuse("legalTime не подан делом") end
|
|
538
|
+
return legal_time
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
return RT
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
// Харнесс сертификации рантайма: исполняет runtime-cert.json и печатает,
|
|
3
|
+
// что получилось. Ожидания сверяют ворота, харнесс их не читает.
|
|
4
|
+
declare(strict_types=1);
|
|
5
|
+
|
|
6
|
+
require __DIR__ . '/runtime.php';
|
|
7
|
+
require __DIR__ . '/json.php';
|
|
8
|
+
|
|
9
|
+
function lit(mixed $x): mixed
|
|
10
|
+
{
|
|
11
|
+
if (is_string($x)) {
|
|
12
|
+
return $x;
|
|
13
|
+
}
|
|
14
|
+
return match (true) {
|
|
15
|
+
array_key_exists('dec', $x) => RT::dec($x['dec']),
|
|
16
|
+
array_key_exists('money', $x) => RT::money($x['money'], $x['cur']),
|
|
17
|
+
array_key_exists('text', $x) => $x['text'],
|
|
18
|
+
array_key_exists('date', $x) => RT::date($x['date']),
|
|
19
|
+
array_key_exists('sym', $x) => RT::sym($x['sym']),
|
|
20
|
+
array_key_exists('row', $x) => array_map('lit', $x['row']),
|
|
21
|
+
default => throw new RuntimeException('литерал фикстуры'),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Отображение с ЯВНЫМ масштабом: 2.00 и 2 — разные записи одного значения.
|
|
26
|
+
function digits(BigI $u, int $s): string
|
|
27
|
+
{
|
|
28
|
+
$body = str_pad($u->abs()->str(), $s + 1, '0', STR_PAD_LEFT);
|
|
29
|
+
$text = $s === 0 ? $body : substr($body, 0, -$s) . '.' . substr($body, -$s);
|
|
30
|
+
return ($u->neg ? '-' : '') . $text;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function render(mixed $v): string
|
|
34
|
+
{
|
|
35
|
+
return match (true) {
|
|
36
|
+
$v === null => 'null',
|
|
37
|
+
is_bool($v) => $v ? 'true' : 'false',
|
|
38
|
+
is_string($v) => JsonText::str($v),
|
|
39
|
+
$v instanceof Money => JsonText::str(digits($v->u, $v->s) . ' ' . $v->cur),
|
|
40
|
+
$v instanceof LDate => JsonText::str($v->v),
|
|
41
|
+
$v instanceof Sym => JsonText::str($v->id),
|
|
42
|
+
default => JsonText::str(digits($v->u, $v->s)),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function run_case(string $op, array $a): mixed
|
|
47
|
+
{
|
|
48
|
+
switch ($op) {
|
|
49
|
+
case 'dec': return RT::dec($a[0]);
|
|
50
|
+
case 'money': return RT::money($a[0], $a[1]);
|
|
51
|
+
case 'date': return RT::date($a[0]);
|
|
52
|
+
case 'sym': return RT::sym($a[0]);
|
|
53
|
+
case 'mul': return RT::mul($a[0], $a[1]);
|
|
54
|
+
case 'add': return RT::add($a[0], $a[1]);
|
|
55
|
+
case 'sub': return RT::sub($a[0], $a[1]);
|
|
56
|
+
case 'div': return RT::div($a[0], $a[1]);
|
|
57
|
+
case 'mod': return RT::mod($a[0], $a[1]);
|
|
58
|
+
case 'min': return RT::min($a[0], $a[1]);
|
|
59
|
+
case 'max': return RT::max($a[0], $a[1]);
|
|
60
|
+
case 'abs': return RT::abs($a[0]);
|
|
61
|
+
case 'pow': return RT::pow($a[0], $a[1]);
|
|
62
|
+
case 'round': return RT::round($a[0], $a[1], $a[2]);
|
|
63
|
+
case 'cmp': return RT::cmp($a[0], $a[1], $a[2]);
|
|
64
|
+
case 'eq': return RT::eq($a[0], $a[1]);
|
|
65
|
+
case 'keyEq': return RT::key($a[0]) === RT::key($a[1]);
|
|
66
|
+
case 'legalTime': return RT::legalTime();
|
|
67
|
+
case 'setLegalTime':
|
|
68
|
+
RT::setLegalTime($a[0]);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
throw new RuntimeException("неизвестная операция фикстуры: $op");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
$fixture = JsonText::read($argv[1]);
|
|
75
|
+
$out = [];
|
|
76
|
+
foreach ($fixture['cases'] as $c) {
|
|
77
|
+
try {
|
|
78
|
+
$got = render(run_case($c['op'], array_map('lit', $c['args'])));
|
|
79
|
+
} catch (Refused $e) {
|
|
80
|
+
$got = '{"refused":' . JsonText::str($e->getMessage()) . '}';
|
|
81
|
+
}
|
|
82
|
+
$out[] = '{"id":' . JsonText::str($c['id']) . ',"got":' . $got . '}';
|
|
83
|
+
}
|
|
84
|
+
echo '[' . implode(',', $out) . "]\n";
|