arxo-codegen 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arxo_codegen/__init__.py +5 -0
- arxo_codegen/__main__.py +7 -0
- arxo_codegen/active-outcomes.json +6837 -0
- arxo_codegen/check.py +158 -0
- arxo_codegen/check_active_canaries.py +262 -0
- arxo_codegen/check_api.py +76 -0
- arxo_codegen/check_conformance.py +414 -0
- arxo_codegen/check_matrix.py +131 -0
- arxo_codegen/check_packages.py +281 -0
- arxo_codegen/check_semantic_probes.py +293 -0
- arxo_codegen/check_xlsx.py +320 -0
- arxo_codegen/consumer_runner.py +316 -0
- arxo_codegen/core_support.py +203 -0
- arxo_codegen/coverage.py +341 -0
- arxo_codegen/deliver.py +180 -0
- arxo_codegen/examples/xlsx/pricing.lawir.json +409 -0
- arxo_codegen/examples/xlsx/pricing.xlsx.json +52 -0
- arxo_codegen/excel_plan.py +602 -0
- arxo_codegen/harness/verify/ci/gates/backends/check_backends.py +1248 -0
- arxo_codegen/harness/verify/ci/gates/backends/l1_fixtures.py +463 -0
- arxo_codegen/inert-nodes.json +62 -0
- arxo_codegen/inert.py +91 -0
- arxo_codegen/inputs.py +59 -0
- arxo_codegen/package.py +565 -0
- arxo_codegen/plan.py +4053 -0
- arxo_codegen/recertify_registry.py +100 -0
- arxo_codegen/runtime-cert.json +5259 -0
- arxo_codegen/runtime_cert.py +233 -0
- arxo_codegen/survey.py +272 -0
- arxo_codegen/target_runner.py +101 -0
- arxo_codegen/targets/bsl/cert.bsl +113 -0
- arxo_codegen/targets/bsl/emit.py +356 -0
- arxo_codegen/targets/bsl/json.bsl +243 -0
- arxo_codegen/targets/bsl/runner.bsl +210 -0
- arxo_codegen/targets/bsl/runtime.bsl +858 -0
- arxo_codegen/targets/cs/Cert.cs +90 -0
- arxo_codegen/targets/cs/Runner.cs +154 -0
- arxo_codegen/targets/cs/Runtime.cs +243 -0
- arxo_codegen/targets/cs/emit.py +223 -0
- arxo_codegen/targets/dart/cert.dart +99 -0
- arxo_codegen/targets/dart/emit.py +206 -0
- arxo_codegen/targets/dart/json.dart +166 -0
- arxo_codegen/targets/dart/runner.dart +154 -0
- arxo_codegen/targets/dart/runtime.dart +293 -0
- arxo_codegen/targets/erl/cert.erl +64 -0
- arxo_codegen/targets/erl/emit.py +216 -0
- arxo_codegen/targets/erl/rt.erl +238 -0
- arxo_codegen/targets/erl/runner.erl +130 -0
- arxo_codegen/targets/ex/cert.exs +62 -0
- arxo_codegen/targets/ex/emit.py +204 -0
- arxo_codegen/targets/ex/runner.exs +124 -0
- arxo_codegen/targets/ex/runtime.ex +227 -0
- arxo_codegen/targets/go/apiBase.go +483 -0
- arxo_codegen/targets/go/apicheck.go +97 -0
- arxo_codegen/targets/go/cert.go +218 -0
- arxo_codegen/targets/go/emit.py +1806 -0
- arxo_codegen/targets/go/evaluate.go +1826 -0
- arxo_codegen/targets/go/runner.go +38 -0
- arxo_codegen/targets/go/runtime.go +2612 -0
- arxo_codegen/targets/java/Cert.java +91 -0
- arxo_codegen/targets/java/Json.java +148 -0
- arxo_codegen/targets/java/Runner.java +151 -0
- arxo_codegen/targets/java/Runtime.java +243 -0
- arxo_codegen/targets/java/emit.py +198 -0
- arxo_codegen/targets/kt/Cert.kt +67 -0
- arxo_codegen/targets/kt/Json.kt +103 -0
- arxo_codegen/targets/kt/Runner.kt +126 -0
- arxo_codegen/targets/kt/Runtime.kt +196 -0
- arxo_codegen/targets/kt/emit.py +195 -0
- arxo_codegen/targets/lua/cert.lua +82 -0
- arxo_codegen/targets/lua/emit.py +250 -0
- arxo_codegen/targets/lua/json.lua +133 -0
- arxo_codegen/targets/lua/runner.lua +167 -0
- arxo_codegen/targets/lua/runtime.lua +541 -0
- arxo_codegen/targets/php/cert.php +84 -0
- arxo_codegen/targets/php/emit.py +229 -0
- arxo_codegen/targets/php/json.php +144 -0
- arxo_codegen/targets/php/runner.php +189 -0
- arxo_codegen/targets/php/runtime.php +660 -0
- arxo_codegen/targets/py/api_base.py +334 -0
- arxo_codegen/targets/py/apicheck.py +51 -0
- arxo_codegen/targets/py/cert.py +140 -0
- arxo_codegen/targets/py/emit.py +1140 -0
- arxo_codegen/targets/py/evaluate.py +980 -0
- arxo_codegen/targets/py/runner.py +38 -0
- arxo_codegen/targets/py/runtime.py +1634 -0
- arxo_codegen/targets/rb/cert.rb +73 -0
- arxo_codegen/targets/rb/emit.py +203 -0
- arxo_codegen/targets/rb/runner.rb +130 -0
- arxo_codegen/targets/rb/runtime.rb +255 -0
- arxo_codegen/targets/rs/Cargo.lock +142 -0
- arxo_codegen/targets/rs/Cargo.toml +16 -0
- arxo_codegen/targets/rs/cert.rs +106 -0
- arxo_codegen/targets/rs/emit.py +233 -0
- arxo_codegen/targets/rs/prefetch.rs +1 -0
- arxo_codegen/targets/rs/runner.rs +181 -0
- arxo_codegen/targets/rs/runtime.rs +448 -0
- arxo_codegen/targets/scala/Cert.scala +77 -0
- arxo_codegen/targets/scala/Json.scala +140 -0
- arxo_codegen/targets/scala/Runner.scala +121 -0
- arxo_codegen/targets/scala/Runtime.scala +269 -0
- arxo_codegen/targets/scala/emit.py +193 -0
- arxo_codegen/targets/swift/cert.swift +105 -0
- arxo_codegen/targets/swift/emit.py +231 -0
- arxo_codegen/targets/swift/json.swift +208 -0
- arxo_codegen/targets/swift/runner.swift +133 -0
- arxo_codegen/targets/swift/runtime.swift +528 -0
- arxo_codegen/targets/ts/apiBase.ts +250 -0
- arxo_codegen/targets/ts/apicheck.ts +37 -0
- arxo_codegen/targets/ts/cert.ts +96 -0
- arxo_codegen/targets/ts/emit.py +1214 -0
- arxo_codegen/targets/ts/evaluate.ts +894 -0
- arxo_codegen/targets/ts/package-lock.json +45 -0
- arxo_codegen/targets/ts/package.json +9 -0
- arxo_codegen/targets/ts/runner.ts +31 -0
- arxo_codegen/targets/ts/runtime.ts +1369 -0
- arxo_codegen/targets/xlsx/README.ru.md +176 -0
- arxo_codegen/targets/xlsx/emit.py +198 -0
- arxo_codegen/temporal.py +59 -0
- arxo_codegen/test_alias_types.py +171 -0
- arxo_codegen/test_api_contract.py +89 -0
- arxo_codegen/test_calendar_policy.py +48 -0
- arxo_codegen/test_check.py +49 -0
- arxo_codegen/test_check_active_canaries.py +96 -0
- arxo_codegen/test_check_conformance.py +132 -0
- arxo_codegen/test_check_semantic_probes.py +85 -0
- arxo_codegen/test_check_xlsx.py +52 -0
- arxo_codegen/test_consumer_runner.py +175 -0
- arxo_codegen/test_core_support.py +80 -0
- arxo_codegen/test_e0212_decision_time.py +63 -0
- arxo_codegen/test_head_aggregate.py +71 -0
- arxo_codegen/test_inert.py +143 -0
- arxo_codegen/test_instant_types.py +113 -0
- arxo_codegen/test_key_conflict_scope.py +92 -0
- arxo_codegen/test_key_conflict_targets.py +17 -0
- arxo_codegen/test_package_layout.py +163 -0
- arxo_codegen/test_plan_language_version.py +44 -0
- arxo_codegen/test_positions_refusal.py +43 -0
- arxo_codegen/test_recertify_registry.py +77 -0
- arxo_codegen/test_ruby_target.py +41 -0
- arxo_codegen/test_runtime_cert.py +83 -0
- arxo_codegen/test_secondary_key_conflict_scope.py +17 -0
- arxo_codegen/test_stage_r.py +108 -0
- arxo_codegen/test_standalone_inputs.py +71 -0
- arxo_codegen/test_support.py +150 -0
- arxo_codegen/test_survey_inputs.py +115 -0
- arxo_codegen-0.4.0.dist-info/LICENSE +202 -0
- arxo_codegen-0.4.0.dist-info/METADATA +1673 -0
- arxo_codegen-0.4.0.dist-info/RECORD +152 -0
- arxo_codegen-0.4.0.dist-info/WHEEL +5 -0
- arxo_codegen-0.4.0.dist-info/entry_points.txt +13 -0
- arxo_codegen-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,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
|
+
}
|