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,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";