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,2612 @@
1
+ package main
2
+
3
+ // Рантайм L0 для Go. Тот же контракт, что у runtime.ts и runtime.py:
4
+ // точная десятичная арифметика §58, деньги с валютой, названный режим
5
+ // округления, календарный день, член перечисления.
6
+
7
+ import (
8
+ "bytes"
9
+ "encoding/json"
10
+ "fmt"
11
+ "math/big"
12
+ "regexp"
13
+ "sort"
14
+ "strconv"
15
+ "strings"
16
+ "time"
17
+ )
18
+
19
+ type Val interface{}
20
+
21
+ // Названный отказ рантайма: то, что срез не считает, а не сбой. Раннер
22
+ // восстанавливает его из panic и печатает как JSON с кодом выхода 3; любая
23
+ // другая panic — крах, который ворота валят.
24
+ type Refused string
25
+
26
+ func refuse(msg string) {
27
+ panic(Refused(msg))
28
+ }
29
+
30
+ // Календарный день §2.15 в канонической записи ISO YYYY-MM-DD: порядок
31
+ // записи и есть порядок дня. Не string: у ключа атома и у сравнения свой вид.
32
+ type Date string
33
+
34
+ var dateRe = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}$`)
35
+
36
+ func strictDay(raw string) time.Time {
37
+ t, err := time.Parse("2006-01-02", raw)
38
+ if !dateRe.MatchString(raw) || err != nil || t.Year() < 1 || t.Year() > 9999 {
39
+ panic(termError{"TYPE_ERROR"})
40
+ }
41
+ return t
42
+ }
43
+ func date(lit string) Date { strictDay(lit); return Date(lit) }
44
+
45
+ // §82.2: record identity stays separate from the exact moment.
46
+ type Instant string
47
+
48
+ func instant(lit string) Instant { exactMoment(Instant(lit)); return Instant(lit) }
49
+
50
+ var instantRe = regexp.MustCompile(`^([0-9]{4}-[0-9]{2}-[0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?:\.([0-9]+))?(Z|[+-][0-9]{2}:[0-9]{2})$`)
51
+
52
+ type instantMoment struct {
53
+ seconds int64
54
+ fraction string
55
+ }
56
+
57
+ func exactMoment(v Instant) instantMoment {
58
+ m := instantRe.FindStringSubmatch(string(v))
59
+ if m == nil {
60
+ panic(termError{"TYPE_ERROR"})
61
+ }
62
+ day := strictDay(m[1])
63
+ h, _ := strconv.Atoi(m[2])
64
+ mi, _ := strconv.Atoi(m[3])
65
+ sec, _ := strconv.Atoi(m[4])
66
+ if h > 23 || mi > 59 || sec > 59 {
67
+ panic(termError{"TYPE_ERROR"})
68
+ }
69
+ offset := 0
70
+ if m[6] != "Z" {
71
+ oh, _ := strconv.Atoi(m[6][1:3])
72
+ om, _ := strconv.Atoi(m[6][4:6])
73
+ if oh > 23 || om > 59 {
74
+ panic(termError{"TYPE_ERROR"})
75
+ }
76
+ offset = oh*3600 + om*60
77
+ if m[6][0] == '-' {
78
+ offset = -offset
79
+ }
80
+ }
81
+ return instantMoment{day.Unix() + int64(h*3600+mi*60+sec-offset), strings.TrimRight(m[5], "0")}
82
+ }
83
+ func momentCmp(a, b Instant) int {
84
+ x, y := exactMoment(a), exactMoment(b)
85
+ if x.seconds < y.seconds {
86
+ return -1
87
+ }
88
+ if x.seconds > y.seconds {
89
+ return 1
90
+ }
91
+ return strings.Compare(x.fraction, y.fraction)
92
+ }
93
+ func validateTemporalCase(raw []byte) {
94
+ var kase map[string]interface{}
95
+ if json.Unmarshal(raw, &kase) != nil {
96
+ return
97
+ }
98
+ assertions, _ := kase["assertions"].([]interface{})
99
+ for i, value := range assertions {
100
+ a, _ := value.(map[string]interface{})
101
+ w, present := a["valid"]
102
+ if !present {
103
+ continue
104
+ }
105
+ base := fmt.Sprintf("TEMPORAL_BOUND_INVALID: request:/case/assertions/%d/valid", i)
106
+ interval, ok := w.(map[string]interface{})
107
+ if !ok {
108
+ refuse(base)
109
+ }
110
+ for _, side := range []string{"start", "end"} {
111
+ v, present := interval[side]
112
+ if !present {
113
+ continue
114
+ }
115
+ text, ok := v.(string)
116
+ sentinel := "infinity"
117
+ if side == "start" {
118
+ sentinel = "-infinity"
119
+ }
120
+ if ok && text == sentinel {
121
+ continue
122
+ }
123
+ func() {
124
+ defer func() {
125
+ if e := recover(); e != nil {
126
+ if _, ok := e.(termError); ok {
127
+ refuse(base + "/" + side)
128
+ }
129
+ panic(e)
130
+ }
131
+ }()
132
+ if !ok {
133
+ panic(termError{"TYPE_ERROR"})
134
+ }
135
+ if dateRe.MatchString(text) {
136
+ strictDay(text)
137
+ } else {
138
+ exactMoment(Instant(text))
139
+ }
140
+ }()
141
+ }
142
+ }
143
+ }
144
+
145
+ // Член перечисления §38 либо иная ссылка const_ref без объявления: значение —
146
+ // stable qualified identity, равенство по ней (§57), порядок не определён.
147
+ type Sym string
148
+
149
+ func sym(id string) Sym { return Sym(id) }
150
+
151
+ type Dec struct {
152
+ U *big.Int
153
+ S int
154
+ }
155
+
156
+ type Money struct {
157
+ U *big.Int
158
+ S int
159
+ Cur string
160
+ }
161
+
162
+ func p10(n int) *big.Int {
163
+ return new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(n)), nil)
164
+ }
165
+
166
+ func dec(lit string) Dec { return litDec("s", lit) }
167
+
168
+ func money(lit, cur string) Money {
169
+ d := dec(lit)
170
+ return Money{U: d.U, S: d.S, Cur: cur}
171
+ }
172
+
173
+ // Quantity §49 v1 (DECISION-0256 п. 5): точное десятичное и тег единицы —
174
+ // байты записи; реестра единиц и алиасов §49.2 здесь нет. Не Money: у ключа
175
+ // и у сравнения свой вид, 1 KZT денег и величина 1 [KZT] — разные значения.
176
+ // Неизменяема: мантисса не мутируется, операции строят new(big.Int).
177
+ type Qty struct {
178
+ U *big.Int
179
+ S int
180
+ Unit string
181
+ }
182
+
183
+ // Величина §49: значение и тег единицы как записаны.
184
+ func qty(lit, unit string) Qty {
185
+ d := dec(lit)
186
+ return Qty{U: d.U, S: d.S, Unit: unit}
187
+ }
188
+
189
+ func isQty(v Val) bool {
190
+ _, ok := v.(Qty)
191
+ return ok
192
+ }
193
+
194
+ // Rational §35 (DECISION-0252 п. 6): несократимая дробь на целых произвольной
195
+ // длины, знаменатель положителен, ноль — 0/1. Не Dec: у ключа строки и у
196
+ // сравнения свой вид, 2/1 и Decimal 2 — разные значения §48. Неизменяема:
197
+ // поля строятся только конструктором, операции над ними берут new(big.Int).
198
+ type Rational struct {
199
+ N *big.Int
200
+ D *big.Int
201
+ }
202
+
203
+ func newRat(n, d *big.Int) Rational {
204
+ if d.Sign() == 0 {
205
+ panic(termError{"DIVISION_BY_ZERO"})
206
+ }
207
+ num, den := new(big.Int).Set(n), new(big.Int).Set(d)
208
+ if den.Sign() < 0 {
209
+ num.Neg(num)
210
+ den.Neg(den)
211
+ }
212
+ g := new(big.Int).GCD(nil, nil, new(big.Int).Abs(num), den)
213
+ if g.Sign() == 0 {
214
+ g = big.NewInt(1)
215
+ }
216
+ return Rational{N: num.Quo(num, g), D: den.Quo(den, g)}
217
+ }
218
+
219
+ // --- сырое поле литерала §208 -------------------------------------------------
220
+ // Правило одно на три цели (errata E-0197, E-0202, E-0203): сырое поле литерала
221
+ // читается НЕ средствами языка цели, а по грамматике §208. `int()` и `Decimal()`
222
+ // Python берут пробел, `_` и юникодную цифру; `Number()` JavaScript берёт `1e2`
223
+ // и `Infinity` и теряет точность выше 2^53; `strconv` и `math/big` Go имеют свои
224
+ // допуски (`SetString` с базой 0 разрешает `_`). Норма у всех одна, поэтому
225
+ // алфавит назван явно и одинаково.
226
+
227
+ // §18/§208 (errata E-0203): лексика ЗАПИСИ десятичного значения — дословный
228
+ // порт `lawref/values.py::_DECIMAL_LEXIS`. Экспонента, ведущие и хвостовые
229
+ // нули, знак `+`, `.5` и `5.` остаются законной записью входа и нормализуются;
230
+ // сужены ровно пробельный символ, `_`, не-ASCII цифра и `Infinity`/`NaN`.
231
+ // `$` в RE2 без флага `m` — конец ТЕКСТА (как `\z`), хвостового перевода
232
+ // строки, из-за которого оракулу понадобился `\Z`, он не пропускает.
233
+ var decimalLexis = regexp.MustCompile(`^[+-]?([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?$`)
234
+
235
+ // §35/§208 (errata E-0203): лексика ЦЕЛОГО внутри записи `<n>/<d>`.
236
+ var fractionPartLexis = regexp.MustCompile(`^[+-]?[0-9]+$`)
237
+
238
+ // §208 (errata E-0202): знаковый 64-битный носитель JSON-числа канона.
239
+ var i64Min = new(big.Int).Neg(new(big.Int).Lsh(big.NewInt(1), 63))
240
+ var i64Max = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 63), big.NewInt(1))
241
+
242
+ // Канон §208 отвергает JSON-float и целое вне i64 ОТКАЗОМ СОБРАТЬ ДОКУМЕНТ
243
+ // (error-объект §172), а не issue. У порождённого модуля канала error-объекта
244
+ // нет — профиль core-executable документа не строит, — поэтому тот же вход даёт
245
+ // названный отказ раннера: «меньше ответа», а не другой ответ.
246
+ const canonNumber = "JSON-число вне канона §208 (только целое знакового 64-битного диапазона) вне среза раннера"
247
+
248
+ // Текст JSON-числа канона: целое в i64 — его цифрами, всё прочее — отказ.
249
+ func canonNumberText(s string) (string, string) {
250
+ n, ok := new(big.Int).SetString(s, 10)
251
+ if !ok || n.Cmp(i64Min) < 0 || n.Cmp(i64Max) > 0 {
252
+ refuse(canonNumber)
253
+ }
254
+ return "i", n.String()
255
+ }
256
+
257
+ // Поле, чей канон §208 — JSON-строка (Text, Date, Instant, Rational, валюта,
258
+ // единица). Порт `values._json_string`: до E-0197 цели приводили к строке ЛЮБОЙ
259
+ // JSON, и литерал без `value` выходил значением "" у go и "undefined" у ts —
260
+ // расхождение в результате, а не в тексте issue.
261
+ func litText(k, text string) string {
262
+ if k != "s" {
263
+ panic(termError{"TYPE_ERROR"})
264
+ }
265
+ return text
266
+ }
267
+
268
+ // Поле, чей канон §208 — целое JSON-число (Integer). Порт `values._json_int`.
269
+ func litIntText(k, text string) string {
270
+ if k != "i" {
271
+ panic(termError{"TYPE_ERROR"})
272
+ }
273
+ return text
274
+ }
275
+
276
+ // Поле Decimal/Money/Quantity: строка по лексике §208 либо целое JSON-число
277
+ // (запись числом остаётся законной — E-0203, долг п. 3).
278
+ func litDecText(k, text string) string {
279
+ if k == "i" {
280
+ return text
281
+ }
282
+ if k != "s" || !decimalLexis.MatchString(text) {
283
+ panic(termError{"TYPE_ERROR"})
284
+ }
285
+ return text
286
+ }
287
+
288
+ // Точное десятичное из записи, уже прошедшей decimalLexis: знак, необязательная
289
+ // экспонента, `.5` и `5.`. Нормализация — как у `Decimal(text)` оракула, чей
290
+ // канон §208 печатает non-exponent форму.
291
+ func decLexis(text string) Dec {
292
+ body := text
293
+ neg := strings.HasPrefix(body, "-")
294
+ if neg || strings.HasPrefix(body, "+") {
295
+ body = body[1:]
296
+ }
297
+ exp := 0
298
+ if at := strings.IndexAny(body, "eE"); at >= 0 {
299
+ n, err := strconv.Atoi(body[at+1:])
300
+ if err != nil {
301
+ panic(termError{"TYPE_ERROR"})
302
+ }
303
+ exp = n
304
+ body = body[:at]
305
+ }
306
+ intPart, frac, _ := strings.Cut(body, ".")
307
+ digits := intPart + frac
308
+ if digits == "" {
309
+ digits = "0"
310
+ }
311
+ u, ok := new(big.Int).SetString(digits, 10)
312
+ if !ok {
313
+ panic(termError{"TYPE_ERROR"})
314
+ }
315
+ s := len(frac) - exp
316
+ if u.Sign() == 0 && (s > 4096 || s < -4096) {
317
+ return Dec{U: big.NewInt(0), S: 0}
318
+ }
319
+ canonicalScale := s - (len(digits) - len(strings.TrimRight(digits, "0")))
320
+ if u.Sign() != 0 && (canonicalScale > 4096 || s < -4096) {
321
+ refuse("DECIMAL_EXPANSION_UNAVAILABLE: E-0320 limit 4096")
322
+ }
323
+ if s < 0 {
324
+ u.Mul(u, p10(-s))
325
+ s = 0
326
+ }
327
+ if neg {
328
+ u.Neg(u)
329
+ }
330
+ return Dec{U: u, S: s}
331
+ }
332
+
333
+ func litDec(k, text string) Dec { return decLexis(litDecText(k, text)) }
334
+ func litInt(k, text string) Dec { return decLexis(litIntText(k, text)) }
335
+ func litMoney(k, text, ck, ctext string) Money {
336
+ d := litDec(k, text)
337
+ return Money{U: d.U, S: d.S, Cur: litText(ck, ctext)}
338
+ }
339
+ func litQty(k, text string, uk, utext string) Qty {
340
+ d := litDec(k, text)
341
+ return Qty{U: d.U, S: d.S, Unit: litText(uk, utext)}
342
+ }
343
+
344
+ // Литерал Rational в вычислимом терме вопроса `term`: разбор оракула
345
+ // (`values.from_literal_term`) — запись `n/d` с целыми частями по лексике §208
346
+ // (E-0203), сократимая и со знаком в знаменателе нормализуется; не `n/d` —
347
+ // TYPE_ERROR, знаменатель 0 — DIVISION_BY_ZERO. Каноничность §168.1 требуется
348
+ // от записей дела и вопроса `truth`, не от операнда вычисления (ERR-E0005).
349
+ func ratTerm(lit string) Rational {
350
+ parts := strings.Split(lit, "/")
351
+ if len(parts) != 2 || !fractionPartLexis.MatchString(parts[0]) || !fractionPartLexis.MatchString(parts[1]) {
352
+ panic(termError{"TYPE_ERROR"})
353
+ }
354
+ n, _ := new(big.Int).SetString(parts[0], 10)
355
+ d, _ := new(big.Int).SetString(parts[1], 10)
356
+ return newRat(n, d)
357
+ }
358
+
359
+ var ratCanon = regexp.MustCompile(`^(0|-?[1-9][0-9]*)/([1-9][0-9]*)$`)
360
+
361
+ // Каноническая запись n/d (§168.1): иная — названный отказ, а не молчаливое
362
+ // сокращение — оракул хранит запись как есть, и сокращённая дала бы другой ответ.
363
+ func rat(lit string) Rational {
364
+ num, den, _ := strings.Cut(lit, "/")
365
+ if !fractionPartLexis.MatchString(num) || !fractionPartLexis.MatchString(den) {
366
+ refuse("§168.1: запись Rational не n/d: " + lit)
367
+ }
368
+ m := ratCanon.FindStringSubmatch(lit)
369
+ if m == nil {
370
+ refuse("§168.1: неканоническая запись Rational: " + lit)
371
+ }
372
+ n, _ := new(big.Int).SetString(m[1], 10)
373
+ d, _ := new(big.Int).SetString(m[2], 10)
374
+ if new(big.Int).GCD(nil, nil, new(big.Int).Abs(n), d).Cmp(big.NewInt(1)) != 0 {
375
+ refuse("§168.1: неканоническая запись Rational: " + lit)
376
+ }
377
+ return newRat(n, d)
378
+ }
379
+
380
+ // --- виды -------------------------------------------------------------------
381
+
382
+ // Числовое значение: мантисса, масштаб, валюта, «это деньги», «это число».
383
+ // Величина — число не-денег: тег проверяют `same`, `cmp` и `eq` раньше.
384
+ func scaled(v Val) (*big.Int, int, string, bool, bool) {
385
+ switch x := v.(type) {
386
+ case Money:
387
+ return x.U, x.S, x.Cur, true, true
388
+ case Qty:
389
+ return x.U, x.S, "", false, true
390
+ case Dec:
391
+ return x.U, x.S, "", false, true
392
+ }
393
+ return nil, 0, "", false, false
394
+ }
395
+
396
+ // §48/§58: сложение, вычитание, min/max требуют совпадения видов; у денег — и
397
+ // валюты, у величин — и единицы (DECISION-0256: ошибка значения).
398
+ func same(a, b Val, op string) {
399
+ if isQty(a) || isQty(b) {
400
+ x, xok := a.(Qty)
401
+ y, yok := b.(Qty)
402
+ if !xok || !yok {
403
+ panic(termError{"TYPE_ERROR"})
404
+ }
405
+ if x.Unit != y.Unit {
406
+ panic(termError{"DIMENSION_MISMATCH"})
407
+ }
408
+ return
409
+ }
410
+ _, _, ac, am, aok := scaled(a)
411
+ _, _, bc, bm, bok := scaled(b)
412
+ if !aok || !bok || am != bm {
413
+ refuse("§58: виды операндов не совпадают: " + op)
414
+ }
415
+ if am && ac != bc {
416
+ refuse("§58: разные валюты: " + op)
417
+ }
418
+ }
419
+
420
+ func pair(a, b Val) (*big.Int, *big.Int, int) {
421
+ au, as, _, _, _ := scaled(a)
422
+ bu, bs, _, _, _ := scaled(b)
423
+ s := as
424
+ if bs > s {
425
+ s = bs
426
+ }
427
+ x, y := new(big.Int).Mul(au, p10(s-as)), new(big.Int).Mul(bu, p10(s-bs))
428
+ boundedCoefficient(x, s)
429
+ boundedCoefficient(y, s)
430
+ return x, y, s
431
+ }
432
+
433
+ func mk(u *big.Int, s int, like Val) Val {
434
+ boundedCoefficient(u, s)
435
+ if q, ok := like.(Qty); ok {
436
+ return Qty{U: u, S: s, Unit: q.Unit}
437
+ }
438
+ if m, ok := like.(Money); ok {
439
+ return Money{U: u, S: s, Cur: m.Cur}
440
+ }
441
+ return Dec{U: u, S: s}
442
+ }
443
+
444
+ // Целое значение: дробная часть нулевая (защита в глубину под статикой плана).
445
+ func integral(v Val) (*big.Int, bool) {
446
+ d, ok := v.(Dec)
447
+ if !ok {
448
+ return nil, false
449
+ }
450
+ q, r := new(big.Int).QuoRem(d.U, p10(d.S), new(big.Int))
451
+ if r.Sign() != 0 {
452
+ return nil, false
453
+ }
454
+ return q, true
455
+ }
456
+
457
+ // --- арифметика §58 ---------------------------------------------------------
458
+
459
+ func isRat(v Val) bool {
460
+ _, ok := v.(Rational)
461
+ return ok
462
+ }
463
+
464
+ func add(a, b Val) Val {
465
+ if isRat(a) || isRat(b) {
466
+ return ratPair(a, b, "add")
467
+ }
468
+ same(a, b, "add")
469
+ x, y, s := pair(a, b)
470
+ return mk(new(big.Int).Add(x, y), s, a)
471
+ }
472
+
473
+ func sub(a, b Val) Val {
474
+ if isRat(a) || isRat(b) {
475
+ return ratPair(a, b, "sub")
476
+ }
477
+ same(a, b, "sub")
478
+ x, y, s := pair(a, b)
479
+ return mk(new(big.Int).Sub(x, y), s, a)
480
+ }
481
+
482
+ // Правило скаляра §49: безразмерное масштабирует размерное; масштаб результата —
483
+ // сумма масштабов. Два размерных не перемножаются.
484
+ func mul(a, b Val) Val {
485
+ au, as, _, am, aok := scaled(a)
486
+ bu, bs, _, bm, bok := scaled(b)
487
+ if !aok || !bok {
488
+ refuse("§58: виды операндов не совпадают: mul")
489
+ }
490
+ // Размерное — деньги либо величина §49 (DECISION-0256).
491
+ am, bm = am || isQty(a), bm || isQty(b)
492
+ if am && bm {
493
+ refuse("§58: произведение двух размерных величин не определено")
494
+ }
495
+ like := b
496
+ if am {
497
+ like = a
498
+ }
499
+ return mk(new(big.Int).Mul(au, bu), as+bs, like)
500
+ }
501
+
502
+ // Точное десятичное частное num/den при 10-гладком знаменателе (§18).
503
+ func exact(num, den *big.Int) (*big.Int, int, bool) {
504
+ g := new(big.Int).GCD(nil, nil, new(big.Int).Abs(num), new(big.Int).Abs(den))
505
+ if g.Sign() == 0 {
506
+ g = big.NewInt(1)
507
+ }
508
+ num = new(big.Int).Quo(num, g)
509
+ den = new(big.Int).Quo(den, g)
510
+ if den.Sign() < 0 {
511
+ num.Neg(num)
512
+ den.Neg(den)
513
+ }
514
+ two, five := big.NewInt(2), big.NewInt(5)
515
+ twos, fives := 0, 0
516
+ for new(big.Int).Mod(den, two).Sign() == 0 {
517
+ den.Quo(den, two)
518
+ twos++
519
+ }
520
+ for new(big.Int).Mod(den, five).Sign() == 0 {
521
+ den.Quo(den, five)
522
+ fives++
523
+ }
524
+ if den.Cmp(big.NewInt(1)) != 0 {
525
+ return nil, 0, false
526
+ }
527
+ k := twos
528
+ if fives > k {
529
+ k = fives
530
+ }
531
+ out := new(big.Int).Mul(num, new(big.Int).Exp(two, big.NewInt(int64(k-twos)), nil))
532
+ out.Mul(out, new(big.Int).Exp(five, big.NewInt(int64(k-fives)), nil))
533
+ return out, k, true
534
+ }
535
+
536
+ // Таблица делимых §58 в срезе: Decimal/Decimal, Money/Decimal, Money/Money одной
537
+ // валюты. Integer/Integer даёт Rational — вне среза, отвергается планом статически.
538
+ func div(a, b Val) Val {
539
+ au, as, ac, am, aok := scaled(a)
540
+ bu, bs, bc, bm, bok := scaled(b)
541
+ if !aok || !bok {
542
+ refuse("§58: деление вне таблицы делимых")
543
+ }
544
+ if bu.Sign() == 0 {
545
+ refuse("деление на ноль — evaluation error (§58)")
546
+ }
547
+ var like Val
548
+ switch {
549
+ case am && bm:
550
+ if ac != bc {
551
+ refuse("CURRENCY_MISMATCH: §58 делит Money на Money только при тождественной валюте")
552
+ }
553
+ case am:
554
+ like = a
555
+ case bm:
556
+ refuse("§58: деление вне таблицы делимых")
557
+ }
558
+ u, s, ok := exact(new(big.Int).Mul(au, p10(bs)), new(big.Int).Mul(bu, p10(as)))
559
+ if !ok {
560
+ if like != nil {
561
+ refuse("INEXACT_DIVISION: округление только явной политикой (§50/§18)")
562
+ }
563
+ refuse("§58: частное не 10-гладкое — Rational вне среза")
564
+ }
565
+ if like != nil {
566
+ return Money{U: u, S: s, Cur: like.(Money).Cur}
567
+ }
568
+ return Dec{U: u, S: s}
569
+ }
570
+
571
+ // Евклидов остаток §58: 0 <= r < |b| при любых знаках операндов.
572
+ func mod(a, b Val) Val {
573
+ x, xok := integral(a)
574
+ y, yok := integral(b)
575
+ if !xok || !yok {
576
+ refuse("§58: mod определён только для Integer")
577
+ }
578
+ if y.Sign() == 0 {
579
+ refuse("остаток от деления на ноль — evaluation error (§58)")
580
+ }
581
+ m := new(big.Int).Abs(y)
582
+ r := new(big.Int).Mod(x, m) // big.Int.Mod — евклидов
583
+ return Dec{U: r, S: 0}
584
+ }
585
+
586
+ // --- Rational §35/§58 и арифметика по видам (DECISION-0252) ------------------
587
+
588
+ func mulInt(a, b *big.Int) *big.Int { return new(big.Int).Mul(a, b) }
589
+
590
+ // add/sub/min/max над Rational: оба операнда Rational, иначе TYPE_ERROR §48.
591
+ func ratPair(a, b Val, op string) Val {
592
+ x, xok := a.(Rational)
593
+ y, yok := b.(Rational)
594
+ if !xok || !yok {
595
+ panic(termError{"TYPE_ERROR"})
596
+ }
597
+ switch op {
598
+ case "add":
599
+ return newRat(new(big.Int).Add(mulInt(x.N, y.D), mulInt(y.N, x.D)), mulInt(x.D, y.D))
600
+ case "sub":
601
+ return newRat(new(big.Int).Sub(mulInt(x.N, y.D), mulInt(y.N, x.D)), mulInt(x.D, y.D))
602
+ case "min":
603
+ if mulInt(x.N, y.D).Cmp(mulInt(y.N, x.D)) <= 0 {
604
+ return a
605
+ }
606
+ return b
607
+ }
608
+ if mulInt(x.N, y.D).Cmp(mulInt(y.N, x.D)) >= 0 {
609
+ return a
610
+ }
611
+ return b
612
+ }
613
+
614
+ // Вид значения динамического терма (DECISION-0252 п. 5): `r` Rational, `d` Decimal.
615
+ func kindOf(v Val) string {
616
+ if isRat(v) {
617
+ return "r"
618
+ }
619
+ return "d"
620
+ }
621
+
622
+ // Статический вид плана → вид значения; `n` — Decimal либо Rational по классу.
623
+ func resolveKind(v Val, k string) string {
624
+ if k == "n" {
625
+ k = kindOf(v)
626
+ }
627
+ _, isM := v.(Money)
628
+ known := k == "i" || k == "d" || k == "m" || k == "r" || k == "q"
629
+ if !known || (k == "r") != isRat(v) || (k == "m") != isM || (k == "q") != isQty(v) {
630
+ refuse("вид операнда расходится с планом: " + k)
631
+ }
632
+ return k
633
+ }
634
+
635
+ // Целое значение операнда вида Integer: мантисса без масштаба (деление вниз).
636
+ func intOf(v Val) *big.Int {
637
+ d := v.(Dec)
638
+ return new(big.Int).Div(d.U, p10(d.S))
639
+ }
640
+
641
+ // Таблица §58 `values.binary` по видам операндов (DECISION-0252 п. 3–4).
642
+ //
643
+ // Виды — из плана: `i` Integer, `d` Decimal, `m` Money, `r` Rational, `q`
644
+ // Quantity (DECISION-0256), `n` — Decimal либо Rational по значению. Integer
645
+ // и Decimal рантайм по значению не различает, поэтому пара решается видами,
646
+ // а не классами. Ошибка значения — termError с кодом оракула, в его порядке проверок.
647
+ func boundedCoefficient(u *big.Int, s int) {
648
+ limit := new(big.Int).Lsh(big.NewInt(1), 127)
649
+ if u.Cmp(new(big.Int).Neg(limit)) < 0 || u.Cmp(new(big.Int).Lsh(big.NewInt(1), 127)) >= 0 || (u.Sign() != 0 && s > 4096) {
650
+ refuse("NUMERIC_LIMIT_UNAVAILABLE: current i64/i128 arithmetic boundary")
651
+ }
652
+ }
653
+ func arith(op string, a, b Val, lk, rk string) Val {
654
+ for _, v := range []Val{a, b} {
655
+ if u, s, _, _, ok := scaled(v); ok {
656
+ boundedCoefficient(u, s)
657
+ }
658
+ }
659
+ out := arithUnchecked(op, a, b, lk, rk)
660
+ if u, s, _, _, ok := scaled(out); ok {
661
+ boundedCoefficient(u, s)
662
+ if lk == "i" && rk == "i" && (op == "add" || op == "sub" || op == "mul" || op == "mod") && !u.IsInt64() {
663
+ refuse("NUMERIC_LIMIT_UNAVAILABLE: current i64/i128 arithmetic boundary")
664
+ }
665
+ }
666
+ return out
667
+ }
668
+
669
+ func arithUnchecked(op string, a, b Val, lk, rk string) Val {
670
+ l, r := resolveKind(a, lk), resolveKind(b, rk)
671
+ has := func(k string) bool { return l == k || r == k }
672
+ switch op {
673
+ case "add", "sub":
674
+ if l == "m" && r == "m" && a.(Money).Cur != b.(Money).Cur {
675
+ panic(termError{"CURRENCY_MISMATCH"})
676
+ }
677
+ if l == "q" && r == "q" && a.(Qty).Unit != b.(Qty).Unit {
678
+ panic(termError{"DIMENSION_MISMATCH"}) // §49, T023
679
+ }
680
+ if l != r {
681
+ panic(termError{"TYPE_ERROR"})
682
+ }
683
+ if l == "r" {
684
+ return ratPair(a, b, op)
685
+ }
686
+ if op == "add" {
687
+ return add(a, b)
688
+ }
689
+ return sub(a, b)
690
+ case "mul":
691
+ if (l == "i" && r == "i") || (has("i") && (has("m") || has("d") || has("q"))) {
692
+ return mul(a, b)
693
+ }
694
+ if has("i") && has("r") {
695
+ q, k := a, b
696
+ if l != "r" {
697
+ q, k = b, a
698
+ }
699
+ qr := q.(Rational)
700
+ return newRat(mulInt(qr.N, intOf(k)), qr.D)
701
+ }
702
+ if has("i") {
703
+ panic(termError{"TYPE_ERROR"})
704
+ }
705
+ if l == "r" && r == "r" {
706
+ x, y := a.(Rational), b.(Rational)
707
+ return newRat(mulInt(x.N, y.N), mulInt(x.D, y.D))
708
+ }
709
+ if has("m") && has("r") {
710
+ // E-0085: доля масштабирует деньги точно; 10-гладкость — после сокращения.
711
+ m, q := a, b
712
+ if l != "m" {
713
+ m, q = b, a
714
+ }
715
+ mm, qr := m.(Money), q.(Rational)
716
+ u, s, ok := exact(mulInt(mm.U, qr.N), mulInt(p10(mm.S), qr.D))
717
+ if !ok {
718
+ panic(termError{"INEXACT_DIVISION"})
719
+ }
720
+ return Money{U: u, S: s, Cur: mm.Cur}
721
+ }
722
+ if has("r") || (l == "m" && r == "m") || (l == "q" && r == "q") || (has("m") && has("q")) {
723
+ panic(termError{"TYPE_ERROR"})
724
+ }
725
+ return mul(a, b)
726
+ case "div":
727
+ if l == "r" && r == "r" {
728
+ x, y := a.(Rational), b.(Rational)
729
+ if y.N.Sign() == 0 {
730
+ panic(termError{"DIVISION_BY_ZERO"})
731
+ }
732
+ return newRat(mulInt(x.N, y.D), mulInt(x.D, y.N))
733
+ }
734
+ if l == "i" && r == "i" {
735
+ y := intOf(b)
736
+ if y.Sign() == 0 {
737
+ panic(termError{"DIVISION_BY_ZERO"})
738
+ }
739
+ return newRat(intOf(a), y)
740
+ }
741
+ if has("r") {
742
+ panic(termError{"TYPE_ERROR"})
743
+ }
744
+ au, as, _, _, _ := scaled(a)
745
+ bu, bs, _, _, _ := scaled(b)
746
+ num, den := mulInt(au, p10(bs)), mulInt(bu, p10(as))
747
+ if l == "m" && r == "m" {
748
+ // E-0083: частное сумм одной валюты безразмерно — Decimal либо Rational.
749
+ if a.(Money).Cur != b.(Money).Cur {
750
+ panic(termError{"CURRENCY_MISMATCH"})
751
+ }
752
+ if bu.Sign() == 0 {
753
+ panic(termError{"DIVISION_BY_ZERO"})
754
+ }
755
+ if u, s, ok := exact(num, den); ok {
756
+ return Dec{U: u, S: s}
757
+ }
758
+ return newRat(num, den)
759
+ }
760
+ if l == "q" && r == "q" {
761
+ // E-0068: частное одной единицы безразмерно; разные единицы — TYPE_ERROR раньше нуля.
762
+ if a.(Qty).Unit != b.(Qty).Unit {
763
+ panic(termError{"TYPE_ERROR"})
764
+ }
765
+ if bu.Sign() == 0 {
766
+ panic(termError{"DIVISION_BY_ZERO"})
767
+ }
768
+ if u, s, ok := exact(num, den); ok {
769
+ return Dec{U: u, S: s}
770
+ }
771
+ return newRat(num, den)
772
+ }
773
+ if !((l == "d" && r == "d") || (l == "m" && r == "d") || (l == "q" && r == "d")) {
774
+ panic(termError{"TYPE_ERROR"})
775
+ }
776
+ if bu.Sign() == 0 {
777
+ panic(termError{"DIVISION_BY_ZERO"})
778
+ }
779
+ if u, s, ok := exact(num, den); ok {
780
+ return mk(u, s, a)
781
+ }
782
+ if l == "d" {
783
+ return newRat(num, den)
784
+ }
785
+ panic(termError{"INEXACT_DIVISION"})
786
+ case "mod":
787
+ if l == "i" && r == "i" {
788
+ y := intOf(b)
789
+ if y.Sign() == 0 {
790
+ panic(termError{"DIVISION_BY_ZERO"})
791
+ }
792
+ return Dec{U: new(big.Int).Mod(intOf(a), new(big.Int).Abs(y)), S: 0}
793
+ }
794
+ panic(termError{"TYPE_ERROR"})
795
+ }
796
+ refuse("операция вне среза рантайма: " + op)
797
+ return nil
798
+ }
799
+
800
+ func min(a, b Val) Val {
801
+ if isRat(a) || isRat(b) {
802
+ return ratPair(a, b, "min")
803
+ }
804
+ same(a, b, "min")
805
+ x, y, _ := pair(a, b)
806
+ if x.Cmp(y) <= 0 {
807
+ return a
808
+ }
809
+ return b
810
+ }
811
+
812
+ func max(a, b Val) Val {
813
+ if isRat(a) || isRat(b) {
814
+ return ratPair(a, b, "max")
815
+ }
816
+ same(a, b, "max")
817
+ x, y, _ := pair(a, b)
818
+ if x.Cmp(y) >= 0 {
819
+ return a
820
+ }
821
+ return b
822
+ }
823
+
824
+ func abs(v Val) Val {
825
+ if u, s, _, _, ok := scaled(v); ok && s == 0 && u.Cmp(i64Min) == 0 {
826
+ refuse("NUMERIC_LIMIT_UNAVAILABLE: current i64/i128 arithmetic boundary")
827
+ }
828
+ if q, isQ := v.(Rational); isQ {
829
+ return newRat(new(big.Int).Abs(q.N), q.D)
830
+ }
831
+ u, s, _, _, ok := scaled(v)
832
+ if !ok {
833
+ refuse("§58: abs — не числовой вид")
834
+ }
835
+ return mk(new(big.Int).Abs(u), s, v)
836
+ }
837
+
838
+ var powExpLimit = big.NewInt(4096)
839
+ var powMin = new(big.Int).Neg(new(big.Int).Lsh(big.NewInt(1), 63))
840
+ var powMax = new(big.Int).Sub(new(big.Int).Lsh(big.NewInt(1), 63), big.NewInt(1))
841
+
842
+ // §58 (E-0075): только Integer pow Integer; порядок проверок — знак, предел,
843
+ // вычисление, диапазон — часть контракта.
844
+ func pow(base, exp Val) Val {
845
+ x, xok := integral(base)
846
+ e, eok := integral(exp)
847
+ if !xok || !eok {
848
+ refuse("§58: pow определён только для Integer")
849
+ }
850
+ if e.Sign() < 0 {
851
+ refuse("§58: pow — показатель отрицателен")
852
+ }
853
+ if e.Cmp(powExpLimit) > 0 {
854
+ refuse("RESOURCE_LIMIT: pow — показатель превышает 4096")
855
+ }
856
+ r := new(big.Int).Exp(x, e, nil)
857
+ if r.Cmp(powMin) < 0 || r.Cmp(powMax) > 0 {
858
+ refuse("RESOURCE_LIMIT: pow — результат вне 64-битного диапазона")
859
+ }
860
+ return Dec{U: r, S: 0}
861
+ }
862
+
863
+ func round(m Val, digits Val, mode string) Val {
864
+ if mode != "HALF_UP" {
865
+ refuse("§50: режим округления не реализован: " + mode)
866
+ }
867
+ mu, ms, _, _, ok := scaled(m)
868
+ if !ok {
869
+ refuse("§50: round — не числовой вид")
870
+ }
871
+ du, ds, _, _, dok := scaled(digits)
872
+ n := 0
873
+ if dok {
874
+ n = int(new(big.Int).Div(du, p10(ds)).Int64())
875
+ }
876
+ if ms <= n {
877
+ return mk(new(big.Int).Mul(mu, p10(n-ms)), n, m)
878
+ }
879
+ drop := p10(ms - n)
880
+ a := new(big.Int).Abs(mu)
881
+ q, r := new(big.Int).QuoRem(a, drop, new(big.Int))
882
+ if new(big.Int).Mul(r, big.NewInt(2)).Cmp(drop) >= 0 {
883
+ q.Add(q, big.NewInt(1)) // HALF_UP — от нуля
884
+ }
885
+ if mu.Sign() < 0 {
886
+ q.Neg(q)
887
+ }
888
+ return mk(q, n, m)
889
+ }
890
+
891
+ // --- политика округления §50 (DECISION-0259) ---------------------------------
892
+
893
+ var roundingModes = []string{"CEILING", "DOWN", "FLOOR", "HALF_DOWN", "HALF_EVEN", "HALF_UP", "UP"}
894
+ var divRoundMaxPrecision = big.NewInt(34)
895
+
896
+ // Порт `values._round_fraction` на целых: num/den (den > 0) к целому по
897
+ // режиму §50; `2*tail` против знаменателя, ветви в порядке оракула.
898
+ func roundInt(num, den *big.Int, mode string) *big.Int {
899
+ negative := num.Sign() < 0
900
+ head, tail := new(big.Int).QuoRem(new(big.Int).Abs(num), den, new(big.Int))
901
+ c := new(big.Int).Lsh(tail, 1).Cmp(den)
902
+ up := false
903
+ switch mode {
904
+ case "HALF_UP":
905
+ up = c >= 0
906
+ case "HALF_EVEN":
907
+ up = c > 0 || (c == 0 && head.Bit(0) == 1)
908
+ case "HALF_DOWN":
909
+ up = c > 0
910
+ case "UP":
911
+ up = tail.Sign() > 0
912
+ case "DOWN":
913
+ up = false
914
+ case "CEILING":
915
+ up = tail.Sign() > 0 && !negative
916
+ case "FLOOR":
917
+ up = tail.Sign() > 0 && negative
918
+ default:
919
+ refuse("§50: режим округления вне семи: " + mode)
920
+ }
921
+ if up {
922
+ head.Add(head, big.NewInt(1))
923
+ }
924
+ if negative {
925
+ head.Neg(head)
926
+ }
927
+ return head
928
+ }
929
+
930
+ // Точность политики: Integer либо целый Decimal, вид — из плана. Дробная
931
+ // точность — TYPE_ERROR; проверка на целом произвольной длины до перевода в int.
932
+ func policyScale(p Val, pk string) *big.Int {
933
+ if pk != "i" && pk != "d" {
934
+ refuse("вид точности расходится с планом: " + pk)
935
+ }
936
+ resolveKind(p, pk)
937
+ d, ok := p.(Dec)
938
+ if !ok {
939
+ refuse("вид операнда расходится с планом: " + pk)
940
+ }
941
+ q, r := new(big.Int).QuoRem(d.U, p10(d.S), new(big.Int))
942
+ if r.Sign() != 0 {
943
+ panic(termError{"TYPE_ERROR"})
944
+ }
945
+ return q
946
+ }
947
+
948
+ // Режим политики: Text, один из семи §50; иное — TYPE_ERROR.
949
+ func policyMode(mode Val) string {
950
+ s, ok := mode.(string)
951
+ if ok {
952
+ for _, m := range roundingModes {
953
+ if m == s {
954
+ return s
955
+ }
956
+ }
957
+ }
958
+ panic(termError{"TYPE_ERROR"})
959
+ }
960
+
961
+ // §50 `round` с проверкой политики (DECISION-0259 п. 4, 7): порядок
962
+ // `values.round_value` — вид, точность (целое >= 0), режим. Integer — тождество
963
+ // (E-0110); точность не короче дробной части — значение то же (E-0184).
964
+ func roundMode(v, p, mode Val, vk, pk string) Val {
965
+ k := resolveKind(v, vk)
966
+ if k == "r" {
967
+ panic(termError{"TYPE_ERROR"})
968
+ }
969
+ scale := policyScale(p, pk)
970
+ if scale.Sign() < 0 {
971
+ panic(termError{"TYPE_ERROR"})
972
+ }
973
+ m := policyMode(mode)
974
+ u, s, _, _, _ := scaled(v)
975
+ if k == "i" || scale.Cmp(big.NewInt(int64(s))) >= 0 {
976
+ return v
977
+ }
978
+ n := int(scale.Int64()) // scale < s: помещается в int
979
+ return mk(roundInt(u, p10(s-n), m), n, v)
980
+ }
981
+
982
+ // §50/§58 `div_round` (E-0036, E-0170; DECISION-0259 п. 4, 7): пара →
983
+ // валюта → ноль → точность 0..34 → режим. Частное точное, округление всегда;
984
+ // Money/Money — доля Decimal без тега.
985
+ func divRound(a, b, p, mode Val, ak, bk, pk string) Val {
986
+ l, r := resolveKind(a, ak), resolveKind(b, bk)
987
+ if !((l == "m" && r == "d") || (l == "q" && r == "d") || (l == "d" && r == "d") || (l == "m" && r == "m")) {
988
+ panic(termError{"TYPE_ERROR"})
989
+ }
990
+ if l == "m" && r == "m" && a.(Money).Cur != b.(Money).Cur {
991
+ panic(termError{"CURRENCY_MISMATCH"})
992
+ }
993
+ au, as, _, _, _ := scaled(a)
994
+ bu, bs, _, _, _ := scaled(b)
995
+ if bu.Sign() == 0 {
996
+ panic(termError{"DIVISION_BY_ZERO"})
997
+ }
998
+ scale := policyScale(p, pk)
999
+ if scale.Sign() < 0 || scale.Cmp(divRoundMaxPrecision) > 0 {
1000
+ panic(termError{"TYPE_ERROR"})
1001
+ }
1002
+ m := policyMode(mode)
1003
+ n := int(scale.Int64())
1004
+ num, den := mulInt(au, p10(bs+n)), mulInt(bu, p10(as))
1005
+ if den.Sign() < 0 {
1006
+ num.Neg(num)
1007
+ den.Neg(den)
1008
+ }
1009
+ q := roundInt(num, den, m)
1010
+ if r == "m" {
1011
+ return Dec{U: q, S: n}
1012
+ }
1013
+ return mk(q, n, a)
1014
+ }
1015
+
1016
+ // --- сравнение и равенство ---------------------------------------------------
1017
+
1018
+ func cmp(a Val, op string, b Val) bool {
1019
+ if ab, aok := a.(bool); aok || isBool(b) {
1020
+ // §57 (DECISION-0266): Boolean сравнимо только на равенство и только с Boolean.
1021
+ bb, bok := b.(bool)
1022
+ sameBool := aok && bok && ab == bb
1023
+ switch op {
1024
+ case "eq":
1025
+ return sameBool
1026
+ case "ne":
1027
+ return !sameBool
1028
+ }
1029
+ refuse("§57: вид Boolean не упорядочен")
1030
+ }
1031
+ if isRat(a) || isRat(b) {
1032
+ // §57/§48 (DECISION-0252 п. 6): Rational сравнивается только с Rational;
1033
+ // иной вид — TYPE_ERROR оракула, а не false.
1034
+ x, xok := a.(Rational)
1035
+ y, yok := b.(Rational)
1036
+ if !xok || !yok {
1037
+ panic(termError{"TYPE_ERROR"})
1038
+ }
1039
+ c := mulInt(x.N, y.D).Cmp(mulInt(y.N, x.D))
1040
+ switch op {
1041
+ case "lt":
1042
+ return c < 0
1043
+ case "le":
1044
+ return c <= 0
1045
+ case "gt":
1046
+ return c > 0
1047
+ case "ge":
1048
+ return c >= 0
1049
+ case "eq":
1050
+ return c == 0
1051
+ case "ne":
1052
+ return c != 0
1053
+ }
1054
+ refuse("сравнение вне среза рантайма: " + op)
1055
+ }
1056
+ if isQty(a) || isQty(b) {
1057
+ // §49/§57 (DECISION-0256 п. 5): величина — только с величиной той же единицы.
1058
+ same(a, b, "cmp")
1059
+ x, y, _ := pair(a, b)
1060
+ c := x.Cmp(y)
1061
+ switch op {
1062
+ case "lt":
1063
+ return c < 0
1064
+ case "le":
1065
+ return c <= 0
1066
+ case "gt":
1067
+ return c > 0
1068
+ case "ge":
1069
+ return c >= 0
1070
+ case "eq":
1071
+ return c == 0
1072
+ case "ne":
1073
+ return c != 0
1074
+ }
1075
+ refuse("сравнение вне среза рантайма: " + op)
1076
+ }
1077
+ asym, asok := a.(Sym)
1078
+ bsym, bsok := b.(Sym)
1079
+ if asok || bsok {
1080
+ sameSym := asok && bsok && asym == bsym
1081
+ switch op {
1082
+ case "eq":
1083
+ return sameSym
1084
+ case "ne":
1085
+ return !sameSym
1086
+ }
1087
+ refuse("§57: вид enum не упорядочен")
1088
+ }
1089
+ ai, aiok := a.(Instant)
1090
+ bi, biok := b.(Instant)
1091
+ if aiok || biok {
1092
+ if aiok && biok {
1093
+ c := momentCmp(ai, bi)
1094
+ switch op {
1095
+ case "lt":
1096
+ return c < 0
1097
+ case "le":
1098
+ return c <= 0
1099
+ case "gt":
1100
+ return c > 0
1101
+ case "ge":
1102
+ return c >= 0
1103
+ case "eq":
1104
+ return c == 0
1105
+ case "ne":
1106
+ return c != 0
1107
+ }
1108
+ refuse("сравнение вне среза рантайма: " + op)
1109
+ }
1110
+ // §57/§2.15: Date и Instant не сравниваются (DATE_INSTANT_MISMATCH оракула).
1111
+ refuse("§57: Instant сравнивается только с Instant")
1112
+ }
1113
+ ad, adok := a.(Date)
1114
+ bd, bdok := b.(Date)
1115
+ if adok || bdok {
1116
+ if adok && bdok {
1117
+ switch op {
1118
+ case "lt":
1119
+ return ad < bd
1120
+ case "le":
1121
+ return ad <= bd
1122
+ case "gt":
1123
+ return ad > bd
1124
+ case "ge":
1125
+ return ad >= bd
1126
+ case "eq":
1127
+ return ad == bd
1128
+ case "ne":
1129
+ return ad != bd
1130
+ }
1131
+ refuse("сравнение вне среза рантайма: " + op)
1132
+ }
1133
+ switch op {
1134
+ case "eq":
1135
+ return false
1136
+ case "ne":
1137
+ return true
1138
+ }
1139
+ refuse("§57: Date сравнивается только с Date")
1140
+ }
1141
+ as, aok := a.(string)
1142
+ bs, bok := b.(string)
1143
+ if aok || bok {
1144
+ switch op {
1145
+ case "eq":
1146
+ return aok && bok && as == bs
1147
+ case "ne":
1148
+ return !(aok && bok && as == bs)
1149
+ }
1150
+ refuse("порядок на Text вне среза рантайма: " + op)
1151
+ }
1152
+ _, _, acur, amoney, _ := scaled(a)
1153
+ _, _, bcur, bmoney, _ := scaled(b)
1154
+ if amoney != bmoney {
1155
+ refuse("§49: сравнение денег с безразмерным")
1156
+ }
1157
+ if amoney && acur != bcur {
1158
+ refuse("§49: разные валюты")
1159
+ }
1160
+ x, y, _ := pair(a, b)
1161
+ c := x.Cmp(y)
1162
+ switch op {
1163
+ case "lt":
1164
+ return c < 0
1165
+ case "le":
1166
+ return c <= 0
1167
+ case "gt":
1168
+ return c > 0
1169
+ case "ge":
1170
+ return c >= 0
1171
+ case "eq":
1172
+ return c == 0
1173
+ case "ne":
1174
+ return c != 0
1175
+ }
1176
+ refuse("сравнение вне среза рантайма: " + op)
1177
+ return false
1178
+ }
1179
+
1180
+ func isBool(v Val) bool {
1181
+ _, ok := v.(bool)
1182
+ return ok
1183
+ }
1184
+
1185
+ func eq(a, b Val) bool {
1186
+ if isBool(a) || isBool(b) {
1187
+ // DECISION-0266: Boolean равен только Boolean того же значения.
1188
+ x, xok := a.(bool)
1189
+ y, yok := b.(bool)
1190
+ return xok && yok && x == y
1191
+ }
1192
+ if isRat(a) || isRat(b) {
1193
+ x, xok := a.(Rational)
1194
+ y, yok := b.(Rational)
1195
+ return xok && yok && x.N.Cmp(y.N) == 0 && x.D.Cmp(y.D) == 0
1196
+ }
1197
+ if isQty(a) || isQty(b) {
1198
+ x, xok := a.(Qty)
1199
+ y, yok := b.(Qty)
1200
+ if !xok || !yok || x.Unit != y.Unit {
1201
+ return false
1202
+ }
1203
+ u, w, _ := pair(a, b)
1204
+ return u.Cmp(w) == 0
1205
+ }
1206
+ asym, asok := a.(Sym)
1207
+ bsym, bsok := b.(Sym)
1208
+ if asok || bsok {
1209
+ return asok && bsok && asym == bsym
1210
+ }
1211
+ ad, adok := a.(Date)
1212
+ bd, bdok := b.(Date)
1213
+ if adok || bdok {
1214
+ return adok && bdok && ad == bd
1215
+ }
1216
+ ai, aiok := a.(Instant)
1217
+ bi, biok := b.(Instant)
1218
+ if aiok || biok {
1219
+ if !aiok || !biok {
1220
+ return false
1221
+ }
1222
+ return momentCmp(ai, bi) == 0
1223
+ }
1224
+ as, aok := a.(string)
1225
+ bs, bok := b.(string)
1226
+ if aok || bok {
1227
+ return aok && bok && as == bs
1228
+ }
1229
+ _, _, acur, amoney, _ := scaled(a)
1230
+ _, _, bcur, bmoney, _ := scaled(b)
1231
+ if amoney != bmoney || acur != bcur {
1232
+ return false
1233
+ }
1234
+ x, y, _ := pair(a, b)
1235
+ return x.Cmp(y) == 0
1236
+ }
1237
+
1238
+ func norm(u *big.Int, s int) string {
1239
+ if u.Sign() == 0 {
1240
+ return "0e-0"
1241
+ }
1242
+ v := new(big.Int).Set(u)
1243
+ ten := big.NewInt(10)
1244
+ for s > 0 && new(big.Int).Mod(v, ten).Sign() == 0 {
1245
+ v.Div(v, ten)
1246
+ s--
1247
+ }
1248
+ return fmt.Sprintf("%se-%d", v.String(), s)
1249
+ }
1250
+
1251
+ // Разделитель и экран внутри текста экранируются: иначе ["a|e:b","c"] и
1252
+ // ["a","b|e:c"] склеивались в один ключ, и второй факт терялся.
1253
+ var keyEscaper = strings.NewReplacer(`\`, `\\`, `|`, `\|`)
1254
+
1255
+ func key(row []Val) string {
1256
+ parts := make([]string, len(row))
1257
+ for i, v := range row {
1258
+ switch x := v.(type) {
1259
+ case string:
1260
+ parts[i] = "e:" + keyEscaper.Replace(x)
1261
+ case bool:
1262
+ if x {
1263
+ parts[i] = "b:true"
1264
+ } else {
1265
+ parts[i] = "b:false"
1266
+ }
1267
+ case Date:
1268
+ parts[i] = "t:" + string(x)
1269
+ case Instant:
1270
+ parts[i] = "i:" + keyEscaper.Replace(string(x))
1271
+ case Sym:
1272
+ parts[i] = "c:" + keyEscaper.Replace(string(x))
1273
+ case Rational:
1274
+ parts[i] = "r:" + x.N.String() + "/" + x.D.String()
1275
+ case Qty:
1276
+ // DECISION-0256 п. 5: тег экранируется, как текст; вид отличен от денег.
1277
+ parts[i] = "q:" + keyEscaper.Replace(x.Unit) + ":" + norm(x.U, x.S)
1278
+ case Money:
1279
+ parts[i] = "m:" + x.Cur + ":" + norm(x.U, x.S)
1280
+ case Dec:
1281
+ parts[i] = "d:" + norm(x.U, x.S)
1282
+ }
1283
+ }
1284
+ return strings.Join(parts, "|")
1285
+ }
1286
+
1287
+ // evalContext — контекст одного вычисления (DECISION-0423 §2.3): правовое время
1288
+ // дела, политика срока §86 и снимок календаря §85. Каждый экземпляр модели
1289
+ // (`newModel`) держит свой контекст; порождённые правила видят его функции через
1290
+ // локальные замыкания фабрики, поэтому параллельные вычисления его не делят.
1291
+ type evalContext struct {
1292
+ lt string
1293
+ policy map[string]interface{}
1294
+ snapshot *Snapshot
1295
+ }
1296
+
1297
+ func (c *evalContext) setLegalTime(t string) { c.lt = t }
1298
+
1299
+ func (c *evalContext) legalTime() string {
1300
+ if c.lt == "" {
1301
+ refuse("legalTime не подан делом")
1302
+ }
1303
+ return c.lt
1304
+ }
1305
+
1306
+ // --- календарь §84–§86 (DECISION-0245) ---------------------------------------
1307
+ // Порт `lawref.values` и `lawref.calendar` дословно по порядку проверок: код
1308
+ // ошибки — часть ответа (issue модели).
1309
+
1310
+ type termError struct{ code string }
1311
+
1312
+ // Снимок §85 плана: датасет либо код ошибки загрузки.
1313
+ type Snapshot struct {
1314
+ ID string `json:"id"`
1315
+ Error string `json:"error"`
1316
+ Start string `json:"start"`
1317
+ EndExclusive string `json:"endExclusive"`
1318
+ Weekly []bool `json:"weekly"`
1319
+ Overrides map[string]bool `json:"overrides"`
1320
+ }
1321
+
1322
+ func (c *evalContext) setCalendar(policy map[string]interface{}, snapshot *Snapshot) {
1323
+ c.policy, c.snapshot = policy, snapshot
1324
+ }
1325
+
1326
+ // Литерал плана JSON-строкой: политики, снимки, правила обхода E-0105.
1327
+ func planJSON(text string) interface{} {
1328
+ d := json.NewDecoder(strings.NewReader(text))
1329
+ d.UseNumber()
1330
+ var v interface{}
1331
+ if err := d.Decode(&v); err != nil {
1332
+ panic(err)
1333
+ }
1334
+ return v
1335
+ }
1336
+
1337
+ func dayOf8(day string) time.Time {
1338
+ t, err := time.Parse("2006-01-02", day)
1339
+ if err != nil {
1340
+ panic(err)
1341
+ }
1342
+ return t
1343
+ }
1344
+
1345
+ func plusDays(day string, n int) string { return dayOf8(day).AddDate(0, 0, n).Format("2006-01-02") }
1346
+
1347
+ func (c *evalContext) calendarLoaded() *Snapshot {
1348
+ if c.snapshot == nil || c.snapshot.Error == "MISSING_CALENDAR" {
1349
+ panic(termError{"MISSING_CALENDAR"})
1350
+ }
1351
+ if c.snapshot.Error != "" {
1352
+ panic(termError{c.snapshot.Error})
1353
+ }
1354
+ return c.snapshot
1355
+ }
1356
+
1357
+ func coverage(cal *Snapshot, day string) {
1358
+ if !(cal.Start <= day && day < cal.EndExclusive) {
1359
+ panic(termError{"CALENDAR_OUT_OF_RANGE"})
1360
+ }
1361
+ }
1362
+
1363
+ func working(cal *Snapshot, day string) bool {
1364
+ coverage(cal, day)
1365
+ if o, ok := cal.Overrides[day]; ok {
1366
+ return o
1367
+ }
1368
+ return cal.Weekly[(int(dayOf8(day).Weekday())+6)%7]
1369
+ }
1370
+
1371
+ func previousWorking(cal *Snapshot, day string) string {
1372
+ coverage(cal, day)
1373
+ current := plusDays(day, -1)
1374
+ for {
1375
+ coverage(cal, current)
1376
+ if working(cal, current) {
1377
+ return current
1378
+ }
1379
+ current = plusDays(current, -1)
1380
+ }
1381
+ }
1382
+
1383
+ func nextWorking(cal *Snapshot, day string) string {
1384
+ for !working(cal, day) {
1385
+ day = plusDays(day, 1)
1386
+ coverage(cal, day)
1387
+ }
1388
+ return day
1389
+ }
1390
+
1391
+ func addMonths(day string, months int, monthEnd string) string {
1392
+ t := dayOf8(day)
1393
+ total := int(t.Month()) - 1 + months
1394
+ year := t.Year() + floorDiv(total, 12)
1395
+ month := ((total%12)+12)%12 + 1
1396
+ last := time.Date(year, time.Month(month)+1, 0, 0, 0, 0, 0, time.UTC).Day()
1397
+ if t.Day() <= last {
1398
+ return time.Date(year, time.Month(month), t.Day(), 0, 0, 0, 0, time.UTC).Format("2006-01-02")
1399
+ }
1400
+ if monthEnd == "last_day_of_month" {
1401
+ return time.Date(year, time.Month(month), last, 0, 0, 0, 0, time.UTC).Format("2006-01-02")
1402
+ }
1403
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1404
+ }
1405
+
1406
+ func floorDiv(a, b int) int {
1407
+ q := a / b
1408
+ if (a%b != 0) && ((a < 0) != (b < 0)) {
1409
+ q--
1410
+ }
1411
+ return q
1412
+ }
1413
+
1414
+ func calUnit(unit string) bool { return unit == "calendar_month" || unit == "calendar_year" }
1415
+ func stepUnit(unit string) bool { return calUnit(unit) || unit == "calendar_week" }
1416
+ func policyStr(p map[string]interface{}, k string) (string, bool, bool) {
1417
+ v, present := p[k]
1418
+ if !present || v == nil {
1419
+ return "", false, true
1420
+ }
1421
+ s, ok := v.(string)
1422
+ return s, true, ok
1423
+ }
1424
+
1425
+ func needsCalendar(unit string, policy map[string]interface{}) bool {
1426
+ if !stepUnit(unit) {
1427
+ return true
1428
+ }
1429
+ roll, _, _ := policyStr(policy, "roll")
1430
+ include, isBool := policy["include_end"].(bool)
1431
+ return roll != "no_roll" || !isBool || !include
1432
+ }
1433
+
1434
+ // Порт `calendar.compute_deadline` без приостановлений (терм их не несёт).
1435
+ func deadlineDue(cal *Snapshot, after string, count int, policy map[string]interface{}, afterTime *string,
1436
+ unit string, calendarDayStep bool) string {
1437
+ if policy == nil {
1438
+ panic(termError{"MISSING_POLICY"})
1439
+ }
1440
+ for k := range policy {
1441
+ switch k {
1442
+ case "start_count", "include_end", "roll", "cutoff", "month_end":
1443
+ default:
1444
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1445
+ }
1446
+ }
1447
+ startCount, _, scOk := policyStr(policy, "start_count")
1448
+ roll, _, rollOk := policyStr(policy, "roll")
1449
+ includeEnd, ieOk := policy["include_end"].(bool)
1450
+ cutoff, hasCutoff, cutoffOk := policyStr(policy, "cutoff")
1451
+ if !scOk || (startCount != "same_day" && startCount != "next_day") || !rollOk ||
1452
+ (roll != "no_roll" && roll != "next_working_day" && roll != "previous_working_day") || !ieOk || !cutoffOk {
1453
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1454
+ }
1455
+ if count < 0 {
1456
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1457
+ }
1458
+ monthEnd, hasMonthEnd, meOk := policyStr(policy, "month_end")
1459
+ if calUnit(unit) {
1460
+ if !hasMonthEnd || !meOk || (monthEnd != "last_day_of_month" && monthEnd != "reject_nonexistent") {
1461
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1462
+ }
1463
+ } else if hasMonthEnd && (!meOk || (monthEnd != "last_day_of_month" && monthEnd != "reject_nonexistent")) {
1464
+ panic(termError{"DEADLINE_POLICY_INVALID"})
1465
+ }
1466
+ start := after
1467
+ wants := !calendarDayStep && needsCalendar(unit, policy)
1468
+ if wants {
1469
+ coverage(cal, start)
1470
+ }
1471
+ if hasCutoff && afterTime != nil && *afterTime > cutoff {
1472
+ start = plusDays(start, 1)
1473
+ if wants {
1474
+ coverage(cal, start)
1475
+ }
1476
+ }
1477
+ if startCount == "next_day" {
1478
+ start = plusDays(start, 1)
1479
+ if wants {
1480
+ coverage(cal, start)
1481
+ }
1482
+ }
1483
+ due := start
1484
+ if calUnit(unit) {
1485
+ months := count
1486
+ if unit == "calendar_year" {
1487
+ months = count * 12
1488
+ }
1489
+ if count > 0 {
1490
+ due = addMonths(start, months, monthEnd)
1491
+ }
1492
+ if count > 0 && !includeEnd {
1493
+ coverage(cal, due)
1494
+ due = previousWorking(cal, due)
1495
+ }
1496
+ } else if calendarDayStep || unit == "calendar_week" {
1497
+ multiplier := 7
1498
+ if calendarDayStep {
1499
+ multiplier = 1
1500
+ }
1501
+ if count > 0 {
1502
+ due = plusDays(start, multiplier*count)
1503
+ }
1504
+ if count > 0 && !includeEnd {
1505
+ coverage(cal, due)
1506
+ due = previousWorking(cal, due)
1507
+ }
1508
+ } else if count > 0 {
1509
+ counted := 0
1510
+ for {
1511
+ coverage(cal, due)
1512
+ if unit != "business_day" || working(cal, due) {
1513
+ counted++
1514
+ if counted == count {
1515
+ break
1516
+ }
1517
+ }
1518
+ due = plusDays(due, 1)
1519
+ }
1520
+ if !includeEnd {
1521
+ due = previousWorking(cal, due)
1522
+ }
1523
+ }
1524
+ skipRoll := (calendarDayStep || stepUnit(unit)) && roll == "no_roll"
1525
+ if !skipRoll && !working(cal, due) && roll != "no_roll" {
1526
+ if roll == "next_working_day" {
1527
+ due = nextWorking(cal, due)
1528
+ } else {
1529
+ due = previousWorking(cal, due)
1530
+ }
1531
+ }
1532
+ return due
1533
+ }
1534
+
1535
+ // §84.2 (E-0084, DECISION-0267): границы месяца — чистые функции над Date; ни
1536
+ // политика §86, ни снимок §85 не читаются (`values.month_bound_value`).
1537
+ func monthParts(d Val) (int, time.Month, int) {
1538
+ day, ok := d.(Date)
1539
+ if !ok || len(string(day)) != 10 {
1540
+ panic(termError{"TYPE_ERROR"})
1541
+ }
1542
+ t, err := time.Parse("2006-01-02", string(day))
1543
+ if err != nil {
1544
+ panic(termError{"TYPE_ERROR"})
1545
+ }
1546
+ last := time.Date(t.Year(), t.Month()+1, 0, 0, 0, 0, 0, time.UTC).Day()
1547
+ return t.Year(), t.Month(), last
1548
+ }
1549
+
1550
+ func month_start(d Val) Date {
1551
+ y, m, _ := monthParts(d)
1552
+ return Date(fmt.Sprintf("%04d-%02d-01", y, int(m)))
1553
+ }
1554
+
1555
+ func month_end(d Val) Date {
1556
+ y, m, last := monthParts(d)
1557
+ return Date(fmt.Sprintf("%04d-%02d-%02d", y, int(m), last))
1558
+ }
1559
+
1560
+ // §84.1 (E-0028/E-0058; W2 часть 2): полных единиц между мгновениями, знак сохранён, дробь к нулю.
1561
+ func durationBetween(a, b Val, unit int64, where string) Dec {
1562
+ ai, aok := a.(Instant)
1563
+ bi, bok := b.(Instant)
1564
+ if !aok || !bok {
1565
+ panic(termError{"TYPE_ERROR"})
1566
+ }
1567
+ x, y := exactMoment(ai), exactMoment(bi)
1568
+ seconds := y.seconds - x.seconds
1569
+ if seconds > 0 && y.fraction < x.fraction {
1570
+ seconds--
1571
+ } else if seconds < 0 && y.fraction > x.fraction {
1572
+ seconds++
1573
+ }
1574
+
1575
+ var units int64
1576
+ if seconds >= 0 {
1577
+ units = seconds / unit
1578
+ } else {
1579
+ units = -((-seconds) / unit)
1580
+ }
1581
+ return Dec{big.NewInt(units), 0}
1582
+ }
1583
+
1584
+ func hours_between(a, b Val) Dec { return durationBetween(a, b, 3600, "hours_between") }
1585
+ func minutes_between(a, b Val) Dec { return durationBetween(a, b, 60, "minutes_between") }
1586
+
1587
+ var durationSeconds = map[string]int64{"second": 1, "minute": 60, "hour": 3600, "day": 86400}
1588
+
1589
+ // §84.1 (E-0185): мгновение через целую Quantity секунд/минут/часов/суток; канон UTC без дробных секунд.
1590
+ func add_duration(after Val, count int, unit string) Instant {
1591
+ ai, ok := after.(Instant)
1592
+ factor, unitOk := durationSeconds[unit]
1593
+ if !ok || !unitOk {
1594
+ panic(termError{"TYPE_ERROR"})
1595
+ }
1596
+ x := exactMoment(ai)
1597
+ delta := new(big.Int).Mul(big.NewInt(int64(count)), big.NewInt(factor))
1598
+ total := new(big.Int).Add(big.NewInt(x.seconds), delta)
1599
+ if !total.IsInt64() {
1600
+ panic(termError{"RESOURCE_LIMIT"})
1601
+ }
1602
+ result := time.Unix(total.Int64(), 0).UTC()
1603
+ if result.Year() < 1 || result.Year() > 9999 {
1604
+ panic(termError{"RESOURCE_LIMIT"})
1605
+ }
1606
+
1607
+ return Instant(result.Format("2006-01-02T15:04:05Z"))
1608
+ }
1609
+
1610
+ // §84.4 (E-0015): календарные дни от `a` до `b`, со знаком.
1611
+ func days_between(a, b Date) Dec {
1612
+ days := int64(dayOf8(string(b)).Sub(dayOf8(string(a))).Hours() / 24)
1613
+ return Dec{big.NewInt(days), 0}
1614
+ }
1615
+
1616
+ // §84.2 (E-0029): `TermEnv.calendar_period` — снимок не читается.
1617
+ func (c *evalContext) add_calendar_period(after Date, count int, unit string) Date {
1618
+ p := c.policy
1619
+ if p == nil {
1620
+ p = map[string]interface{}{}
1621
+ }
1622
+ roll, hasRoll, _ := policyStr(p, "roll")
1623
+ _, hasCutoff, _ := policyStr(p, "cutoff")
1624
+ include, isBool := p["include_end"].(bool)
1625
+ if (hasRoll && roll != "no_roll") || hasCutoff || !isBool || !include {
1626
+ panic(termError{"MISSING_POLICY"})
1627
+ }
1628
+ return Date(deadlineDue(nil, string(after), count, p, nil, unit, unit == "calendar_day"))
1629
+ }
1630
+
1631
+ // Единицы счётчика-связывателя по функции (E-0029/E-0109).
1632
+ var counterUnits = map[string][]string{
1633
+ "add_business_days": {"business_day", "calendar_day"},
1634
+ "add_calendar_period": {"calendar_day", "calendar_month", "calendar_year", "calendar_week"},
1635
+ "add_legal_term": {"business_day", "calendar_day", "calendar_month", "calendar_year", "calendar_week"},
1636
+ }
1637
+
1638
+ // §86.1 (DECISION-0256 п. 6): счётчик-связыватель — Quantity с единицей,
1639
+ // допустимой функции, целый; иначе TYPE_ERROR (`values.add_*_value`). Знак не
1640
+ // проверяется: отрицательный счётчик отвергает сама операция политикой §86.
1641
+ func counter(v Val, fn string) (int, string) {
1642
+ q, ok := v.(Qty)
1643
+ if !ok {
1644
+ panic(termError{"TYPE_ERROR"})
1645
+ }
1646
+ allowed := false
1647
+ for _, u := range counterUnits[fn] {
1648
+ if u == q.Unit {
1649
+ allowed = true
1650
+ }
1651
+ }
1652
+ whole, rest := new(big.Int).QuoRem(q.U, p10(q.S), new(big.Int))
1653
+ if !allowed || rest.Sign() != 0 {
1654
+ panic(termError{"TYPE_ERROR"})
1655
+ }
1656
+ return int(whole.Int64()), q.Unit
1657
+ }
1658
+
1659
+ // §86.1: ветвь по единице на деле — рабочий счёт либо календарный шаг.
1660
+ func (c *evalContext) add_legal_term(after Date, count int, unit string) Date {
1661
+ if unit == "business_day" || unit == "calendar_day" {
1662
+ return c.add_business_days(after, count, unit)
1663
+ }
1664
+ return c.add_calendar_period(after, count, unit)
1665
+ }
1666
+
1667
+ // §86: `TermEnv.deadline` — политика, затем снимок, если срок его спрашивает.
1668
+ func (c *evalContext) add_business_days(after Date, count int, unit string, afterTime ...Val) Date {
1669
+ if c.policy != nil {
1670
+ if _, hasCutoff, _ := policyStr(c.policy, "cutoff"); hasCutoff && len(afterTime) == 0 {
1671
+ panic(termError{"MISSING_POLICY"})
1672
+ }
1673
+ }
1674
+ var cal *Snapshot
1675
+ if needsCalendar(unit, c.policy) {
1676
+ cal = c.calendarLoaded()
1677
+ }
1678
+ var at *string
1679
+ if len(afterTime) == 1 {
1680
+ s := fmt.Sprint(afterTime[0])
1681
+ at = &s
1682
+ }
1683
+ return Date(deadlineDue(cal, string(after), count, c.policy, at, unit, false))
1684
+ }
1685
+
1686
+ // Поимка ошибки календаря в гарде, голове и границе окна (DECISION-0245 п. 6, 8).
1687
+ func tryBool(f func() bool) (ok bool, code string) {
1688
+ defer func() {
1689
+ if rec := recover(); rec != nil {
1690
+ if e, isT := rec.(termError); isT {
1691
+ code = e.code
1692
+ return
1693
+ }
1694
+ panic(rec)
1695
+ }
1696
+ }()
1697
+ return f(), ""
1698
+ }
1699
+
1700
+ func tryRow(f func() []Val) (row []Val, code string) {
1701
+ defer func() {
1702
+ if rec := recover(); rec != nil {
1703
+ if e, isT := rec.(termError); isT {
1704
+ code = e.code
1705
+ return
1706
+ }
1707
+ panic(rec)
1708
+ }
1709
+ }()
1710
+ return f(), ""
1711
+ }
1712
+
1713
+ func planSnapshots(text string) []Snapshot {
1714
+ var out []Snapshot
1715
+ if err := json.Unmarshal([]byte(text), &out); err != nil {
1716
+ panic(err)
1717
+ }
1718
+ return out
1719
+ }
1720
+
1721
+ // ---------------------------------------------------------------------------
1722
+ // §47.3 Канал суждения (DECISION-0274): порт `whynot.judgment_blockers`,
1723
+ // `defeater_judgment_blockers`, `power_judgment_blockers` и
1724
+ // `position_judgment_blockers` над ГОТОВЫМ состоянием модели. Один код для
1725
+ // раннера и API; модель отдаёт состояние функциями-доступами (judgmentDecls,
1726
+ // backchainEnv, truthOf, appliedCands, supportBits, prioHas, judgmentPositions,
1727
+ // blockedImmunity), переводы термов приходят в jHooks. Запрос называет судью
1728
+ // только там, где его ответ может изменить исход: живость правила по
1729
+ // конъюнктам (E-0101), по поражению вывода (E-0103), по способности дефитера
1730
+ // снять вывод (E-0100), по иммунитету (E-0120); порядок запросов —
1731
+ // канонические байты объекта `request` (E-0111).
1732
+
1733
+ type judgmentDecl struct {
1734
+ Authority string
1735
+ RequestSchema interface{} // nil — форма запроса не объявлена
1736
+ }
1737
+
1738
+ type appliedCand struct {
1739
+ rule string
1740
+ defeater bool
1741
+ urn string
1742
+ positive bool
1743
+ key string
1744
+ defeated bool
1745
+ }
1746
+
1747
+ type jLit struct {
1748
+ pred string
1749
+ positive bool
1750
+ row []Val
1751
+ }
1752
+
1753
+ type jPos struct {
1754
+ modality, status, rule string
1755
+ exercise, validWhen, effect, cond *jLit
1756
+ }
1757
+
1758
+ // Доступы модели для обхода: печатает порождённая модель (`judgmentModel()`).
1759
+ type jModel struct {
1760
+ truth func(urn string, row []Val) string
1761
+ tables func() map[string][][]Val
1762
+ backchain func() ([]interface{}, map[string]interface{})
1763
+ decls func() map[string]judgmentDecl
1764
+ applied func() []appliedCand
1765
+ supportBits func(urn string, row []Val) int
1766
+ prioHas func(a, b string) bool
1767
+ positions func() []jPos
1768
+ blockedImmunity func(pred string, row []Val) bool
1769
+ legalTime func() string // правовое время вычисления (DECISION-0423 §2.3)
1770
+ }
1771
+
1772
+ type jHooks struct {
1773
+ val func(map[string]interface{}) Val // литерал-терм §53 → значение
1774
+ cell func(pred string, col int, v Val) map[string]interface{} // ячейка таблицы → литерал-терм
1775
+ }
1776
+
1777
+ const judgmentEnumCap = 8
1778
+
1779
+ var jTestable = map[string]bool{"established": true, "supported": true, "monotone": true, "refuted": true}
1780
+ var jMirror = map[string]string{"TRUE_ONLY": "FALSE_ONLY", "FALSE_ONLY": "TRUE_ONLY", "BOTH": "BOTH", "NEITHER": "NEITHER"}
1781
+ var jConjunct = map[string]string{"TRUE_ONLY": "SATISFIED", "FALSE_ONLY": "NOT_SATISFIED", "BOTH": "CONFLICTED", "NEITHER": "UNDETERMINED"}
1782
+ var jComputed = map[string]bool{"binary": true, "call": true, "if": true, "let": true, "match": true, "aggregate": true}
1783
+
1784
+ func jfield(m map[string]interface{}, k string) map[string]interface{} {
1785
+ if m == nil {
1786
+ return nil
1787
+ }
1788
+ if x, ok := m[k].(map[string]interface{}); ok {
1789
+ return x
1790
+ }
1791
+ return nil
1792
+ }
1793
+
1794
+ func jlist(m map[string]interface{}, k string) []interface{} {
1795
+ if m == nil {
1796
+ return nil
1797
+ }
1798
+ if x, ok := m[k].([]interface{}); ok {
1799
+ return x
1800
+ }
1801
+ return nil
1802
+ }
1803
+
1804
+ func jstr(v interface{}) string {
1805
+ if s, ok := v.(string); ok {
1806
+ return s
1807
+ }
1808
+ return ""
1809
+ }
1810
+
1811
+ func jstrJSON(s string) string {
1812
+ var b bytes.Buffer
1813
+ enc := json.NewEncoder(&b)
1814
+ enc.SetEscapeHTML(false)
1815
+ _ = enc.Encode(s)
1816
+ return strings.TrimRight(b.String(), "\n")
1817
+ }
1818
+
1819
+ // Канонические байты §208 объекта запроса: ключи по порядку, целое из JSON — цифрами.
1820
+ func jcanon(v interface{}) string {
1821
+ switch x := v.(type) {
1822
+ case nil:
1823
+ return "null"
1824
+ case bool:
1825
+ if x {
1826
+ return "true"
1827
+ }
1828
+ return "false"
1829
+ case string:
1830
+ return jstrJSON(x)
1831
+ case json.Number:
1832
+ return x.String()
1833
+ case float64:
1834
+ return strconv.FormatFloat(x, 'f', -1, 64)
1835
+ case int:
1836
+ return strconv.Itoa(x)
1837
+ case []interface{}:
1838
+ parts := make([]string, len(x))
1839
+ for i, e := range x {
1840
+ parts[i] = jcanon(e)
1841
+ }
1842
+ return "[" + strings.Join(parts, ",") + "]"
1843
+ case map[string]interface{}:
1844
+ if len(x) == 1 {
1845
+ if n, ok := x["int"]; ok {
1846
+ return jcanon(n)
1847
+ }
1848
+ }
1849
+ keys := make([]string, 0, len(x))
1850
+ for k := range x {
1851
+ keys = append(keys, k)
1852
+ }
1853
+ sort.Strings(keys)
1854
+ parts := make([]string, len(keys))
1855
+ for i, k := range keys {
1856
+ parts[i] = jstrJSON(k) + ":" + jcanon(x[k])
1857
+ }
1858
+ return "{" + strings.Join(parts, ",") + "}"
1859
+ }
1860
+ return "null"
1861
+ }
1862
+
1863
+ func jpolarity(lit map[string]interface{}) string {
1864
+ if p := jstr(lit["polarity"]); p != "" {
1865
+ return p
1866
+ }
1867
+ return "positive"
1868
+ }
1869
+
1870
+ func requestKey(lit map[string]interface{}) string {
1871
+ args := jlist(lit, "args")
1872
+ if args == nil {
1873
+ args = []interface{}{}
1874
+ }
1875
+ return jcanon(map[string]interface{}{"relation": lit["predicate"], "polarity": jpolarity(lit), "args": args})
1876
+ }
1877
+
1878
+ func orderRequests(lits []map[string]interface{}) []map[string]interface{} {
1879
+ found := map[string]map[string]interface{}{}
1880
+ keys := []string{}
1881
+ for _, l := range lits {
1882
+ k := requestKey(l)
1883
+ if _, ok := found[k]; !ok {
1884
+ found[k] = l
1885
+ keys = append(keys, k)
1886
+ }
1887
+ }
1888
+ sort.Strings(keys)
1889
+ out := make([]map[string]interface{}, len(keys))
1890
+ for i, k := range keys {
1891
+ out[i] = found[k]
1892
+ }
1893
+ return out
1894
+ }
1895
+
1896
+ func jtruth(m jModel, h jHooks, lit map[string]interface{}) string {
1897
+ args := jlist(lit, "args")
1898
+ row := make([]Val, len(args))
1899
+ for i, a := range args {
1900
+ row[i] = h.val(a.(map[string]interface{}))
1901
+ }
1902
+ st := m.truth(jstr(lit["predicate"]), row)
1903
+ if jpolarity(lit) == "negative" {
1904
+ return jMirror[st]
1905
+ }
1906
+ return st
1907
+ }
1908
+
1909
+ func jvars(t interface{}, out map[string]bool) {
1910
+ switch x := t.(type) {
1911
+ case []interface{}:
1912
+ for _, e := range x {
1913
+ jvars(e, out)
1914
+ }
1915
+ case map[string]interface{}:
1916
+ if jstr(x["kind"]) == "var" {
1917
+ out[jstr(x["var"])] = true
1918
+ return
1919
+ }
1920
+ for _, e := range x {
1921
+ jvars(e, out)
1922
+ }
1923
+ }
1924
+ }
1925
+
1926
+ func junbound(t interface{}, subst map[string]map[string]interface{}) []string {
1927
+ names := map[string]bool{}
1928
+ jvars(t, names)
1929
+ out := []string{}
1930
+ for n := range names {
1931
+ if _, ok := subst[n]; !ok {
1932
+ out = append(out, n)
1933
+ }
1934
+ }
1935
+ sort.Strings(out)
1936
+ return out
1937
+ }
1938
+
1939
+ func jkind(name string) string {
1940
+ switch name {
1941
+ case "urn:law:std#Integer", "urn:law:std#Natural":
1942
+ return "i"
1943
+ case "urn:law:std#Decimal":
1944
+ return "d"
1945
+ case "urn:law:std#Money":
1946
+ return "m"
1947
+ case "urn:law:std#Rational":
1948
+ return "r"
1949
+ case "urn:law:std#Quantity":
1950
+ return "q"
1951
+ }
1952
+ return "x"
1953
+ }
1954
+
1955
+ // Гард §57–§58 под подстановкой: переменные, литералы, константы §54 и
1956
+ // арифметика §58; иное — паника, у оракула это UNDETERMINED (E-0101).
1957
+ func jterm(m jModel, h jHooks, t map[string]interface{}, subst map[string]map[string]interface{}) (Val, string) {
1958
+ switch jstr(t["kind"]) {
1959
+ case "var":
1960
+ s, ok := subst[jstr(t["var"])]
1961
+ if !ok {
1962
+ panic(Refused("несвязанная переменная гарда"))
1963
+ }
1964
+ return jterm(m, h, s, subst)
1965
+ case "const_ref":
1966
+ if _, consts := m.backchain(); consts != nil {
1967
+ if c, has := consts[jstr(t["id"])].(map[string]interface{}); has {
1968
+ return jterm(m, h, c, subst)
1969
+ }
1970
+ }
1971
+ return h.val(t), "x"
1972
+ case "entity_ref":
1973
+ return h.val(t), "x"
1974
+ case "value":
1975
+ return h.val(t), jkind(jstr(jfield(t, "type")["name"]))
1976
+ case "binary":
1977
+ op := jstr(t["op"])
1978
+ switch op {
1979
+ case "add", "sub", "mul", "div", "mod":
1980
+ lv, lk := jterm(m, h, jfield(t, "left"), subst)
1981
+ rv, rk := jterm(m, h, jfield(t, "right"), subst)
1982
+ if lk == "x" || rk == "x" {
1983
+ panic(Refused("арифметика над нечисловым операндом"))
1984
+ }
1985
+ v := arith(op, lv, rv, lk, rk)
1986
+ switch v.(type) {
1987
+ case Rational:
1988
+ return v, "r"
1989
+ case Money:
1990
+ return v, "m"
1991
+ case Qty:
1992
+ return v, "q"
1993
+ }
1994
+ if lk == "i" && rk == "i" {
1995
+ return v, "i"
1996
+ }
1997
+ return v, "d"
1998
+ }
1999
+ }
2000
+ panic(Refused("терм гарда вне обхода суждения: " + jstr(t["kind"])))
2001
+ }
2002
+
2003
+ func jguard(m jModel, h jHooks, g map[string]interface{}, subst map[string]map[string]interface{}) (ok bool, failed bool) {
2004
+ defer func() {
2005
+ if r := recover(); r != nil {
2006
+ ok, failed = false, true
2007
+ }
2008
+ }()
2009
+ lv, _ := jterm(m, h, jfield(g, "left"), subst)
2010
+ rv, _ := jterm(m, h, jfield(g, "right"), subst)
2011
+ return cmp(lv, jstr(g["op"]), rv), false
2012
+ }
2013
+
2014
+ type jReport struct {
2015
+ trigger string
2016
+ ground map[string]interface{} // nil у гарда, квантора и вычисляемого аргумента
2017
+ }
2018
+
2019
+ func jsubstArgs(lit map[string]interface{}, subst map[string]map[string]interface{}) map[string]interface{} {
2020
+ args := jlist(lit, "args")
2021
+ out := make([]interface{}, len(args))
2022
+ for i, a := range args {
2023
+ am := a.(map[string]interface{})
2024
+ if jstr(am["kind"]) == "var" {
2025
+ out[i] = subst[jstr(am["var"])]
2026
+ } else {
2027
+ out[i] = am
2028
+ }
2029
+ }
2030
+ shown := map[string]interface{}{}
2031
+ for k, v := range lit {
2032
+ shown[k] = v
2033
+ }
2034
+ shown["args"] = out
2035
+ return shown
2036
+ }
2037
+
2038
+ // Порт `whynot._conjunct_report`: ok=false — конъюнкт с несвязанной переменной.
2039
+ func jconjunct(m jModel, h jHooks, status string, lit map[string]interface{}, subst map[string]map[string]interface{}) (jReport, bool) {
2040
+ if status == "guard" {
2041
+ if len(junbound(lit, subst)) > 0 {
2042
+ return jReport{}, false
2043
+ }
2044
+ val, failed := jguard(m, h, lit, subst)
2045
+ if failed {
2046
+ return jReport{"UNDETERMINED", nil}, true
2047
+ }
2048
+ if val {
2049
+ return jReport{"SATISFIED", nil}, true
2050
+ }
2051
+ return jReport{"NOT_SATISFIED", nil}, true
2052
+ }
2053
+ if jstr(lit["kind"]) != "literal" {
2054
+ return jReport{"UNDETERMINED", nil}, true
2055
+ }
2056
+ args := jlist(lit, "args")
2057
+ if len(junbound(args, subst)) > 0 {
2058
+ return jReport{}, false
2059
+ }
2060
+ wild := false
2061
+ for _, a := range args {
2062
+ k := jstr(a.(map[string]interface{})["kind"])
2063
+ if jComputed[k] {
2064
+ return jReport{"UNDETERMINED", nil}, true
2065
+ }
2066
+ if k == "wildcard" {
2067
+ wild = true
2068
+ }
2069
+ }
2070
+ shown := jsubstArgs(lit, subst)
2071
+ if wild {
2072
+ // §98 (E-0165): свидетель существования, значение не связывается.
2073
+ if jpolarity(lit) != "positive" || !(status == "established" || status == "supported" || status == "monotone") {
2074
+ return jReport{"UNDETERMINED", shown}, true
2075
+ }
2076
+ want := []Val{}
2077
+ wildAt := []bool{}
2078
+ for _, a := range jlist(shown, "args") {
2079
+ am := a.(map[string]interface{})
2080
+ if jstr(am["kind"]) == "wildcard" {
2081
+ want = append(want, nil)
2082
+ wildAt = append(wildAt, true)
2083
+ } else {
2084
+ want = append(want, h.val(am))
2085
+ wildAt = append(wildAt, false)
2086
+ }
2087
+ }
2088
+ pred := jstr(lit["predicate"])
2089
+ witness := false
2090
+ for _, row := range m.tables()[pred] {
2091
+ if len(row) != len(want) {
2092
+ continue
2093
+ }
2094
+ all := true
2095
+ for i := range row {
2096
+ if !wildAt[i] && key([]Val{row[i]}) != key([]Val{want[i]}) {
2097
+ all = false
2098
+ break
2099
+ }
2100
+ }
2101
+ if !all {
2102
+ continue
2103
+ }
2104
+ st := m.truth(pred, row)
2105
+ if (status == "established" && st == "TRUE_ONLY") || (status != "established" && (st == "TRUE_ONLY" || st == "BOTH")) {
2106
+ witness = true
2107
+ break
2108
+ }
2109
+ }
2110
+ if witness {
2111
+ return jReport{"SATISFIED", shown}, true
2112
+ }
2113
+ return jReport{"UNDETERMINED", shown}, true
2114
+ }
2115
+ pair := jtruth(m, h, shown)
2116
+ switch status {
2117
+ case "not_known":
2118
+ if pair == "NEITHER" {
2119
+ return jReport{"SATISFIED", shown}, true
2120
+ }
2121
+ return jReport{"NOT_SATISFIED", shown}, true
2122
+ case "supported", "monotone":
2123
+ if pair == "TRUE_ONLY" || pair == "BOTH" {
2124
+ return jReport{"SATISFIED", shown}, true
2125
+ }
2126
+ return jReport{"UNDETERMINED", shown}, true
2127
+ case "refuted", "unknown":
2128
+ want := "FALSE_ONLY"
2129
+ if status == "unknown" {
2130
+ want = "NEITHER"
2131
+ }
2132
+ if pair == want {
2133
+ return jReport{"SATISFIED", shown}, true
2134
+ }
2135
+ return jReport{"NOT_SATISFIED", shown}, true
2136
+ }
2137
+ tr, ok := jConjunct[pair]
2138
+ if !ok {
2139
+ tr = "UNDETERMINED"
2140
+ }
2141
+ return jReport{tr, shown}, true
2142
+ }
2143
+
2144
+ // Порт `unify_head`: подстановка головы под ground-цель; переменная дважды — одинаково.
2145
+ func junify(head, target map[string]interface{}) (map[string]map[string]interface{}, bool) {
2146
+ if jstr(head["kind"]) != "literal" || jstr(target["kind"]) != "literal" {
2147
+ return nil, false
2148
+ }
2149
+ if jstr(head["predicate"]) != jstr(target["predicate"]) || jpolarity(head) != jpolarity(target) {
2150
+ return nil, false
2151
+ }
2152
+ h, g := jlist(head, "args"), jlist(target, "args")
2153
+ if len(h) != len(g) {
2154
+ return nil, false
2155
+ }
2156
+ subst := map[string]map[string]interface{}{}
2157
+ for i := range h {
2158
+ hm, gm := h[i].(map[string]interface{}), g[i].(map[string]interface{})
2159
+ if jstr(hm["kind"]) == "var" {
2160
+ name := jstr(hm["var"])
2161
+ if prev, ok := subst[name]; ok && jcanon(prev) != jcanon(gm) {
2162
+ return nil, false
2163
+ }
2164
+ subst[name] = gm
2165
+ } else if jcanon(hm) != jcanon(gm) {
2166
+ return nil, false
2167
+ }
2168
+ }
2169
+ return subst, true
2170
+ }
2171
+
2172
+ // DECISION-0036 (б): перечисление судейского конъюнкта по активному домену —
2173
+ // аргументы всех атомов таблиц, порядок §208, кап ДО фильтра по NEITHER.
2174
+ func jenumerate(m jModel, h jHooks, lit map[string]interface{}, subst map[string]map[string]interface{}, unbound []string) []map[string]interface{} {
2175
+ args := jlist(lit, "args")
2176
+ for _, a := range args {
2177
+ if jstr(a.(map[string]interface{})["kind"]) == "wildcard" {
2178
+ return nil
2179
+ }
2180
+ }
2181
+ domain := map[string]map[string]interface{}{}
2182
+ for pred, rows := range m.tables() {
2183
+ for _, row := range rows {
2184
+ for i, v := range row {
2185
+ t := h.cell(pred, i, v)
2186
+ domain[jcanon(t)] = t
2187
+ }
2188
+ }
2189
+ }
2190
+ keys := make([]string, 0, len(domain))
2191
+ for k := range domain {
2192
+ keys = append(keys, k)
2193
+ }
2194
+ sort.Strings(keys)
2195
+ if len(keys) == 0 {
2196
+ return nil
2197
+ }
2198
+ total := 1
2199
+ for range unbound {
2200
+ total *= len(keys)
2201
+ if total > judgmentEnumCap {
2202
+ total = judgmentEnumCap + 1
2203
+ break
2204
+ }
2205
+ }
2206
+ out := []map[string]interface{}{}
2207
+ for idx := 0; idx < total && idx < judgmentEnumCap; idx++ {
2208
+ // itertools.product: первая переменная меняется медленнее всех.
2209
+ full := map[string]map[string]interface{}{}
2210
+ for k, v := range subst {
2211
+ full[k] = v
2212
+ }
2213
+ rest := idx
2214
+ for j := len(unbound) - 1; j >= 0; j-- {
2215
+ full[unbound[j]] = domain[keys[rest%len(keys)]]
2216
+ rest /= len(keys)
2217
+ }
2218
+ ground := jsubstArgs(lit, full)
2219
+ if jtruth(m, h, ground) == "NEITHER" {
2220
+ out = append(out, ground)
2221
+ }
2222
+ }
2223
+ return out
2224
+ }
2225
+
2226
+ func jrowKey(h jHooks, lit map[string]interface{}) string {
2227
+ args := jlist(lit, "args")
2228
+ row := make([]Val, len(args))
2229
+ for i, a := range args {
2230
+ row[i] = h.val(a.(map[string]interface{}))
2231
+ }
2232
+ return key(row)
2233
+ }
2234
+
2235
+ // E-0103: поражён ли был бы вывод поражаемого правила при любом ответе судьи.
2236
+ func jconclusionDefeated(m jModel, h jHooks, rule, target map[string]interface{}) bool {
2237
+ if jstr(rule["strength"]) != "defeasible" {
2238
+ return false
2239
+ }
2240
+ tkey, pos := jrowKey(h, target), jpolarity(target) != "negative"
2241
+ for _, a := range m.applied() {
2242
+ if !a.defeater || a.urn != jstr(target["predicate"]) || a.key != tkey || a.positive != pos {
2243
+ continue
2244
+ }
2245
+ if !m.prioHas(jstr(rule["name"]), a.rule) {
2246
+ return true
2247
+ }
2248
+ }
2249
+ return false
2250
+ }
2251
+
2252
+ // E-0100: способен ли живой дефитер снять СТОЯЩИЙ вывод.
2253
+ func jdefeaterCanRemove(m jModel, h jHooks, rule, target map[string]interface{}) bool {
2254
+ args := jlist(target, "args")
2255
+ row := make([]Val, len(args))
2256
+ for i, a := range args {
2257
+ row[i] = h.val(a.(map[string]interface{}))
2258
+ }
2259
+ pos := jpolarity(target) != "negative"
2260
+ side := 2
2261
+ if pos {
2262
+ side = 1
2263
+ }
2264
+ if m.supportBits(jstr(target["predicate"]), row)&side != 0 {
2265
+ return false
2266
+ }
2267
+ tkey := key(row)
2268
+ apps := []appliedCand{}
2269
+ for _, a := range m.applied() {
2270
+ if !a.defeater && !a.defeated && a.urn == jstr(target["predicate"]) && a.key == tkey && a.positive == pos {
2271
+ apps = append(apps, a)
2272
+ }
2273
+ }
2274
+ if len(apps) == 0 {
2275
+ return false
2276
+ }
2277
+ for _, a := range apps {
2278
+ if m.prioHas(a.rule, jstr(rule["name"])) {
2279
+ return false
2280
+ }
2281
+ }
2282
+ return true
2283
+ }
2284
+
2285
+ // Порт `whynot._collect_judgments`: общий обход §185 от цели без опоры
2286
+ // (rootDefeaters=false) и от стоящей цели через дефитеры её головы (true).
2287
+ func collectJudgments(m jModel, h jHooks, goal map[string]interface{}, rootDefeaters bool) []map[string]interface{} {
2288
+ J := m.decls()
2289
+ rules, _ := m.backchain()
2290
+ index := map[string][]map[string]interface{}{}
2291
+ for _, x := range rules {
2292
+ r := x.(map[string]interface{})
2293
+ pred := jstr(jfield(r, "head")["predicate"])
2294
+ index[pred] = append(index[pred], r)
2295
+ }
2296
+ found := map[string]map[string]interface{}{}
2297
+ order := []string{}
2298
+ visited := map[string]bool{}
2299
+ now := m.legalTime()
2300
+ add := func(g map[string]interface{}) {
2301
+ k := requestKey(g)
2302
+ if _, ok := found[k]; !ok {
2303
+ found[k] = g
2304
+ order = append(order, k)
2305
+ }
2306
+ }
2307
+ type rep struct {
2308
+ st string
2309
+ lit map[string]interface{}
2310
+ r jReport
2311
+ ok bool
2312
+ }
2313
+ var walk func(target map[string]interface{}, defeatersOnly bool)
2314
+ walk = func(target map[string]interface{}, defeatersOnly bool) {
2315
+ vkey := jcanon(map[string]interface{}{"p": target["predicate"], "a": jlist(target, "args"), "n": jpolarity(target)})
2316
+ if visited[vkey] {
2317
+ return
2318
+ }
2319
+ visited[vkey] = true
2320
+ for _, r := range index[jstr(target["predicate"])] {
2321
+ if defeatersOnly && jstr(r["strength"]) != "defeater" {
2322
+ continue
2323
+ }
2324
+ subst, ok := junify(jfield(r, "head"), target)
2325
+ if !ok {
2326
+ continue
2327
+ }
2328
+ if w := jfield(r, "window"); w != nil {
2329
+ if s := jstr(w["start"]); s != "" && now < s {
2330
+ continue
2331
+ }
2332
+ if e := jstr(w["end"]); e != "" && now >= e {
2333
+ continue
2334
+ }
2335
+ }
2336
+ reports := []rep{}
2337
+ for _, c := range jlist(r, "body") {
2338
+ pair := c.([]interface{})
2339
+ st, lit := jstr(pair[0]), pair[1].(map[string]interface{})
2340
+ rp, bound := jconjunct(m, h, st, lit, subst)
2341
+ reports = append(reports, rep{st, lit, rp, bound})
2342
+ }
2343
+ dead := false
2344
+ for _, x := range reports {
2345
+ if !x.ok || (x.r.trigger != "NOT_SATISFIED" && x.r.trigger != "CONFLICTED") {
2346
+ continue
2347
+ }
2348
+ // DECISION-0036 (а): refuted-тест над самим отношением суждения при паре NEITHER решает судья.
2349
+ if x.st == "refuted" && x.r.ground != nil {
2350
+ if _, isJ := J[jstr(x.r.ground["predicate"])]; isJ && jtruth(m, h, x.r.ground) == "NEITHER" {
2351
+ continue
2352
+ }
2353
+ }
2354
+ dead = true
2355
+ break
2356
+ }
2357
+ if dead || jconclusionDefeated(m, h, r, target) {
2358
+ continue
2359
+ }
2360
+ if defeatersOnly && !jdefeaterCanRemove(m, h, r, target) {
2361
+ continue
2362
+ }
2363
+ for _, x := range reports {
2364
+ if !x.ok {
2365
+ if _, isJ := J[jstr(x.lit["predicate"])]; jTestable[x.st] && jstr(x.lit["kind"]) == "literal" && isJ {
2366
+ for _, g := range jenumerate(m, h, x.lit, subst, junbound(jlist(x.lit, "args"), subst)) {
2367
+ add(g)
2368
+ }
2369
+ }
2370
+ continue
2371
+ }
2372
+ ground := x.r.ground
2373
+ if ground == nil || jstr(ground["kind"]) != "literal" {
2374
+ continue
2375
+ }
2376
+ hasWild := false
2377
+ for _, a := range jlist(ground, "args") {
2378
+ if jstr(a.(map[string]interface{})["kind"]) == "wildcard" {
2379
+ hasWild = true
2380
+ }
2381
+ }
2382
+ if hasWild {
2383
+ continue
2384
+ }
2385
+ _, isJ := J[jstr(ground["predicate"])]
2386
+ if x.r.trigger == "UNDETERMINED" {
2387
+ if isJ {
2388
+ add(ground)
2389
+ } else {
2390
+ walk(ground, false)
2391
+ }
2392
+ } else if x.st == "refuted" && x.r.trigger == "NOT_SATISFIED" && isJ && jtruth(m, h, ground) == "NEITHER" {
2393
+ add(ground)
2394
+ }
2395
+ }
2396
+ }
2397
+ }
2398
+ walk(goal, rootDefeaters)
2399
+ out := make([]map[string]interface{}, len(order))
2400
+ for i, k := range order {
2401
+ out[i] = found[k]
2402
+ }
2403
+ return out
2404
+ }
2405
+
2406
+ func jlitLiteral(h jHooks, n *jLit) map[string]interface{} {
2407
+ args := make([]interface{}, len(n.row))
2408
+ for i, v := range n.row {
2409
+ args[i] = h.cell(n.pred, i, v)
2410
+ }
2411
+ pol := "negative"
2412
+ if n.positive {
2413
+ pol = "positive"
2414
+ }
2415
+ return map[string]interface{}{"kind": "literal", "polarity": pol, "predicate": n.pred, "args": args}
2416
+ }
2417
+
2418
+ func jlitStatus(m jModel, n *jLit) string {
2419
+ st := m.truth(n.pred, n.row)
2420
+ if n.positive {
2421
+ return st
2422
+ }
2423
+ return jMirror[st]
2424
+ }
2425
+
2426
+ // E-0100, носитель `valid_when` §127: судьи действительности осуществлённого
2427
+ // полномочия, чей эффект есть цель; E-0120 — не при эффекте, запертом иммунитетом.
2428
+ func powerJudgments(m jModel, h jHooks, goal map[string]interface{}) []map[string]interface{} {
2429
+ J := m.decls()
2430
+ if jpolarity(goal) != "positive" {
2431
+ return nil
2432
+ }
2433
+ args := jlist(goal, "args")
2434
+ row := make([]Val, len(args))
2435
+ for i, a := range args {
2436
+ row[i] = h.val(a.(map[string]interface{}))
2437
+ }
2438
+ if m.blockedImmunity(jstr(goal["predicate"]), row) {
2439
+ return nil
2440
+ }
2441
+ gkey := key(row)
2442
+ out := []map[string]interface{}{}
2443
+ for _, p := range m.positions() {
2444
+ if p.modality != "power" || p.status != "ACTIVE" || p.effect == nil {
2445
+ continue
2446
+ }
2447
+ if !p.effect.positive || p.effect.pred != jstr(goal["predicate"]) || key(p.effect.row) != gkey {
2448
+ continue
2449
+ }
2450
+ if p.exercise == nil {
2451
+ continue
2452
+ }
2453
+ if st := jlitStatus(m, p.exercise); st != "TRUE_ONLY" && st != "BOTH" {
2454
+ continue
2455
+ }
2456
+ if p.validWhen == nil {
2457
+ continue
2458
+ }
2459
+ if _, isJ := J[p.validWhen.pred]; !isJ {
2460
+ continue
2461
+ }
2462
+ if jlitStatus(m, p.validWhen) == "NEITHER" {
2463
+ out = append(out, jlitLiteral(h, p.validWhen))
2464
+ }
2465
+ }
2466
+ return orderRequests(out)
2467
+ }
2468
+
2469
+ // E-0100, носитель `condition` цели §123/§124: судьи статуса позиции обязанности.
2470
+ func positionJudgments(m jModel, h jHooks) []map[string]interface{} {
2471
+ J := m.decls()
2472
+ out := []map[string]interface{}{}
2473
+ for _, p := range m.positions() {
2474
+ if (p.status != "ACTIVE" && p.status != "UNDETERMINED") || p.cond == nil {
2475
+ continue
2476
+ }
2477
+ if _, isJ := J[p.cond.pred]; !isJ {
2478
+ continue
2479
+ }
2480
+ if jlitStatus(m, p.cond) == "NEITHER" {
2481
+ out = append(out, jlitLiteral(h, p.cond))
2482
+ }
2483
+ }
2484
+ return orderRequests(out)
2485
+ }
2486
+
2487
+ // JudgmentRequest схемы evaluation по упорядоченным блокерам (без proof-узла).
2488
+ func buildRequests(m jModel, blockers []map[string]interface{}, tail string) []map[string]interface{} {
2489
+ J := m.decls()
2490
+ out := make([]map[string]interface{}, len(blockers))
2491
+ for i, b := range blockers {
2492
+ pred := jstr(b["predicate"])
2493
+ d, ok := J[pred]
2494
+ authority := pred
2495
+ if ok {
2496
+ authority = d.Authority
2497
+ }
2498
+ args := jlist(b, "args")
2499
+ if args == nil {
2500
+ args = []interface{}{}
2501
+ }
2502
+ r := map[string]interface{}{"id": "urn:judgment:" + tail + ":" + strconv.Itoa(i), "predicate": pred, "authority": authority,
2503
+ "request": map[string]interface{}{"relation": pred, "polarity": jpolarity(b), "args": args}}
2504
+ if ok && d.RequestSchema != nil {
2505
+ r["requestSchema"] = d.RequestSchema
2506
+ }
2507
+ out[i] = r
2508
+ }
2509
+ return out
2510
+ }
2511
+
2512
+ // Порт ветвей `queries.query_truth`: цель — отношение суждения без опоры;
2513
+ // цель без опоры — тела правил и `valid_when`; стоящая цель — дефитеры головы.
2514
+ func judgmentBlockers(m jModel, h jHooks, goal map[string]interface{}, status string, conflict bool) []map[string]interface{} {
2515
+ J := m.decls()
2516
+ if len(J) == 0 {
2517
+ return nil
2518
+ }
2519
+ unresolved := status == "NEITHER" && !conflict
2520
+ if _, isJ := J[jstr(goal["predicate"])]; isJ && unresolved {
2521
+ return []map[string]interface{}{goal}
2522
+ }
2523
+ if unresolved {
2524
+ return orderRequests(append(collectJudgments(m, h, goal, false), powerJudgments(m, h, goal)...))
2525
+ }
2526
+ if status == "TRUE_ONLY" && !conflict {
2527
+ return orderRequests(collectJudgments(m, h, goal, true))
2528
+ }
2529
+ return nil
2530
+ }
2531
+
2532
+ var jKinds = map[string]string{
2533
+ "urn:law:std#Boolean": "boolean", "urn:law:std#Decimal": "decimal", "urn:law:std#Text": "text", "urn:law:std#Date": "date",
2534
+ "urn:law:std#Instant": "instant", "urn:law:std#Integer": "integer", "urn:law:std#Natural": "integer",
2535
+ "urn:law:std#Money": "money", "urn:law:std#Quantity": "quantity", "urn:law:std#Rational": "rational",
2536
+ }
2537
+
2538
+ // §47.3.1 (E-0089): ответ судьи с объектом `judgment` — орган и форма payload.
2539
+ // Возвращает код issue либо "" (ответ принят).
2540
+ func judgmentAnswer(m jModel, pred string, judgment map[string]interface{}) string {
2541
+ if judgment == nil {
2542
+ return ""
2543
+ }
2544
+ d, ok := m.decls()[pred]
2545
+ authority := pred
2546
+ if ok {
2547
+ authority = d.Authority
2548
+ }
2549
+ if jstr(judgment["authority"]) != authority {
2550
+ return "JUDGMENT_AUTHORITY_MISMATCH"
2551
+ }
2552
+ payload, has := judgment["payload"].(map[string]interface{})
2553
+ if judgment["payload"] != nil && has {
2554
+ if !ok || d.RequestSchema == nil {
2555
+ return "JUDGMENT_PAYLOAD_MISMATCH"
2556
+ }
2557
+ schema, _ := d.RequestSchema.(map[string]interface{})
2558
+ want, known := jKinds[jstr(schema["name"])]
2559
+ if known {
2560
+ got := "enum"
2561
+ switch jstr(payload["kind"]) {
2562
+ case "value":
2563
+ got = jKinds[jstr(jfield(payload, "type")["name"])]
2564
+ case "entity_ref":
2565
+ got = "entity_ref"
2566
+ }
2567
+ if got != want {
2568
+ return "JUDGMENT_PAYLOAD_MISMATCH"
2569
+ }
2570
+ }
2571
+ }
2572
+ return ""
2573
+ }
2574
+
2575
+ // Ключ кандидата страты для учёта выживших (DECISION-0274).
2576
+ func candKey(rule, urn, k string, positive bool) string {
2577
+ if positive {
2578
+ return rule + "|" + urn + "|" + k + "|+"
2579
+ }
2580
+ return rule + "|" + urn + "|" + k + "|-"
2581
+ }
2582
+
2583
+ func admitNumericInputs(kase, query map[string]interface{}, judgments bool) {
2584
+ stack := []interface{}{query}
2585
+ assertions, _ := kase["assertions"].([]interface{})
2586
+ for _, v := range assertions {
2587
+ a, _ := v.(map[string]interface{})
2588
+ stack = append(stack, a["literal"])
2589
+ }
2590
+ for len(stack) > 0 {
2591
+ n := stack[len(stack)-1]
2592
+ stack = stack[:len(stack)-1]
2593
+ switch x := n.(type) {
2594
+ case []interface{}:
2595
+ stack = append(stack, x...)
2596
+ case map[string]interface{}:
2597
+ t, _ := x["type"].(map[string]interface{})
2598
+ if x["kind"] == "value" && t["name"] == "urn:law:std#Number" {
2599
+ refuse("NUMBER_UNAVAILABLE: exact Number and slot narrowing (E-0316/E-0319/E-0334) are outside this slice")
2600
+ }
2601
+ if judgments && (x["kind"] == "aggregate" || x["kind"] == "comprehension") {
2602
+ refuse("AGGREGATE_JUDGMENT_UNAVAILABLE: open aggregate members require E-0357")
2603
+ }
2604
+ for _, v := range x {
2605
+ switch v.(type) {
2606
+ case []interface{}, map[string]interface{}:
2607
+ stack = append(stack, v)
2608
+ }
2609
+ }
2610
+ }
2611
+ }
2612
+ }