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,1826 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
// Единый вычислительный вход Go-цели (DECISION-0423 §2.2).
|
|
4
|
+
//
|
|
5
|
+
// Дело и вопрос приходят канонической JSON-записью (§168, §172) — той же, что у
|
|
6
|
+
// векторов и оракула, — и `evaluate` возвращает ответ исполняемого профиля той же
|
|
7
|
+
// JSON-строкой, что раньше печатал раннер. Раннер и API вызывают только его,
|
|
8
|
+
// поэтому разбор сырых литералов §208, сверка аргументов §189, разворот контекста,
|
|
9
|
+
// насыщение и лестница статусов §175 у них одни. Состояние — на вызов (§2.3):
|
|
10
|
+
// модель строит `newModel()`, контекст вычисления живёт в ней, сопутствующие
|
|
11
|
+
// множества — в `evaluation`; глобального состояния и мьютекса нет. Ответ — БЕЗ
|
|
12
|
+
// доказательства и хэшей: профиль core-executable.
|
|
13
|
+
|
|
14
|
+
import (
|
|
15
|
+
"bytes"
|
|
16
|
+
"encoding/json"
|
|
17
|
+
"fmt"
|
|
18
|
+
"math/big"
|
|
19
|
+
"sort"
|
|
20
|
+
"strings"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// Model — хуки экземпляра модели (DECISION-0423 §2.3): `newModel()` порождённого
|
|
24
|
+
// model.go строит таблицы и правила замыканиями над ними и заполняет эти поля.
|
|
25
|
+
type Model struct {
|
|
26
|
+
NODE_IDS map[string]bool
|
|
27
|
+
ctx *evalContext
|
|
28
|
+
TABLES map[string][][]Val
|
|
29
|
+
PARAMS map[string][]string
|
|
30
|
+
CONSTS map[string]constDecl
|
|
31
|
+
BASES map[string]string
|
|
32
|
+
FUNCTIONS map[string]funcDecl
|
|
33
|
+
A3_TYPES map[string]string
|
|
34
|
+
KEY_READERS map[string][]string
|
|
35
|
+
CARRIER_EDGES map[string][]string
|
|
36
|
+
saturate func()
|
|
37
|
+
register func(urn string, row []Val)
|
|
38
|
+
registerNegative func(urn string, row []Val)
|
|
39
|
+
packageFacts func()
|
|
40
|
+
keyConflicts func() int
|
|
41
|
+
keyConflictPredicates func() map[string]bool
|
|
42
|
+
truthOf func(urn string, row []Val) string
|
|
43
|
+
establishedRows func(urn string) [][]Val
|
|
44
|
+
ruleIssues func() []string
|
|
45
|
+
normIssues func() []string
|
|
46
|
+
normPositions func() ([]normPosition, bool)
|
|
47
|
+
blockedEffect func(pred string, row []Val) bool
|
|
48
|
+
calendarEnv func() (string, map[string]interface{}, []Snapshot, bool)
|
|
49
|
+
backchainEnv func() ([]interface{}, map[string]interface{}, map[string]bool, bool)
|
|
50
|
+
judgmentModel func() jModel
|
|
51
|
+
setStatusTime func(t string)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// evaluation — одно вычисление: экземпляр модели, дело и вопрос, сопутствующие множества.
|
|
55
|
+
type evaluation struct {
|
|
56
|
+
m *Model
|
|
57
|
+
caseRaw, queryRaw []byte
|
|
58
|
+
issues []string
|
|
59
|
+
typeErrorPredicates map[string]bool
|
|
60
|
+
// Запись строки, поданная делом как сущность и как Text, — в рантайме одно
|
|
61
|
+
// значение, у оракула — разные атомы. Для печати результата вид берётся по
|
|
62
|
+
// тому, как запись подана; одна запись в обеих ролях — названный отказ.
|
|
63
|
+
entities, texts map[string]bool
|
|
64
|
+
lastTermCodes map[string]bool
|
|
65
|
+
hooks jHooks
|
|
66
|
+
api bool // разрешён внутренний вид `rows` (DECISION-0423 §2.1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// RefusedError — названный отказ среза (DECISION-0423 §2.5): значение `error`,
|
|
70
|
+
// распознаваемое `errors.As`; паники на границе входа нет.
|
|
71
|
+
type RefusedError struct{ Message string }
|
|
72
|
+
|
|
73
|
+
func (r *RefusedError) Error() string { return r.Message }
|
|
74
|
+
|
|
75
|
+
// evaluate — ответ на вопрос по делу JSON-строкой; названный отказ — *RefusedError.
|
|
76
|
+
func evaluate(caseJSON, queryJSON []byte) (string, error) {
|
|
77
|
+
return evaluateWith(caseJSON, queryJSON, false)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// evaluateRows — то же с внутренним видом API `rows`, которого протокол раннера не знает.
|
|
81
|
+
func evaluateRows(caseJSON, queryJSON []byte) (string, error) {
|
|
82
|
+
return evaluateWith(caseJSON, queryJSON, true)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func evaluateWith(caseJSON, queryJSON []byte, api bool) (answer string, err error) {
|
|
86
|
+
var requested struct {
|
|
87
|
+
Kind string `json:"kind"`
|
|
88
|
+
}
|
|
89
|
+
if json.Unmarshal(queryJSON, &requested) == nil && requested.Kind == "positions" {
|
|
90
|
+
return "", &RefusedError{"POSITIONS_UNAVAILABLE: positions requires current norm ranks (W6)"}
|
|
91
|
+
}
|
|
92
|
+
defer func() {
|
|
93
|
+
if r := recover(); r != nil {
|
|
94
|
+
if msg, ok := r.(Refused); ok {
|
|
95
|
+
answer, err = "", &RefusedError{string(msg)}
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
if te, ok := r.(termError); ok {
|
|
99
|
+
// Ошибка значения вне вопроса `term` и вне правил модели (гард
|
|
100
|
+
// comprehension вопроса) — названный отказ, а не крах.
|
|
101
|
+
answer, err = "", &RefusedError{"ошибка значения вне среза раннера: " + te.code}
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
panic(r)
|
|
105
|
+
}
|
|
106
|
+
}()
|
|
107
|
+
validateTemporalCase(caseJSON)
|
|
108
|
+
ev := &evaluation{m: newModel(), caseRaw: caseJSON, queryRaw: queryJSON, issues: []string{},
|
|
109
|
+
typeErrorPredicates: map[string]bool{}, entities: map[string]bool{}, texts: map[string]bool{},
|
|
110
|
+
lastTermCodes: map[string]bool{}, api: api}
|
|
111
|
+
var admissionCase, admissionQuery map[string]interface{}
|
|
112
|
+
_ = json.Unmarshal(caseJSON, &admissionCase)
|
|
113
|
+
_ = json.Unmarshal(queryJSON, &admissionQuery)
|
|
114
|
+
assertions, _ := admissionCase["assertions"].([]interface{})
|
|
115
|
+
for _, value := range assertions {
|
|
116
|
+
a, _ := value.(map[string]interface{})
|
|
117
|
+
if ev.m.NODE_IDS[str(a["id"])] {
|
|
118
|
+
refuse("LDC-E1382: case assertion id collides with a program node")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
admitNumericInputs(admissionCase, admissionQuery, len(ev.m.judgmentModel().decls()) > 0)
|
|
122
|
+
// Обход запроса судье §47.3 (DECISION-0274): переводы термов раннера для рантайма;
|
|
123
|
+
// множества сущностей и текстов дела обходом не двигаются.
|
|
124
|
+
ev.hooks = jHooks{
|
|
125
|
+
val: func(a map[string]interface{}) Val { return ev.typedArg(a).v },
|
|
126
|
+
cell: func(pred string, col int, v Val) map[string]interface{} {
|
|
127
|
+
var out map[string]interface{}
|
|
128
|
+
d := json.NewDecoder(strings.NewReader(literalOf(ev.slotTyped(pred, col, v))))
|
|
129
|
+
d.UseNumber()
|
|
130
|
+
if err := d.Decode(&out); err != nil {
|
|
131
|
+
panic(err)
|
|
132
|
+
}
|
|
133
|
+
return out
|
|
134
|
+
},
|
|
135
|
+
}
|
|
136
|
+
return ev.run(), nil
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Обобщённый разбор JSON: числа — json.Number, чтобы длинное целое не теряло знаков.
|
|
140
|
+
func decodeGeneric(b []byte) map[string]interface{} {
|
|
141
|
+
d := json.NewDecoder(bytes.NewReader(b))
|
|
142
|
+
d.UseNumber()
|
|
143
|
+
var v map[string]interface{}
|
|
144
|
+
if err := d.Decode(&v); err != nil {
|
|
145
|
+
panic(err)
|
|
146
|
+
}
|
|
147
|
+
return v
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
func mustUnmarshal(b []byte, v interface{}) {
|
|
151
|
+
if err := json.Unmarshal(b, v); err != nil {
|
|
152
|
+
panic(err)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type argJSON struct {
|
|
157
|
+
Kind string `json:"kind"`
|
|
158
|
+
ID string `json:"id"`
|
|
159
|
+
Value json.RawMessage `json:"value"`
|
|
160
|
+
Currency string `json:"currency"`
|
|
161
|
+
Unit string `json:"unit"`
|
|
162
|
+
Type struct {
|
|
163
|
+
Name string `json:"name"`
|
|
164
|
+
} `json:"type"`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
type litJSON struct {
|
|
168
|
+
Predicate string `json:"predicate"`
|
|
169
|
+
Polarity string `json:"polarity"`
|
|
170
|
+
Args []argJSON `json:"args"`
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
func literal(raw json.RawMessage) string {
|
|
174
|
+
if len(raw) > 0 && raw[0] == '"' {
|
|
175
|
+
var s string
|
|
176
|
+
if err := json.Unmarshal(raw, &s); err != nil {
|
|
177
|
+
panic(err)
|
|
178
|
+
}
|
|
179
|
+
return s
|
|
180
|
+
}
|
|
181
|
+
return string(raw)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// §208/§48 (errata E-0197, E-0202, E-0203): вид JSON-носителя сырого поля
|
|
185
|
+
// литерала и его текст — "s" (JSON-строка), "i" (целое JSON-число канона),
|
|
186
|
+
// "x" (любая другая форма, включая отсутствие поля: схема LiteralTerm не
|
|
187
|
+
// требует `value`). Отсюда и только отсюда читают `lit*` рантайма.
|
|
188
|
+
func rawOfMessage(raw json.RawMessage) (string, string) {
|
|
189
|
+
s := strings.TrimSpace(string(raw))
|
|
190
|
+
if s == "" || s == "null" || s == "true" || s == "false" {
|
|
191
|
+
return "x", ""
|
|
192
|
+
}
|
|
193
|
+
if s[0] == '"' {
|
|
194
|
+
var v string
|
|
195
|
+
if err := json.Unmarshal(raw, &v); err != nil {
|
|
196
|
+
return "x", ""
|
|
197
|
+
}
|
|
198
|
+
return "s", v
|
|
199
|
+
}
|
|
200
|
+
if s[0] == '-' || (s[0] >= '0' && s[0] <= '9') {
|
|
201
|
+
return canonNumberText(s)
|
|
202
|
+
}
|
|
203
|
+
return "x", ""
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// То же по значению обобщённого разбора: числа приходят `json.Number`
|
|
207
|
+
// (`UseNumber`), то есть текстом исходного литерала, а не float64.
|
|
208
|
+
func rawOfValue(v interface{}) (string, string) {
|
|
209
|
+
switch x := v.(type) {
|
|
210
|
+
case string:
|
|
211
|
+
return "s", x
|
|
212
|
+
case json.Number:
|
|
213
|
+
return canonNumberText(x.String())
|
|
214
|
+
}
|
|
215
|
+
return "x", ""
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
func (ev *evaluation) toVal(a argJSON) Val {
|
|
219
|
+
if a.Kind == "entity_ref" {
|
|
220
|
+
ev.entities[a.ID] = true
|
|
221
|
+
return a.ID
|
|
222
|
+
}
|
|
223
|
+
if a.Kind == "const_ref" {
|
|
224
|
+
// §54: разыменование предшествует исполнению; ниже ссылок нет.
|
|
225
|
+
c, ok := ev.m.CONSTS[a.ID]
|
|
226
|
+
if !ok {
|
|
227
|
+
// Член перечисления §38 либо имя без объявления: оракул оставляет
|
|
228
|
+
// такой const_ref в ключе атома как есть — значение есть идентичность.
|
|
229
|
+
return sym(a.ID)
|
|
230
|
+
}
|
|
231
|
+
return c.Value
|
|
232
|
+
}
|
|
233
|
+
if a.Kind != "value" {
|
|
234
|
+
refuse("аргумент вне среза раннера: " + a.Kind)
|
|
235
|
+
}
|
|
236
|
+
// §208/§48 (errata E-0197, E-0202, E-0203): сырое поле литерала читается по
|
|
237
|
+
// грамматике §208, а не байтами `json.RawMessage`: до errata литерал без
|
|
238
|
+
// `value` выходил у go значением "" (а у ts — "undefined"), то есть ответом,
|
|
239
|
+
// а не диагностикой.
|
|
240
|
+
k, text := rawOfMessage(a.Value)
|
|
241
|
+
switch a.Type.Name {
|
|
242
|
+
case "urn:law:std#Money":
|
|
243
|
+
ck, ctext := "s", a.Currency
|
|
244
|
+
return litMoney(k, text, ck, ctext)
|
|
245
|
+
case "urn:law:std#Decimal":
|
|
246
|
+
return litDec(k, text)
|
|
247
|
+
case "urn:law:std#Integer":
|
|
248
|
+
return litInt(k, text)
|
|
249
|
+
case "urn:law:std#Text":
|
|
250
|
+
s := litText(k, text)
|
|
251
|
+
ev.texts[s] = true
|
|
252
|
+
return s
|
|
253
|
+
case "urn:law:std#Date":
|
|
254
|
+
return date(litText(k, text))
|
|
255
|
+
case "urn:law:std#Instant":
|
|
256
|
+
return instant(litText(k, text))
|
|
257
|
+
case "urn:law:std#Rational":
|
|
258
|
+
// §168.1 (DECISION-0252 п. 8): только каноническая запись, иная — отказ.
|
|
259
|
+
return rat(litText(k, text))
|
|
260
|
+
case "urn:law:std#Quantity":
|
|
261
|
+
// §49 v1 (DECISION-0256 п. 7): значение и тег единицы как записаны.
|
|
262
|
+
if a.Unit != "" {
|
|
263
|
+
return litQty(k, text, "s", a.Unit)
|
|
264
|
+
}
|
|
265
|
+
case "urn:law:std#Boolean":
|
|
266
|
+
// DECISION-0266: только JSON-булево; иная запись у оракула — TYPE_ERROR при чтении значения.
|
|
267
|
+
var b bool
|
|
268
|
+
if err := json.Unmarshal(a.Value, &b); err != nil {
|
|
269
|
+
refuse("значение Boolean не true/false вне среза раннера (§48)")
|
|
270
|
+
}
|
|
271
|
+
return b
|
|
272
|
+
case "urn:law:std#Natural":
|
|
273
|
+
// DECISION-0266: неотрицательное целое; иное у оракула — TYPE_ERROR при чтении значения.
|
|
274
|
+
if k != "i" || strings.HasPrefix(text, "-") {
|
|
275
|
+
refuse("значение Natural не неотрицательное целое вне среза раннера (§48)")
|
|
276
|
+
}
|
|
277
|
+
return litInt(k, text)
|
|
278
|
+
}
|
|
279
|
+
refuse("аргумент вне среза раннера: " + a.Type.Name)
|
|
280
|
+
return nil
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Тип аргумента, если известен: у value — из узла, у const_ref — из
|
|
284
|
+
// объявления константы; entity_ref типа не несёт (как у оракула).
|
|
285
|
+
func (ev *evaluation) argType(a argJSON) (string, bool) {
|
|
286
|
+
if a.Kind == "value" {
|
|
287
|
+
return a.Type.Name, true
|
|
288
|
+
}
|
|
289
|
+
if a.Kind == "const_ref" {
|
|
290
|
+
// Оракул разыменовывает константы §54 до сверки: сущность и член
|
|
291
|
+
// перечисления типа не несут, тип — только у константы std-типа.
|
|
292
|
+
c, ok := ev.m.CONSTS[a.ID]
|
|
293
|
+
return c.Type, ok && strings.HasPrefix(c.Type, "urn:law:std#")
|
|
294
|
+
}
|
|
295
|
+
return "", false
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Совпадение типа с наследованием сущностей вверх от фактического.
|
|
299
|
+
func (ev *evaluation) conforms(actual, expected string) bool {
|
|
300
|
+
seen := map[string]bool{}
|
|
301
|
+
cur := actual
|
|
302
|
+
for cur != "" && !seen[cur] {
|
|
303
|
+
if cur == expected {
|
|
304
|
+
return true
|
|
305
|
+
}
|
|
306
|
+
seen[cur] = true
|
|
307
|
+
cur = ev.m.BASES[cur]
|
|
308
|
+
}
|
|
309
|
+
return false
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Арность и типы аргументов утверждения против сигнатуры §189 (E-0046) —
|
|
313
|
+
// зеркало check_argument_types оракула: несогласованное утверждение не
|
|
314
|
+
// принимается, причина названа кодом TYPE_ERROR; entity_ref типа не несёт;
|
|
315
|
+
// предикат без сигнатуры принимается (его никто не читает). Проверка идёт
|
|
316
|
+
// ДО преобразования значений.
|
|
317
|
+
func (ev *evaluation) wellTyped(lit litJSON) bool {
|
|
318
|
+
params, ok := ev.m.PARAMS[lit.Predicate]
|
|
319
|
+
if !ok {
|
|
320
|
+
return true
|
|
321
|
+
}
|
|
322
|
+
if len(lit.Args) != len(params) {
|
|
323
|
+
ev.issues = append(ev.issues, "TYPE_ERROR")
|
|
324
|
+
ev.typeErrorPredicates[lit.Predicate] = true
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
for i, a := range lit.Args {
|
|
328
|
+
if actual, known := ev.argType(a); known && !ev.conforms(actual, params[i]) {
|
|
329
|
+
ev.issues = append(ev.issues, "TYPE_ERROR")
|
|
330
|
+
ev.typeErrorPredicates[lit.Predicate] = true
|
|
331
|
+
return false
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return true
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
func (ev *evaluation) queryTyped(lit litJSON) bool {
|
|
338
|
+
params, ok := ev.m.PARAMS[lit.Predicate]
|
|
339
|
+
if !ok || len(lit.Args) != len(params) {
|
|
340
|
+
return false
|
|
341
|
+
}
|
|
342
|
+
for i, a := range lit.Args {
|
|
343
|
+
if actual, known := ev.argType(a); known && !ev.conforms(actual, params[i]) {
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return true
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// --- вопросы term §57–§59 и collect §52.1 -------------------------------------
|
|
351
|
+
// Срез раннера: comprehension по установленным атомам таблиц с гардами,
|
|
352
|
+
// элемент — ячейка или литерал; агрегаты count/sum/min/max; арифметика §58
|
|
353
|
+
// и функции рантайма над результатом. Вид результата выводится статически
|
|
354
|
+
// по тем же таблицам §58, что у плана: у оракула вид — часть значения
|
|
355
|
+
// (Integer 5 и Decimal 5 — разные байты), у рантайма — нет.
|
|
356
|
+
const (
|
|
357
|
+
tINT = "urn:law:std#Integer"
|
|
358
|
+
tDEC = "urn:law:std#Decimal"
|
|
359
|
+
tMON = "urn:law:std#Money"
|
|
360
|
+
tTEXT = "urn:law:std#Text"
|
|
361
|
+
tDATE = "urn:law:std#Date"
|
|
362
|
+
tINSTANT = "urn:law:std#Instant"
|
|
363
|
+
tRAT = "urn:law:std#Rational"
|
|
364
|
+
tQTY = "urn:law:std#Quantity"
|
|
365
|
+
tBOOL = "urn:law:std#Boolean"
|
|
366
|
+
tNAT = "urn:law:std#Natural"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
// DECISION-0266: Natural считает как Integer (`values._TYPE_KINDS`), тип-строка своя.
|
|
370
|
+
func normKind(t string) string {
|
|
371
|
+
if t == tNAT {
|
|
372
|
+
return tINT
|
|
373
|
+
}
|
|
374
|
+
return t
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// §59 (errata E-0195): вид элемента ПУСТОЙ коллекции несёт ОБЪЯВЛЕННЫЙ тип
|
|
378
|
+
// проекции comprehension §52.1 (`variable.type.name`) — единственный носитель
|
|
379
|
+
// вида, не зависящий от числа решений generator-а (порт
|
|
380
|
+
// `values.aggregate_element_type`). Таблица — порт `values._TYPE_KINDS` плюс
|
|
381
|
+
// `_OPAQUE_KINDS`: Natural и Integer у оракула один вид `integer`. Имя вне
|
|
382
|
+
// таблицы (сущность, перечисление, чужой символ) вида не предъявляет, и сверка
|
|
383
|
+
// МОЛЧИТ: Unknown есть «утверждать нечего», а не «совместимо со всем».
|
|
384
|
+
var emptyElementKinds = map[string]string{
|
|
385
|
+
tINT: tINT, tNAT: tINT, tDEC: tDEC, tMON: tMON, tTEXT: tTEXT, tDATE: tDATE,
|
|
386
|
+
tBOOL: tBOOL, tRAT: tRAT, tQTY: tQTY,
|
|
387
|
+
"urn:law:std#Instant": "urn:law:std#Instant",
|
|
388
|
+
"urn:law:std#Magnitude": "urn:law:std#Magnitude",
|
|
389
|
+
"urn:law:std#Bounds": "urn:law:std#Bounds",
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Имя функции рантайма по имени std-функции вопроса (DECISION-0267).
|
|
393
|
+
var rtName = map[string]string{"month_start_of": "month_start", "month_end_of": "month_end"}
|
|
394
|
+
|
|
395
|
+
// Сравнения §57 в вычислимом терме вопроса: исход — Boolean.
|
|
396
|
+
func isCmpOp(op string) bool {
|
|
397
|
+
switch op {
|
|
398
|
+
case "lt", "le", "gt", "ge", "eq", "ne":
|
|
399
|
+
return true
|
|
400
|
+
}
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// DECISION-0252 п. 3: вид операнда для `arith` рантайма. У вопроса вид каждого
|
|
405
|
+
// значения известен точно — из литерала, слота или исхода операции.
|
|
406
|
+
var kindCode = map[string]string{tINT: "i", tDEC: "d", tMON: "m", tRAT: "r", tQTY: "q", tNAT: "i"}
|
|
407
|
+
|
|
408
|
+
type typed struct {
|
|
409
|
+
v Val
|
|
410
|
+
t string // URN типа std либо "entity" | "enum"
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
type env map[string]typed
|
|
414
|
+
|
|
415
|
+
func isNumT(t string) bool {
|
|
416
|
+
return t == tINT || t == tDEC || t == tMON || t == tRAT || t == tQTY || t == tNAT
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
func arithKind(op, l, r string) string {
|
|
420
|
+
l, r = normKind(l), normKind(r)
|
|
421
|
+
if !isNumT(l) || !isNumT(r) {
|
|
422
|
+
refuse("арифметика вне среза (§58): не числовой операнд " + op)
|
|
423
|
+
}
|
|
424
|
+
switch op {
|
|
425
|
+
case "add", "sub":
|
|
426
|
+
if l != r {
|
|
427
|
+
refuse("арифметика вне среза (§58): виды операндов не совпадают у " + op)
|
|
428
|
+
}
|
|
429
|
+
return l
|
|
430
|
+
case "mul":
|
|
431
|
+
if l == tMON && r == tMON {
|
|
432
|
+
refuse("арифметика вне среза (§58): произведение двух размерных величин не определено")
|
|
433
|
+
}
|
|
434
|
+
if l == tINT {
|
|
435
|
+
return r
|
|
436
|
+
}
|
|
437
|
+
if r == tINT {
|
|
438
|
+
return l
|
|
439
|
+
}
|
|
440
|
+
if l == tMON || r == tMON {
|
|
441
|
+
return tMON
|
|
442
|
+
}
|
|
443
|
+
return tDEC
|
|
444
|
+
case "div":
|
|
445
|
+
if (l == tDEC && r == tDEC) || (l == tMON && r == tMON) {
|
|
446
|
+
return tDEC
|
|
447
|
+
}
|
|
448
|
+
if l == tMON && r == tDEC {
|
|
449
|
+
return tMON
|
|
450
|
+
}
|
|
451
|
+
if l == tINT && r == tINT {
|
|
452
|
+
refuse("арифметика вне среза (§58): частное Integer/Integer — Rational")
|
|
453
|
+
}
|
|
454
|
+
refuse("арифметика вне среза (§58): пара вне таблицы делимых")
|
|
455
|
+
case "mod":
|
|
456
|
+
if l != tINT || r != tINT {
|
|
457
|
+
refuse("арифметика вне среза (§58): остаток только Integer mod Integer")
|
|
458
|
+
}
|
|
459
|
+
return tINT
|
|
460
|
+
}
|
|
461
|
+
refuse("операция вне среза раннера: " + op)
|
|
462
|
+
return ""
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
func callKind(fn string, ks []string) string {
|
|
466
|
+
switch fn {
|
|
467
|
+
case "min", "max":
|
|
468
|
+
if len(ks) != 2 || !isNumT(ks[0]) || ks[0] != ks[1] {
|
|
469
|
+
refuse("арифметика вне среза (§58): виды операндов не совпадают у " + fn)
|
|
470
|
+
}
|
|
471
|
+
return ks[0]
|
|
472
|
+
case "abs":
|
|
473
|
+
if len(ks) != 1 || !isNumT(ks[0]) {
|
|
474
|
+
refuse("арифметика вне среза (§58): abs над нечисловым видом")
|
|
475
|
+
}
|
|
476
|
+
return ks[0]
|
|
477
|
+
case "pow":
|
|
478
|
+
if len(ks) != 2 || ks[0] != tINT || ks[1] != tINT {
|
|
479
|
+
refuse("арифметика вне среза (§58): pow только Integer pow Integer")
|
|
480
|
+
}
|
|
481
|
+
return tINT
|
|
482
|
+
case "round":
|
|
483
|
+
if len(ks) != 3 {
|
|
484
|
+
refuse("round вне среза раннера: ожидаются значение, разряды и режим (§50)")
|
|
485
|
+
}
|
|
486
|
+
if ks[0] == tRAT {
|
|
487
|
+
refuse("round над Rational вне среза генератора (§50: round не берёт Rational)")
|
|
488
|
+
}
|
|
489
|
+
if !isNumT(ks[0]) {
|
|
490
|
+
refuse("round вне среза генератора: вид операнда не числовой (§50)")
|
|
491
|
+
}
|
|
492
|
+
policyKinds("round", ks[1:])
|
|
493
|
+
return ks[0]
|
|
494
|
+
case "div_round":
|
|
495
|
+
// §50/§58 (E-0036, E-0170; DECISION-0259 п. 3): четыре пары видов.
|
|
496
|
+
if len(ks) != 4 {
|
|
497
|
+
refuse("div_round вне среза генератора: ожидаются делимое, делитель, точность и режим (§50)")
|
|
498
|
+
}
|
|
499
|
+
if ks[0] == tRAT || ks[1] == tRAT {
|
|
500
|
+
refuse("div_round над Rational вне среза генератора (§50/§58, E-0036)")
|
|
501
|
+
}
|
|
502
|
+
l, r := ks[0], ks[1]
|
|
503
|
+
if !((l == tMON && r == tDEC) || (l == tQTY && r == tDEC) || (l == tDEC && r == tDEC) || (l == tMON && r == tMON)) {
|
|
504
|
+
refuse("div_round вне среза генератора: пара видов вне E-0036/E-0170 (§50/§58)")
|
|
505
|
+
}
|
|
506
|
+
policyKinds("div_round", ks[2:])
|
|
507
|
+
if r == tDEC && (l == tMON || l == tQTY) {
|
|
508
|
+
return l
|
|
509
|
+
}
|
|
510
|
+
return tDEC
|
|
511
|
+
case "month_start_of", "month_end_of":
|
|
512
|
+
// §84.2 (E-0084, DECISION-0267): арность и вид — TYPE_ERROR оракула (`month_bound_value`).
|
|
513
|
+
if len(ks) != 1 || ks[0] != tDATE {
|
|
514
|
+
panic(termError{"TYPE_ERROR"})
|
|
515
|
+
}
|
|
516
|
+
return tDATE
|
|
517
|
+
case "hours_between", "minutes_between":
|
|
518
|
+
// §84.1 (E-0028/E-0058; W2 часть 2): оба аргумента — Instant, иначе TYPE_ERROR оракула.
|
|
519
|
+
if len(ks) != 2 || ks[0] != tINSTANT || ks[1] != tINSTANT {
|
|
520
|
+
panic(termError{"TYPE_ERROR"})
|
|
521
|
+
}
|
|
522
|
+
return tINT
|
|
523
|
+
case "add_duration":
|
|
524
|
+
refuse("add_duration в вопросе вне среза раннера (§84.1)")
|
|
525
|
+
}
|
|
526
|
+
refuse("функция вне среза раннера: " + fn)
|
|
527
|
+
return ""
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// §50: точность — Integer либо Decimal, режим — Text (DECISION-0259 п. 3).
|
|
531
|
+
func policyKinds(fn string, ks []string) {
|
|
532
|
+
if ks[0] != tINT && ks[0] != tDEC {
|
|
533
|
+
refuse(fn + " вне среза генератора: точность не Integer и не Decimal (§50)")
|
|
534
|
+
}
|
|
535
|
+
if ks[1] != tTEXT {
|
|
536
|
+
refuse(fn + " вне среза генератора: режим не Text (§50)")
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
func str(v interface{}) string {
|
|
541
|
+
switch x := v.(type) {
|
|
542
|
+
case nil:
|
|
543
|
+
return ""
|
|
544
|
+
case string:
|
|
545
|
+
return x
|
|
546
|
+
case json.Number:
|
|
547
|
+
return x.String()
|
|
548
|
+
}
|
|
549
|
+
return fmt.Sprint(v)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
func field(m map[string]interface{}, k string) map[string]interface{} {
|
|
553
|
+
if x, ok := m[k].(map[string]interface{}); ok {
|
|
554
|
+
return x
|
|
555
|
+
}
|
|
556
|
+
return map[string]interface{}{}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
func list(m map[string]interface{}, k string) []interface{} {
|
|
560
|
+
if x, ok := m[k].([]interface{}); ok {
|
|
561
|
+
return x
|
|
562
|
+
}
|
|
563
|
+
return nil
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Литерал-терм §53 как typed-значение; тип сущности у entity_ref — "entity".
|
|
567
|
+
func (ev *evaluation) typedArg(a map[string]interface{}) typed {
|
|
568
|
+
switch str(a["kind"]) {
|
|
569
|
+
case "entity_ref":
|
|
570
|
+
return typed{str(a["id"]), "entity"}
|
|
571
|
+
case "const_ref":
|
|
572
|
+
// DECISION-0273: параметр A3 в вопросе term — за срезом раннера (значение на legalTime дела).
|
|
573
|
+
if _, isA3 := ev.m.A3_TYPES[str(a["id"])]; isA3 {
|
|
574
|
+
refuse("параметр A3 в вопросе вне среза раннера: " + str(a["id"]))
|
|
575
|
+
}
|
|
576
|
+
c, ok := ev.m.CONSTS[str(a["id"])]
|
|
577
|
+
if !ok {
|
|
578
|
+
return typed{sym(str(a["id"])), "enum"}
|
|
579
|
+
}
|
|
580
|
+
return typed{c.Value, c.Type}
|
|
581
|
+
case "value":
|
|
582
|
+
t := str(field(a, "type")["name"])
|
|
583
|
+
// §208/§48 (errata E-0197, E-0202, E-0203): та же воронка, что у факта.
|
|
584
|
+
k, text := rawOfValue(a["value"])
|
|
585
|
+
switch t {
|
|
586
|
+
case tMON:
|
|
587
|
+
ck, ctext := rawOfValue(a["currency"])
|
|
588
|
+
return typed{litMoney(k, text, ck, ctext), t}
|
|
589
|
+
case tDEC:
|
|
590
|
+
return typed{litDec(k, text), t}
|
|
591
|
+
case tINT:
|
|
592
|
+
return typed{litInt(k, text), t}
|
|
593
|
+
case tTEXT:
|
|
594
|
+
return typed{litText(k, text), t}
|
|
595
|
+
case tDATE:
|
|
596
|
+
return typed{date(litText(k, text)), t}
|
|
597
|
+
case tINSTANT:
|
|
598
|
+
return typed{instant(litText(k, text)), t}
|
|
599
|
+
case tRAT:
|
|
600
|
+
return typed{rat(litText(k, text)), t}
|
|
601
|
+
case tQTY:
|
|
602
|
+
// §49 v1 (DECISION-0256 п. 7): без тега единицы — отказ, как у факта.
|
|
603
|
+
if uk, utext := rawOfValue(a["unit"]); utext != "" {
|
|
604
|
+
return typed{litQty(k, text, uk, utext), t}
|
|
605
|
+
}
|
|
606
|
+
case tBOOL:
|
|
607
|
+
b, ok := a["value"].(bool)
|
|
608
|
+
if !ok {
|
|
609
|
+
refuse("значение Boolean не true/false вне среза раннера (§48)")
|
|
610
|
+
}
|
|
611
|
+
return typed{b, t}
|
|
612
|
+
case tNAT:
|
|
613
|
+
if k != "i" || strings.HasPrefix(text, "-") {
|
|
614
|
+
refuse("значение Natural не неотрицательное целое вне среза раннера (§48)")
|
|
615
|
+
}
|
|
616
|
+
return typed{litInt(k, text), t}
|
|
617
|
+
}
|
|
618
|
+
refuse("аргумент вне среза раннера: " + t)
|
|
619
|
+
}
|
|
620
|
+
refuse("узел терма вне среза раннера: " + str(a["kind"]))
|
|
621
|
+
return typed{}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
func (ev *evaluation) slotTyped(pred string, col int, v Val) typed {
|
|
625
|
+
t := ev.m.PARAMS[pred][col]
|
|
626
|
+
if s, ok := v.(string); ok {
|
|
627
|
+
ent, txt := ev.entities[s], ev.texts[s]
|
|
628
|
+
if ent && txt {
|
|
629
|
+
refuse("одна запись подана делом и сущностью, и Text: вид результата не определён в срезе")
|
|
630
|
+
}
|
|
631
|
+
if ent {
|
|
632
|
+
return typed{v, "entity"}
|
|
633
|
+
}
|
|
634
|
+
if txt {
|
|
635
|
+
return typed{v, tTEXT}
|
|
636
|
+
}
|
|
637
|
+
if t == tTEXT {
|
|
638
|
+
return typed{v, tTEXT}
|
|
639
|
+
}
|
|
640
|
+
return typed{v, "entity"}
|
|
641
|
+
}
|
|
642
|
+
if _, ok := v.(bool); ok {
|
|
643
|
+
return typed{v, tBOOL}
|
|
644
|
+
}
|
|
645
|
+
switch t {
|
|
646
|
+
case tMON, tDEC, tINT, tDATE, tRAT, tQTY, tNAT, tINSTANT:
|
|
647
|
+
return typed{v, t}
|
|
648
|
+
}
|
|
649
|
+
if _, ok := v.(Sym); ok {
|
|
650
|
+
return typed{v, "enum"}
|
|
651
|
+
}
|
|
652
|
+
return typed{v, "entity"}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
func flatten(body map[string]interface{}, atoms *[]map[string]interface{}, guards *[]map[string]interface{}) {
|
|
656
|
+
switch str(body["kind"]) {
|
|
657
|
+
case "status":
|
|
658
|
+
if str(body["status"]) != "established" {
|
|
659
|
+
refuse("статусный тест вне L0-среза: " + str(body["status"]))
|
|
660
|
+
}
|
|
661
|
+
*atoms = append(*atoms, field(body, "formula"))
|
|
662
|
+
case "literal":
|
|
663
|
+
*atoms = append(*atoms, body)
|
|
664
|
+
case "comparison":
|
|
665
|
+
*guards = append(*guards, body)
|
|
666
|
+
case "and", "conjunction":
|
|
667
|
+
subs := list(body, "items")
|
|
668
|
+
if subs == nil {
|
|
669
|
+
subs = list(body, "args")
|
|
670
|
+
}
|
|
671
|
+
if subs == nil {
|
|
672
|
+
subs = list(body, "operands")
|
|
673
|
+
}
|
|
674
|
+
for _, s := range subs {
|
|
675
|
+
if m, ok := s.(map[string]interface{}); ok {
|
|
676
|
+
flatten(m, atoms, guards)
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
default:
|
|
680
|
+
refuse("форма тела вне среза генератора: " + str(body["kind"]))
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Data-условие §56 (`values.is_data_condition`): boolean, comparison, and, or.
|
|
685
|
+
func (ev *evaluation) dataCond(f map[string]interface{}, e env) bool {
|
|
686
|
+
switch str(f["kind"]) {
|
|
687
|
+
case "boolean":
|
|
688
|
+
b, _ := f["value"].(bool)
|
|
689
|
+
return b
|
|
690
|
+
case "comparison":
|
|
691
|
+
v := ev.evalTerm(map[string]interface{}{"kind": "binary", "op": f["op"], "left": f["left"], "right": f["right"]}, e)
|
|
692
|
+
return v.v.(bool)
|
|
693
|
+
case "and":
|
|
694
|
+
for _, x := range list(f, "items") {
|
|
695
|
+
if !ev.dataCond(x.(map[string]interface{}), e) {
|
|
696
|
+
return false
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return true
|
|
700
|
+
case "or":
|
|
701
|
+
for _, x := range list(f, "items") {
|
|
702
|
+
if ev.dataCond(x.(map[string]interface{}), e) {
|
|
703
|
+
return true
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return false
|
|
707
|
+
}
|
|
708
|
+
refuse("условие терма §56 вне data-условия вне среза раннера: " + str(f["kind"]))
|
|
709
|
+
return false
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
func (ev *evaluation) evalTerm(node map[string]interface{}, e env) typed {
|
|
713
|
+
switch str(node["kind"]) {
|
|
714
|
+
case "var":
|
|
715
|
+
b, ok := e[str(node["var"])]
|
|
716
|
+
if !ok {
|
|
717
|
+
refuse("несвязанная переменная терма: " + str(node["var"]))
|
|
718
|
+
}
|
|
719
|
+
return b
|
|
720
|
+
case "value", "entity_ref", "const_ref":
|
|
721
|
+
if str(node["kind"]) == "value" && str(field(node, "type")["name"]) == tRAT {
|
|
722
|
+
// ERR-E0005: операнд вычисления разбирается как у оракула — сократимая
|
|
723
|
+
// запись нормализуется; каноничность §168.1 — у записей, не у операнда.
|
|
724
|
+
rk, rtext := rawOfValue(node["value"])
|
|
725
|
+
return typed{ratTerm(litText(rk, rtext)), tRAT}
|
|
726
|
+
}
|
|
727
|
+
return ev.typedArg(node)
|
|
728
|
+
case "binary":
|
|
729
|
+
l := ev.evalTerm(field(node, "left"), e)
|
|
730
|
+
r := ev.evalTerm(field(node, "right"), e)
|
|
731
|
+
op := str(node["op"])
|
|
732
|
+
if isCmpOp(op) {
|
|
733
|
+
// §57 (`values.binary`, `_check_same_kind`): валюта, единица, затем вид.
|
|
734
|
+
if l.t == tMON && r.t == tMON && l.v.(Money).Cur != r.v.(Money).Cur {
|
|
735
|
+
panic(termError{"CURRENCY_MISMATCH"})
|
|
736
|
+
}
|
|
737
|
+
if l.t == tQTY && r.t == tQTY && l.v.(Qty).Unit != r.v.(Qty).Unit {
|
|
738
|
+
panic(termError{"DIMENSION_MISMATCH"})
|
|
739
|
+
}
|
|
740
|
+
if (l.t == tDATE && r.t == tINSTANT) || (l.t == tINSTANT && r.t == tDATE) {
|
|
741
|
+
panic(termError{"DATE_INSTANT_MISMATCH"})
|
|
742
|
+
}
|
|
743
|
+
if normKind(l.t) != normKind(r.t) {
|
|
744
|
+
panic(termError{"TYPE_ERROR"})
|
|
745
|
+
}
|
|
746
|
+
if l.t == tBOOL {
|
|
747
|
+
// §57 (DECISION-0266): Boolean сравнимо только на равенство; порядок — TYPE_ERROR оракула.
|
|
748
|
+
if op != "eq" && op != "ne" {
|
|
749
|
+
panic(termError{"TYPE_ERROR"})
|
|
750
|
+
}
|
|
751
|
+
return typed{cmp(l.v, op, r.v), tBOOL}
|
|
752
|
+
}
|
|
753
|
+
if !isNumT(l.t) && l.t != tDATE && l.t != tINSTANT {
|
|
754
|
+
refuse("операция вне среза раннера: " + op)
|
|
755
|
+
}
|
|
756
|
+
return typed{cmp(l.v, op, r.v), tBOOL}
|
|
757
|
+
}
|
|
758
|
+
if !isNumT(l.t) || !isNumT(r.t) {
|
|
759
|
+
refuse("арифметика вне среза (§58): не числовой операнд " + op)
|
|
760
|
+
}
|
|
761
|
+
switch op {
|
|
762
|
+
case "add", "sub", "mul", "div", "mod":
|
|
763
|
+
default:
|
|
764
|
+
refuse("операция вне среза раннера: " + op)
|
|
765
|
+
}
|
|
766
|
+
// §58 `values.binary` (DECISION-0252 п. 8): вид исхода — по значению;
|
|
767
|
+
// ошибка значения — termError, ответ вопроса `term` — RUNTIME_ERROR.
|
|
768
|
+
v := arith(op, l.v, r.v, kindCode[l.t], kindCode[r.t])
|
|
769
|
+
switch v.(type) {
|
|
770
|
+
case Rational:
|
|
771
|
+
return typed{v, tRAT}
|
|
772
|
+
case Money:
|
|
773
|
+
return typed{v, tMON}
|
|
774
|
+
case Qty:
|
|
775
|
+
return typed{v, tQTY}
|
|
776
|
+
}
|
|
777
|
+
if normKind(l.t) == tINT && normKind(r.t) == tINT {
|
|
778
|
+
return typed{v, tINT}
|
|
779
|
+
}
|
|
780
|
+
return typed{v, tDEC}
|
|
781
|
+
case "call":
|
|
782
|
+
fn := str(node["function"])
|
|
783
|
+
if uf, isUser := ev.m.FUNCTIONS[fn]; isUser {
|
|
784
|
+
// DECISION-0273: функция §46 модуля; арность и типы аргументов — TYPE_ERROR
|
|
785
|
+
// оракула (`_require_type`), вид результата — объявленный.
|
|
786
|
+
var vals []Val
|
|
787
|
+
raw := list(node, "args")
|
|
788
|
+
if len(raw) != len(uf.Params) {
|
|
789
|
+
panic(termError{"TYPE_ERROR"})
|
|
790
|
+
}
|
|
791
|
+
for i, a := range raw {
|
|
792
|
+
t := ev.evalTerm(a.(map[string]interface{}), e)
|
|
793
|
+
want := uf.Params[i]
|
|
794
|
+
ok := t.t == "enum" || t.t == want
|
|
795
|
+
if strings.HasPrefix(want, "urn:law:std#") {
|
|
796
|
+
ok = normKind(t.t) == normKind(want)
|
|
797
|
+
}
|
|
798
|
+
if !ok {
|
|
799
|
+
panic(termError{"TYPE_ERROR"})
|
|
800
|
+
}
|
|
801
|
+
vals = append(vals, t.v)
|
|
802
|
+
}
|
|
803
|
+
rt := uf.Returns
|
|
804
|
+
if !strings.HasPrefix(rt, "urn:law:std#") {
|
|
805
|
+
rt = "enum"
|
|
806
|
+
}
|
|
807
|
+
return typed{uf.Fn(vals), rt}
|
|
808
|
+
}
|
|
809
|
+
if i := strings.LastIndex(fn, "#"); i >= 0 {
|
|
810
|
+
fn = fn[i+1:]
|
|
811
|
+
}
|
|
812
|
+
var args []typed
|
|
813
|
+
for _, a := range list(node, "args") {
|
|
814
|
+
args = append(args, ev.evalTerm(a.(map[string]interface{}), e))
|
|
815
|
+
}
|
|
816
|
+
ks := make([]string, len(args))
|
|
817
|
+
for i, a := range args {
|
|
818
|
+
ks[i] = a.t
|
|
819
|
+
}
|
|
820
|
+
t := callKind(fn, ks)
|
|
821
|
+
switch fn {
|
|
822
|
+
case "round":
|
|
823
|
+
// DECISION-0259: политика §50 проверяется рантаймом; виды вопроса точные.
|
|
824
|
+
return typed{roundMode(args[0].v, args[1].v, args[2].v, kindCode[args[0].t], kindCode[args[1].t]), t}
|
|
825
|
+
case "div_round":
|
|
826
|
+
return typed{divRound(args[0].v, args[1].v, args[2].v, args[3].v,
|
|
827
|
+
kindCode[args[0].t], kindCode[args[1].t], kindCode[args[2].t]), t}
|
|
828
|
+
case "min":
|
|
829
|
+
return typed{min(args[0].v, args[1].v), t}
|
|
830
|
+
case "max":
|
|
831
|
+
return typed{max(args[0].v, args[1].v), t}
|
|
832
|
+
case "abs":
|
|
833
|
+
return typed{abs(args[0].v), t}
|
|
834
|
+
case "pow":
|
|
835
|
+
return typed{pow(args[0].v, args[1].v), t}
|
|
836
|
+
case "month_start_of":
|
|
837
|
+
return typed{month_start(args[0].v), t}
|
|
838
|
+
case "month_end_of":
|
|
839
|
+
return typed{month_end(args[0].v), t}
|
|
840
|
+
}
|
|
841
|
+
case "aggregate":
|
|
842
|
+
return ev.aggregate(node)
|
|
843
|
+
case "if":
|
|
844
|
+
// §56 (E-0180, DECISION-0270): чистое data-условие, ветвь вычисляется лениво.
|
|
845
|
+
if ev.dataCond(field(node, "condition"), e) {
|
|
846
|
+
return ev.evalTerm(field(node, "then"), e)
|
|
847
|
+
}
|
|
848
|
+
return ev.evalTerm(field(node, "else"), e)
|
|
849
|
+
}
|
|
850
|
+
refuse("узел терма вне среза раннера: " + str(node["kind"]))
|
|
851
|
+
return typed{}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
type keyed struct {
|
|
855
|
+
key string
|
|
856
|
+
val typed
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Материализация §52.1: элементы в порядке канонических байтов, distinct — множество.
|
|
860
|
+
func (ev *evaluation) materialize(comp map[string]interface{}) []typed {
|
|
861
|
+
if str(comp["kind"]) != "comprehension" {
|
|
862
|
+
refuse("aggregate.input вне среза раннера")
|
|
863
|
+
}
|
|
864
|
+
if len(list(comp, "binders")) > 0 {
|
|
865
|
+
refuse("связки comprehension вне среза раннера")
|
|
866
|
+
}
|
|
867
|
+
var atoms, guards []map[string]interface{}
|
|
868
|
+
flatten(field(comp, "generator"), &atoms, &guards)
|
|
869
|
+
if len(atoms) == 0 {
|
|
870
|
+
refuse("тело без перебираемых атомов вне среза генератора")
|
|
871
|
+
}
|
|
872
|
+
for _, a := range atoms {
|
|
873
|
+
if str(a["polarity"]) != "positive" {
|
|
874
|
+
refuse("отрицательный литерал в теле вне L0-среза")
|
|
875
|
+
}
|
|
876
|
+
params, ok := ev.m.PARAMS[str(a["predicate"])]
|
|
877
|
+
if !ok {
|
|
878
|
+
refuse("предикат comprehension не объявлен в пакете: " + str(a["predicate"]))
|
|
879
|
+
}
|
|
880
|
+
if len(list(a, "args")) != len(params) {
|
|
881
|
+
refuse("арность атома тела не совпадает с сигнатурой §189")
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
distinct, _ := comp["distinct"].(bool)
|
|
885
|
+
out := []keyed{}
|
|
886
|
+
seen := map[string]bool{}
|
|
887
|
+
element := field(comp, "element")
|
|
888
|
+
var walk func(i int, e env)
|
|
889
|
+
walk = func(i int, e env) {
|
|
890
|
+
if i == len(atoms) {
|
|
891
|
+
for _, g := range guards {
|
|
892
|
+
if !cmp(ev.evalTerm(field(g, "left"), e).v, str(g["op"]), ev.evalTerm(field(g, "right"), e).v) {
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
val := ev.evalTerm(element, e)
|
|
897
|
+
key := literalOf(val)
|
|
898
|
+
if distinct {
|
|
899
|
+
if seen[key] {
|
|
900
|
+
return
|
|
901
|
+
}
|
|
902
|
+
seen[key] = true
|
|
903
|
+
}
|
|
904
|
+
out = append(out, keyed{key, val})
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
a := atoms[i]
|
|
908
|
+
pred := str(a["predicate"])
|
|
909
|
+
args := list(a, "args")
|
|
910
|
+
// L1 (DECISION-0237): только установленные строки (TRUE_ONLY), без BOTH.
|
|
911
|
+
for _, row := range ev.m.establishedRows(pred) {
|
|
912
|
+
next := env{}
|
|
913
|
+
for k, v := range e {
|
|
914
|
+
next[k] = v
|
|
915
|
+
}
|
|
916
|
+
ok := true
|
|
917
|
+
for j := 0; j < len(row) && ok; j++ {
|
|
918
|
+
x := args[j].(map[string]interface{})
|
|
919
|
+
if str(x["kind"]) == "var" {
|
|
920
|
+
name := str(x["var"])
|
|
921
|
+
if b, bound := next[name]; !bound {
|
|
922
|
+
next[name] = ev.slotTyped(pred, j, row[j])
|
|
923
|
+
} else if !eq(b.v, row[j]) {
|
|
924
|
+
ok = false
|
|
925
|
+
}
|
|
926
|
+
} else if !eq(ev.evalTerm(x, next).v, row[j]) {
|
|
927
|
+
ok = false
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if ok {
|
|
931
|
+
walk(i+1, next)
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
walk(0, env{})
|
|
936
|
+
sort.SliceStable(out, func(i, j int) bool { return out[i].key < out[j].key })
|
|
937
|
+
vals := make([]typed, len(out))
|
|
938
|
+
for i, kv := range out {
|
|
939
|
+
vals[i] = kv.val
|
|
940
|
+
}
|
|
941
|
+
return vals
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
func (ev *evaluation) aggregate(node map[string]interface{}) typed {
|
|
945
|
+
items := ev.materialize(field(node, "input"))
|
|
946
|
+
op := str(node["op"])
|
|
947
|
+
if op == "count" {
|
|
948
|
+
return typed{dec(fmt.Sprint(len(items))), tINT}
|
|
949
|
+
}
|
|
950
|
+
if op == "only" {
|
|
951
|
+
// §51 (errata E-0256): функциональный lookup. Политика — TYPE_ERROR у
|
|
952
|
+
// оракула; пусто и два различных значения (канонические байты §208) —
|
|
953
|
+
// ошибки значения RUNTIME_ERROR, код тот же, что у оракула.
|
|
954
|
+
if len(field(node, "options")) > 0 {
|
|
955
|
+
panic(termError{"TYPE_ERROR"})
|
|
956
|
+
}
|
|
957
|
+
if len(items) == 0 {
|
|
958
|
+
panic(termError{"EMPTY_AGGREGATE"})
|
|
959
|
+
}
|
|
960
|
+
distinct := map[string]bool{}
|
|
961
|
+
for _, it := range items {
|
|
962
|
+
distinct[literalOf(it)] = true
|
|
963
|
+
}
|
|
964
|
+
if len(distinct) > 1 {
|
|
965
|
+
panic(termError{"CARDINALITY_CONFLICT"})
|
|
966
|
+
}
|
|
967
|
+
return typed{items[0].v, normKind(items[0].t)}
|
|
968
|
+
}
|
|
969
|
+
if op != "sum" && op != "min" && op != "max" {
|
|
970
|
+
refuse("агрегат вне среза раннера: " + op)
|
|
971
|
+
}
|
|
972
|
+
if len(items) == 0 {
|
|
973
|
+
empty, ok := field(node, "options")["empty"].(map[string]interface{})
|
|
974
|
+
if !ok {
|
|
975
|
+
refuse("EMPTY_AGGREGATE: " + op + " над пустой коллекцией без явной empty-политики (§59)")
|
|
976
|
+
}
|
|
977
|
+
// `from_literal_term` оракула: политика — LiteralTerm §53; иной узел
|
|
978
|
+
// даёт TYPE_ERROR раньше сверки вида.
|
|
979
|
+
if str(empty["kind"]) != "value" {
|
|
980
|
+
panic(termError{"TYPE_ERROR"})
|
|
981
|
+
}
|
|
982
|
+
value := ev.typedArg(empty)
|
|
983
|
+
// errata E-0195: вид политики обязан совпасть с видом элемента. У оракула
|
|
984
|
+
// это issue TYPE_ERROR с текстом «aggregate.empty: вид политики integer
|
|
985
|
+
// не совпадает с видом элемента коллекции money (§59)»; порождённый
|
|
986
|
+
// модуль сообщений не печатает вовсе, и байтовый контракт среза здесь —
|
|
987
|
+
// КОД issue, который тот же. До правки коллекция Money с политикой
|
|
988
|
+
// Integer отвечала `Integer 0` и `COMPUTED` — другой ответ, не меньший.
|
|
989
|
+
elem := str(field(field(field(node, "input"), "variable"), "type")["name"])
|
|
990
|
+
if expected, known := emptyElementKinds[elem]; known && normKind(value.t) != expected {
|
|
991
|
+
panic(termError{"TYPE_ERROR"})
|
|
992
|
+
}
|
|
993
|
+
return value
|
|
994
|
+
}
|
|
995
|
+
acc := items[0]
|
|
996
|
+
for _, it := range items[1:] {
|
|
997
|
+
if !isNumT(acc.t) || normKind(it.t) != normKind(acc.t) {
|
|
998
|
+
refuse("арифметика вне среза (§58): виды операндов не совпадают у " + op)
|
|
999
|
+
}
|
|
1000
|
+
if op == "sum" {
|
|
1001
|
+
acc = typed{add(acc.v, it.v), acc.t}
|
|
1002
|
+
} else {
|
|
1003
|
+
c := "gt"
|
|
1004
|
+
if op == "min" {
|
|
1005
|
+
c = "lt"
|
|
1006
|
+
}
|
|
1007
|
+
if cmp(it.v, c, acc.v) {
|
|
1008
|
+
acc = it
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
if !isNumT(acc.t) {
|
|
1013
|
+
refuse("арифметика вне среза (§58): не числовой операнд " + op)
|
|
1014
|
+
}
|
|
1015
|
+
// DECISION-0266: итог агрегата над Natural у оракула — вид `integer`, то есть Integer.
|
|
1016
|
+
return typed{acc.v, normKind(acc.t)}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Каноническая десятичная запись §18/§208: без хвостовых нулей и minus-zero.
|
|
1020
|
+
func canonDec(v Val) string {
|
|
1021
|
+
u, s, _, _, _ := scaled(v)
|
|
1022
|
+
if u.Sign() == 0 {
|
|
1023
|
+
return "0"
|
|
1024
|
+
}
|
|
1025
|
+
x := new(big.Int).Set(u)
|
|
1026
|
+
ten := big.NewInt(10)
|
|
1027
|
+
for s > 0 && new(big.Int).Mod(x, ten).Sign() == 0 {
|
|
1028
|
+
x.Div(x, ten)
|
|
1029
|
+
s--
|
|
1030
|
+
}
|
|
1031
|
+
neg := x.Sign() < 0
|
|
1032
|
+
body := new(big.Int).Abs(x).String()
|
|
1033
|
+
for len(body) < s+1 {
|
|
1034
|
+
body = "0" + body
|
|
1035
|
+
}
|
|
1036
|
+
text := body
|
|
1037
|
+
if s > 0 {
|
|
1038
|
+
text = body[:len(body)-s] + "." + body[len(body)-s:]
|
|
1039
|
+
}
|
|
1040
|
+
if neg && x.Sign() != 0 {
|
|
1041
|
+
return "-" + text
|
|
1042
|
+
}
|
|
1043
|
+
return text
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Каноническая строка §208: экраны как у оракула, не-ASCII как есть.
|
|
1047
|
+
func canonStr(s string) string {
|
|
1048
|
+
var b bytes.Buffer
|
|
1049
|
+
b.WriteByte('"')
|
|
1050
|
+
for _, r := range s {
|
|
1051
|
+
switch r {
|
|
1052
|
+
case '"':
|
|
1053
|
+
b.WriteString(`\"`)
|
|
1054
|
+
case '\\':
|
|
1055
|
+
b.WriteString(`\\`)
|
|
1056
|
+
case '\b':
|
|
1057
|
+
b.WriteString(`\b`)
|
|
1058
|
+
case '\f':
|
|
1059
|
+
b.WriteString(`\f`)
|
|
1060
|
+
case '\n':
|
|
1061
|
+
b.WriteString(`\n`)
|
|
1062
|
+
case '\r':
|
|
1063
|
+
b.WriteString(`\r`)
|
|
1064
|
+
case '\t':
|
|
1065
|
+
b.WriteString(`\t`)
|
|
1066
|
+
default:
|
|
1067
|
+
if r < 0x20 {
|
|
1068
|
+
fmt.Fprintf(&b, `\u%04x`, r)
|
|
1069
|
+
} else {
|
|
1070
|
+
b.WriteRune(r)
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
b.WriteByte('"')
|
|
1075
|
+
return b.String()
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Литерал-терм §53 из typed-значения в канонических байтах — то, что печатает оракул.
|
|
1079
|
+
func literalOf(x typed) string {
|
|
1080
|
+
switch x.t {
|
|
1081
|
+
case "entity":
|
|
1082
|
+
return `{"id":` + canonStr(x.v.(string)) + `,"kind":"entity_ref"}`
|
|
1083
|
+
case "enum":
|
|
1084
|
+
return `{"id":` + canonStr(string(x.v.(Sym))) + `,"kind":"const_ref"}`
|
|
1085
|
+
case tTEXT:
|
|
1086
|
+
return `{"kind":"value","type":{"name":"` + tTEXT + `"},"value":` + canonStr(x.v.(string)) + `}`
|
|
1087
|
+
case tDATE:
|
|
1088
|
+
return `{"kind":"value","type":{"name":"` + tDATE + `"},"value":` + canonStr(string(x.v.(Date))) + `}`
|
|
1089
|
+
case tINSTANT:
|
|
1090
|
+
return `{"kind":"value","type":{"name":"` + tINSTANT + `"},"value":` + canonStr(string(x.v.(Instant))) + `}`
|
|
1091
|
+
case tMON:
|
|
1092
|
+
m := x.v.(Money)
|
|
1093
|
+
return `{"currency":` + canonStr(m.Cur) + `,"kind":"value","type":{"name":"` + tMON + `"},"value":"` + canonDec(x.v) + `"}`
|
|
1094
|
+
case tDEC:
|
|
1095
|
+
return `{"kind":"value","type":{"name":"` + tDEC + `"},"value":"` + canonDec(x.v) + `"}`
|
|
1096
|
+
case tRAT:
|
|
1097
|
+
q := x.v.(Rational)
|
|
1098
|
+
return `{"kind":"value","type":{"name":"` + tRAT + `"},"value":` + canonStr(q.N.String()+"/"+q.D.String()) + `}`
|
|
1099
|
+
case tBOOL:
|
|
1100
|
+
// Исход сравнения §57 — JSON-булево; ключи по порядку §208.
|
|
1101
|
+
v := "false"
|
|
1102
|
+
if x.v.(bool) {
|
|
1103
|
+
v = "true"
|
|
1104
|
+
}
|
|
1105
|
+
return `{"kind":"value","type":{"name":"` + tBOOL + `"},"value":` + v + `}`
|
|
1106
|
+
case tQTY:
|
|
1107
|
+
// DECISION-0256 п. 7: канонический Decimal и тег как есть; ключи по порядку §208.
|
|
1108
|
+
return `{"kind":"value","type":{"name":"` + tQTY + `"},"unit":` + canonStr(x.v.(Qty).Unit) + `,"value":"` + canonDec(x.v) + `"}`
|
|
1109
|
+
case tNAT:
|
|
1110
|
+
// DECISION-0266: запись дела хранит тип Natural; вычисленное целое — всегда Integer.
|
|
1111
|
+
u, s, _, _, _ := scaled(x.v)
|
|
1112
|
+
return `{"kind":"value","type":{"name":"` + tNAT + `"},"value":` + new(big.Int).Div(u, p10(s)).String() + `}`
|
|
1113
|
+
case tINT:
|
|
1114
|
+
u, s, _, _, _ := scaled(x.v)
|
|
1115
|
+
if new(big.Int).Mod(u, p10(s)).Sign() != 0 {
|
|
1116
|
+
refuse("Integer с дробной частью в результате")
|
|
1117
|
+
}
|
|
1118
|
+
return `{"kind":"value","type":{"name":"` + tINT + `"},"value":` + new(big.Int).Div(u, p10(s)).String() + `}`
|
|
1119
|
+
}
|
|
1120
|
+
refuse("вид результата вне среза раннера: " + x.t)
|
|
1121
|
+
return ""
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// Канонический JSON §208 значения шаблона с заземлением переменных payload —
|
|
1125
|
+
// порт `norms._ground_payload` (DECISION-0242).
|
|
1126
|
+
func canonAny(v interface{}, subst map[string]string) string {
|
|
1127
|
+
switch x := v.(type) {
|
|
1128
|
+
case nil:
|
|
1129
|
+
return "null"
|
|
1130
|
+
case bool:
|
|
1131
|
+
if x {
|
|
1132
|
+
return "true"
|
|
1133
|
+
}
|
|
1134
|
+
return "false"
|
|
1135
|
+
case string:
|
|
1136
|
+
return canonStr(x)
|
|
1137
|
+
case json.Number:
|
|
1138
|
+
return string(x)
|
|
1139
|
+
case []interface{}:
|
|
1140
|
+
parts := make([]string, len(x))
|
|
1141
|
+
for i, item := range x {
|
|
1142
|
+
parts[i] = canonAny(item, subst)
|
|
1143
|
+
}
|
|
1144
|
+
return "[" + strings.Join(parts, ",") + "]"
|
|
1145
|
+
case map[string]interface{}:
|
|
1146
|
+
if x["kind"] == "var" {
|
|
1147
|
+
if name, ok := x["var"].(string); ok {
|
|
1148
|
+
if s, ok := subst[name]; ok {
|
|
1149
|
+
return s
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
fields := map[string]string{}
|
|
1154
|
+
for k, item := range x {
|
|
1155
|
+
fields[k] = canonAny(item, subst)
|
|
1156
|
+
}
|
|
1157
|
+
return canonObject(fields)
|
|
1158
|
+
}
|
|
1159
|
+
refuse("значение payload вне среза раннера")
|
|
1160
|
+
return ""
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
func canonObject(fields map[string]string) string {
|
|
1164
|
+
keys := make([]string, 0, len(fields))
|
|
1165
|
+
for k := range fields {
|
|
1166
|
+
keys = append(keys, k)
|
|
1167
|
+
}
|
|
1168
|
+
sort.Strings(keys)
|
|
1169
|
+
parts := make([]string, len(keys))
|
|
1170
|
+
for i, k := range keys {
|
|
1171
|
+
parts[i] = canonStr(k) + ":" + fields[k]
|
|
1172
|
+
}
|
|
1173
|
+
return "{" + strings.Join(parts, ",") + "}"
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Вопрос `positions` (DECISION-0242 K4): позиции без id и доказательства.
|
|
1177
|
+
// Обход запроса судье §47.3 (DECISION-0274): переводы термов раннера для рантайма;
|
|
1178
|
+
// множества сущностей и текстов дела обходом не двигаются.
|
|
1179
|
+
func queryTail(qid string) string {
|
|
1180
|
+
if i := strings.LastIndex(qid, ":"); i >= 0 {
|
|
1181
|
+
return qid[i+1:]
|
|
1182
|
+
}
|
|
1183
|
+
return qid
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
func (ev *evaluation) judgmentRequestsFor(status string, conflict bool) []map[string]interface{} {
|
|
1187
|
+
generic := decodeGeneric(ev.queryRaw)
|
|
1188
|
+
goal := field(generic, "literal")
|
|
1189
|
+
if goal == nil {
|
|
1190
|
+
return nil
|
|
1191
|
+
}
|
|
1192
|
+
if _, ok := goal["polarity"]; !ok {
|
|
1193
|
+
goal["polarity"] = "positive"
|
|
1194
|
+
}
|
|
1195
|
+
return buildRequests(ev.m.judgmentModel(), judgmentBlockers(ev.m.judgmentModel(), ev.hooks, goal, status, conflict), queryTail(str(generic["queryId"])))
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
func effectiveContext(kase map[string]interface{}) map[string]interface{} {
|
|
1199
|
+
raw := map[string]interface{}{}
|
|
1200
|
+
for k, v := range field(kase, "context") {
|
|
1201
|
+
raw[k] = v
|
|
1202
|
+
}
|
|
1203
|
+
profiles := field(kase, "contextProfiles")
|
|
1204
|
+
parent, _ := raw["extends"].(string)
|
|
1205
|
+
delete(raw, "extends")
|
|
1206
|
+
chain, seen := []string{}, map[string]bool{}
|
|
1207
|
+
for current := parent; current != ""; {
|
|
1208
|
+
if seen[current] {
|
|
1209
|
+
refuse("CONTEXT_PROFILE_CYCLE: цикл наследования профилей (§89.1)")
|
|
1210
|
+
}
|
|
1211
|
+
prof, ok := profiles[current].(map[string]interface{})
|
|
1212
|
+
if !ok {
|
|
1213
|
+
refuse("CONTEXT_PROFILE_MISSING: профиль не определён (§89.1)")
|
|
1214
|
+
}
|
|
1215
|
+
seen[current] = true
|
|
1216
|
+
chain = append(chain, current)
|
|
1217
|
+
current, _ = prof["extends"].(string)
|
|
1218
|
+
}
|
|
1219
|
+
effective := map[string]interface{}{}
|
|
1220
|
+
for i := len(chain) - 1; i >= 0; i-- {
|
|
1221
|
+
for k, v := range field(profiles[chain[i]].(map[string]interface{}), "fields") {
|
|
1222
|
+
effective[k] = v
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
for k, v := range raw {
|
|
1226
|
+
effective[k] = v
|
|
1227
|
+
}
|
|
1228
|
+
return effective
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
func (ev *evaluation) keyConflictBlocks(predicate string) bool {
|
|
1232
|
+
tainted := ev.m.keyConflictPredicates()
|
|
1233
|
+
waiting := make([]string, 0, len(tainted))
|
|
1234
|
+
for source := range tainted {
|
|
1235
|
+
waiting = append(waiting, source)
|
|
1236
|
+
}
|
|
1237
|
+
for len(waiting) != 0 {
|
|
1238
|
+
source := waiting[len(waiting)-1]
|
|
1239
|
+
waiting = waiting[:len(waiting)-1]
|
|
1240
|
+
for _, consumer := range ev.m.KEY_READERS[source] {
|
|
1241
|
+
if !tainted[consumer] {
|
|
1242
|
+
tainted[consumer] = true
|
|
1243
|
+
waiting = append(waiting, consumer)
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return tainted[predicate]
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Политика §86 и снимок §85 дела до вывода (DECISION-0245 п. 4): порт
|
|
1251
|
+
// `engine._resolve_deadline_policy` и `engine._calendar_snapshot`.
|
|
1252
|
+
func (ev *evaluation) calendarSetup() {
|
|
1253
|
+
semver, policies, snapshots, ok := ev.m.calendarEnv()
|
|
1254
|
+
if !ok {
|
|
1255
|
+
return
|
|
1256
|
+
}
|
|
1257
|
+
kase := decodeGeneric(ev.caseRaw)
|
|
1258
|
+
var policy map[string]interface{}
|
|
1259
|
+
if ref, present := effectiveContext(kase)["deadlinePolicy"]; present && ref != nil {
|
|
1260
|
+
if strings.HasPrefix(semver, "0.1") {
|
|
1261
|
+
obj, isObj := ref.(map[string]interface{})
|
|
1262
|
+
if !isObj {
|
|
1263
|
+
refuse("case.context.deadlinePolicy вне среза раннера (§86)")
|
|
1264
|
+
}
|
|
1265
|
+
policy = obj
|
|
1266
|
+
} else if name, isStr := ref.(string); !isStr {
|
|
1267
|
+
refuse("case.context.deadlinePolicy: в 0.2 ожидался DeadlinePolicyRef (DECISION-0111 §2.9)")
|
|
1268
|
+
} else if node, found := policies[name].(map[string]interface{}); !found {
|
|
1269
|
+
refuse("case.context.deadlinePolicy: ссылка не разрешена в закреплённом CLIR (§86)")
|
|
1270
|
+
} else {
|
|
1271
|
+
policy = node
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
selected, _ := field(kase, "options")["calendarSnapshot"].(string)
|
|
1275
|
+
var snapshot *Snapshot
|
|
1276
|
+
if selected != "" {
|
|
1277
|
+
for i := range snapshots {
|
|
1278
|
+
if snapshots[i].ID == selected {
|
|
1279
|
+
snapshot = &snapshots[i]
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
if snapshot == nil {
|
|
1283
|
+
refuse("MISSING_CALENDAR: calendar_snapshot не объявлен в программе (§85, §168.2)")
|
|
1284
|
+
}
|
|
1285
|
+
} else if len(snapshots) >= 2 {
|
|
1286
|
+
refuse("AMBIGUOUS_CALENDAR: в программе несколько calendar_snapshot без выбора (§85, §168.2)")
|
|
1287
|
+
} else if len(snapshots) == 1 {
|
|
1288
|
+
snapshot = &snapshots[0]
|
|
1289
|
+
}
|
|
1290
|
+
ev.m.ctx.setCalendar(policy, snapshot)
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// E-0105: код ошибки терма → статус результата (`whynot.TERM_ERROR_STATUS`);
|
|
1294
|
+
// код вне таблицы — RUNTIME_ERROR; старшинство — `TERM_ERROR_PRECEDENCE`.
|
|
1295
|
+
var termErrorStatusOf = map[string]string{"MISSING_POLICY": "MISSING_POLICY", "DEADLINE_POLICY_INVALID": "MISSING_POLICY",
|
|
1296
|
+
"MISSING_CALENDAR": "MISSING_INPUT", "CALENDAR_DATASET_INVALID": "MISSING_INPUT",
|
|
1297
|
+
"CALENDAR_DATASET_HASH_MISMATCH": "MISSING_INPUT", "CALENDAR_OUT_OF_RANGE": "MISSING_INPUT", "TYPE_ERROR": "TYPE_ERROR",
|
|
1298
|
+
"CURRENCY_MISMATCH": "TYPE_ERROR", "DIMENSION_MISMATCH": "TYPE_ERROR", "DATE_INSTANT_MISMATCH": "TYPE_ERROR",
|
|
1299
|
+
"RESOURCE_LIMIT": "RESOURCE_LIMIT", "OPAQUE_NON_EXECUTABLE": "NON_EXECUTABLE", "NON_EXHAUSTIVE_MATCH": "NON_EXECUTABLE",
|
|
1300
|
+
"UNKNOWN_FUNCTION": "NON_EXECUTABLE", "NON_EXECUTABLE_FUNCTION_RECURSION": "NON_EXECUTABLE"}
|
|
1301
|
+
var termErrorPrecedence = []string{"MISSING_INPUT", "MISSING_POLICY", "NON_EXECUTABLE", "EXTERNAL_UNAVAILABLE",
|
|
1302
|
+
"TYPE_ERROR", "RUNTIME_ERROR", "RESOURCE_LIMIT"}
|
|
1303
|
+
|
|
1304
|
+
var outcomeCodes = map[string]bool{"PRIORITY_CONFLICT": true, "INTERPRETATION_REQUIRED": true, "TYPE_ERROR": true,
|
|
1305
|
+
"RESOURCE_LIMIT": true, "CONFLICTED_INPUTS": true, "SOURCE_RESOLUTION_FAILED": true}
|
|
1306
|
+
|
|
1307
|
+
// Статус результата, зависящего от упавшего правила: NEITHER — статус кода (E-0105),
|
|
1308
|
+
// иная истинность — итог §181.2; смешанные коды — порядок issue, которого раннер
|
|
1309
|
+
// не воспроизводит, — названный отказ. `pred` пуст — носители TYPE_ERROR не читаются.
|
|
1310
|
+
func (ev *evaluation) ruleErrorStatus(pred, truth string) string {
|
|
1311
|
+
te := ev.termErrorStatus()
|
|
1312
|
+
codes := map[string]bool{}
|
|
1313
|
+
if te != "" {
|
|
1314
|
+
for c := range ev.lastTermCodes {
|
|
1315
|
+
codes[c] = true
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
ct := ""
|
|
1319
|
+
if pred != "" {
|
|
1320
|
+
ct = ev.carrierTypeError(pred)
|
|
1321
|
+
}
|
|
1322
|
+
if te == "" && ct == "" {
|
|
1323
|
+
return ""
|
|
1324
|
+
}
|
|
1325
|
+
if truth == "NEITHER" {
|
|
1326
|
+
if te != "" {
|
|
1327
|
+
return te
|
|
1328
|
+
}
|
|
1329
|
+
return ct
|
|
1330
|
+
}
|
|
1331
|
+
if ct != "" {
|
|
1332
|
+
codes["TYPE_ERROR"] = true
|
|
1333
|
+
}
|
|
1334
|
+
kept := []string{}
|
|
1335
|
+
for c := range codes {
|
|
1336
|
+
if outcomeCodes[c] {
|
|
1337
|
+
kept = append(kept, c)
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if len(kept) == 0 {
|
|
1341
|
+
return "NON_EXECUTABLE"
|
|
1342
|
+
}
|
|
1343
|
+
if len(kept) == len(codes) && len(kept) == 1 {
|
|
1344
|
+
return kept[0]
|
|
1345
|
+
}
|
|
1346
|
+
refuse("статус итога §181.2 зависит от порядка issue вне среза раннера")
|
|
1347
|
+
return ""
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
func (ev *evaluation) carrierTypeError(pred string) string {
|
|
1351
|
+
if len(ev.typeErrorPredicates) == 0 {
|
|
1352
|
+
return ""
|
|
1353
|
+
}
|
|
1354
|
+
seen := map[string]bool{}
|
|
1355
|
+
var walk func(string) bool
|
|
1356
|
+
walk = func(p string) bool {
|
|
1357
|
+
if seen[p] {
|
|
1358
|
+
return false
|
|
1359
|
+
}
|
|
1360
|
+
seen[p] = true
|
|
1361
|
+
if ev.typeErrorPredicates[p] {
|
|
1362
|
+
return true
|
|
1363
|
+
}
|
|
1364
|
+
for _, c := range ev.m.CARRIER_EDGES[p] {
|
|
1365
|
+
if walk(c) {
|
|
1366
|
+
return true
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
return false
|
|
1370
|
+
}
|
|
1371
|
+
if walk(pred) {
|
|
1372
|
+
return "TYPE_ERROR"
|
|
1373
|
+
}
|
|
1374
|
+
return ""
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Порт `whynot.term_error_blockers` + `term_error_status`: правила с совместимой
|
|
1378
|
+
// головой, их ошибки терма и конъюнкты без опоры — вглубь.
|
|
1379
|
+
func (ev *evaluation) termErrorStatus() string {
|
|
1380
|
+
rules, consts, seen, ok := ev.m.backchainEnv()
|
|
1381
|
+
if !ok {
|
|
1382
|
+
return ""
|
|
1383
|
+
}
|
|
1384
|
+
byRule := map[string][]string{}
|
|
1385
|
+
for k := range seen {
|
|
1386
|
+
at := strings.LastIndex(k, "|")
|
|
1387
|
+
name, code := k[:at], k[at+1:]
|
|
1388
|
+
if code != "MISSING_PARAMETER_VALUE" {
|
|
1389
|
+
byRule[name] = append(byRule[name], code)
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if len(byRule) == 0 {
|
|
1393
|
+
return ""
|
|
1394
|
+
}
|
|
1395
|
+
index := map[string][]map[string]interface{}{}
|
|
1396
|
+
for _, x := range rules {
|
|
1397
|
+
r := x.(map[string]interface{})
|
|
1398
|
+
pred := str(field(r, "head")["predicate"])
|
|
1399
|
+
index[pred] = append(index[pred], r)
|
|
1400
|
+
}
|
|
1401
|
+
canon := func(v interface{}) string { return canonAny(v, map[string]string{}) }
|
|
1402
|
+
argsOf := func(lit map[string]interface{}) []interface{} { return list(lit, "args") }
|
|
1403
|
+
kindOf := func(a interface{}) string { return str(a.(map[string]interface{})["kind"]) }
|
|
1404
|
+
varOf := func(a interface{}) string { return str(a.(map[string]interface{})["var"]) }
|
|
1405
|
+
polarity := func(lit map[string]interface{}) string {
|
|
1406
|
+
if p := str(lit["polarity"]); p != "" {
|
|
1407
|
+
return p
|
|
1408
|
+
}
|
|
1409
|
+
return "positive"
|
|
1410
|
+
}
|
|
1411
|
+
compatible := func(head, target map[string]interface{}) map[string]interface{} {
|
|
1412
|
+
if polarity(head) != polarity(target) {
|
|
1413
|
+
return nil
|
|
1414
|
+
}
|
|
1415
|
+
ha, ga := argsOf(head), argsOf(target)
|
|
1416
|
+
if len(ha) != len(ga) {
|
|
1417
|
+
return nil
|
|
1418
|
+
}
|
|
1419
|
+
subst := map[string]interface{}{}
|
|
1420
|
+
for i := range ha {
|
|
1421
|
+
h, g := ha[i], ga[i]
|
|
1422
|
+
if kindOf(g) == "var" {
|
|
1423
|
+
continue
|
|
1424
|
+
}
|
|
1425
|
+
switch kindOf(h) {
|
|
1426
|
+
case "var":
|
|
1427
|
+
if prev, bound := subst[varOf(h)]; bound && canon(prev) != canon(g) {
|
|
1428
|
+
return nil
|
|
1429
|
+
}
|
|
1430
|
+
subst[varOf(h)] = g
|
|
1431
|
+
case "entity_ref", "const_ref", "value":
|
|
1432
|
+
if canon(h) != canon(g) {
|
|
1433
|
+
return nil
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return subst
|
|
1438
|
+
}
|
|
1439
|
+
mirror := map[string]string{"TRUE_ONLY": "FALSE_ONLY", "FALSE_ONLY": "TRUE_ONLY", "BOTH": "BOTH", "NEITHER": "NEITHER"}
|
|
1440
|
+
truth := func(lit map[string]interface{}) string {
|
|
1441
|
+
savedE, savedT := map[string]bool{}, map[string]bool{}
|
|
1442
|
+
for k := range ev.entities {
|
|
1443
|
+
savedE[k] = true
|
|
1444
|
+
}
|
|
1445
|
+
for k := range ev.texts {
|
|
1446
|
+
savedT[k] = true
|
|
1447
|
+
}
|
|
1448
|
+
defer func() { ev.entities, ev.texts = savedE, savedT }()
|
|
1449
|
+
row := []Val{}
|
|
1450
|
+
for _, a := range argsOf(lit) {
|
|
1451
|
+
b, _ := json.Marshal(a)
|
|
1452
|
+
var aj argJSON
|
|
1453
|
+
if err := json.Unmarshal(b, &aj); err != nil {
|
|
1454
|
+
panic(err)
|
|
1455
|
+
}
|
|
1456
|
+
row = append(row, ev.toVal(aj))
|
|
1457
|
+
}
|
|
1458
|
+
st := ev.m.truthOf(str(lit["predicate"]), row)
|
|
1459
|
+
if polarity(lit) == "negative" {
|
|
1460
|
+
return mirror[st]
|
|
1461
|
+
}
|
|
1462
|
+
return st
|
|
1463
|
+
}
|
|
1464
|
+
found, visited := map[string]bool{}, map[string]bool{}
|
|
1465
|
+
var walk func(target map[string]interface{})
|
|
1466
|
+
walk = func(target map[string]interface{}) {
|
|
1467
|
+
key := canon(target)
|
|
1468
|
+
if visited[key] {
|
|
1469
|
+
return
|
|
1470
|
+
}
|
|
1471
|
+
visited[key] = true
|
|
1472
|
+
for _, r := range index[str(target["predicate"])] {
|
|
1473
|
+
subst := compatible(field(r, "head"), target)
|
|
1474
|
+
if subst == nil {
|
|
1475
|
+
continue
|
|
1476
|
+
}
|
|
1477
|
+
if w, isMap := r["window"].(map[string]interface{}); isMap {
|
|
1478
|
+
start, _ := w["start"].(string)
|
|
1479
|
+
end, _ := w["end"].(string)
|
|
1480
|
+
if (start != "" && ev.m.ctx.legalTime() < start) || (end != "" && ev.m.ctx.legalTime() >= end) {
|
|
1481
|
+
continue
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
for _, c := range byRule[str(r["name"])] {
|
|
1485
|
+
found[c] = true
|
|
1486
|
+
}
|
|
1487
|
+
for _, cj := range list(r, "body") {
|
|
1488
|
+
lit := cj.([]interface{})[1].(map[string]interface{})
|
|
1489
|
+
if str(lit["kind"]) != "literal" {
|
|
1490
|
+
continue // DECISION-0274: гарды в backchain — только для пути судьи
|
|
1491
|
+
}
|
|
1492
|
+
args := argsOf(lit)
|
|
1493
|
+
wildcard, unbound := false, false
|
|
1494
|
+
computed := func(a interface{}) bool {
|
|
1495
|
+
switch kindOf(a) {
|
|
1496
|
+
case "binary", "call", "if", "let", "match":
|
|
1497
|
+
return true
|
|
1498
|
+
}
|
|
1499
|
+
return false
|
|
1500
|
+
}
|
|
1501
|
+
for _, a := range args {
|
|
1502
|
+
if kindOf(a) == "wildcard" {
|
|
1503
|
+
wildcard = true
|
|
1504
|
+
}
|
|
1505
|
+
// DECISION-0270: вычисляемый аргумент не заземляется обходом —
|
|
1506
|
+
// как несвязанная переменная, конъюнкт идёт вглубь без оценки.
|
|
1507
|
+
if computed(a) {
|
|
1508
|
+
unbound = true
|
|
1509
|
+
}
|
|
1510
|
+
if kindOf(a) == "var" {
|
|
1511
|
+
if v, bound := subst[varOf(a)]; !bound || computed(v) {
|
|
1512
|
+
unbound = true
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if wildcard {
|
|
1517
|
+
continue
|
|
1518
|
+
}
|
|
1519
|
+
next := map[string]interface{}{}
|
|
1520
|
+
for k, v := range lit {
|
|
1521
|
+
next[k] = v
|
|
1522
|
+
}
|
|
1523
|
+
ground := []interface{}{}
|
|
1524
|
+
for _, a := range args {
|
|
1525
|
+
if kindOf(a) == "var" {
|
|
1526
|
+
if v, bound := subst[varOf(a)]; bound {
|
|
1527
|
+
ground = append(ground, v)
|
|
1528
|
+
continue
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
ground = append(ground, a)
|
|
1532
|
+
}
|
|
1533
|
+
next["args"] = ground
|
|
1534
|
+
if unbound {
|
|
1535
|
+
walk(next)
|
|
1536
|
+
continue
|
|
1537
|
+
}
|
|
1538
|
+
if truth(next) == "NEITHER" {
|
|
1539
|
+
walk(next)
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
goal := map[string]interface{}{}
|
|
1545
|
+
for k, v := range field(decodeGeneric(ev.queryRaw), "literal") {
|
|
1546
|
+
goal[k] = v
|
|
1547
|
+
}
|
|
1548
|
+
derefd := []interface{}{}
|
|
1549
|
+
for _, a := range list(goal, "args") {
|
|
1550
|
+
m := a.(map[string]interface{})
|
|
1551
|
+
if kindOf(a) == "const_ref" {
|
|
1552
|
+
if v, isConst := consts[str(m["id"])]; isConst {
|
|
1553
|
+
derefd = append(derefd, v)
|
|
1554
|
+
continue
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
derefd = append(derefd, a)
|
|
1558
|
+
}
|
|
1559
|
+
goal["args"] = derefd
|
|
1560
|
+
walk(goal)
|
|
1561
|
+
ev.lastTermCodes = map[string]bool{}
|
|
1562
|
+
for c := range found {
|
|
1563
|
+
ev.lastTermCodes[c] = true
|
|
1564
|
+
}
|
|
1565
|
+
for _, st := range termErrorPrecedence {
|
|
1566
|
+
for c := range found {
|
|
1567
|
+
mapped, known := termErrorStatusOf[c]
|
|
1568
|
+
if !known {
|
|
1569
|
+
mapped = "RUNTIME_ERROR"
|
|
1570
|
+
}
|
|
1571
|
+
if mapped == st {
|
|
1572
|
+
return st
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
return ""
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
func (ev *evaluation) run() string {
|
|
1580
|
+
var kase struct {
|
|
1581
|
+
Context struct {
|
|
1582
|
+
LegalTime string `json:"legalTime"`
|
|
1583
|
+
DecisionTime *string `json:"decisionTime"`
|
|
1584
|
+
} `json:"context"`
|
|
1585
|
+
Assertions []struct {
|
|
1586
|
+
Literal litJSON `json:"literal"`
|
|
1587
|
+
Valid json.RawMessage `json:"valid"`
|
|
1588
|
+
Judgment json.RawMessage `json:"judgment"`
|
|
1589
|
+
} `json:"assertions"`
|
|
1590
|
+
}
|
|
1591
|
+
var query struct {
|
|
1592
|
+
Kind string `json:"kind"`
|
|
1593
|
+
QueryID string `json:"queryId"`
|
|
1594
|
+
Literal litJSON `json:"literal"`
|
|
1595
|
+
Predicate string `json:"predicate"`
|
|
1596
|
+
}
|
|
1597
|
+
mustUnmarshal(ev.caseRaw, &kase)
|
|
1598
|
+
mustUnmarshal(ev.queryRaw, &query)
|
|
1599
|
+
ev.m.ctx.setLegalTime(kase.Context.LegalTime)
|
|
1600
|
+
if kase.Context.DecisionTime != nil {
|
|
1601
|
+
ev.m.setStatusTime((*kase.Context.DecisionTime)[:10])
|
|
1602
|
+
}
|
|
1603
|
+
ev.calendarSetup()
|
|
1604
|
+
ev.m.packageFacts()
|
|
1605
|
+
for _, a := range kase.Assertions {
|
|
1606
|
+
if len(a.Valid) != 0 && string(a.Valid) != "null" {
|
|
1607
|
+
refuse("поле assertion.valid вне среза раннера (§72)")
|
|
1608
|
+
}
|
|
1609
|
+
if a.Literal.Polarity != "positive" && (LAYER != "L1" || a.Literal.Polarity != "negative") {
|
|
1610
|
+
refuse("§113: отрицательный факт вне L0-среза")
|
|
1611
|
+
}
|
|
1612
|
+
if a.Literal.Predicate == "urn:law:std#incompatible" {
|
|
1613
|
+
if _, ok := ev.m.normPositions(); ok {
|
|
1614
|
+
// §105.1: явная несовместимость позиций — конфликт вне среза (DECISION-0242 K5).
|
|
1615
|
+
refuse("конфликт позиций §105.1 (incompatible) вне среза раннера (DECISION-0242 K5)")
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
if !ev.wellTyped(a.Literal) {
|
|
1619
|
+
continue
|
|
1620
|
+
}
|
|
1621
|
+
if _, ok := ev.m.PARAMS[a.Literal.Predicate]; !ok {
|
|
1622
|
+
continue
|
|
1623
|
+
}
|
|
1624
|
+
// §47.3.1 (E-0089, DECISION-0274): ответ судьи не от того органа или не по форме — не принят.
|
|
1625
|
+
if len(a.Judgment) != 0 && string(a.Judgment) != "null" {
|
|
1626
|
+
var judgment map[string]interface{}
|
|
1627
|
+
if err := json.Unmarshal(a.Judgment, &judgment); err != nil {
|
|
1628
|
+
panic(err)
|
|
1629
|
+
}
|
|
1630
|
+
if rejected := judgmentAnswer(ev.m.judgmentModel(), a.Literal.Predicate, judgment); rejected != "" {
|
|
1631
|
+
ev.issues = append(ev.issues, rejected)
|
|
1632
|
+
continue
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
row := make([]Val, len(a.Literal.Args))
|
|
1636
|
+
for i, x := range a.Literal.Args {
|
|
1637
|
+
row[i] = ev.toVal(x)
|
|
1638
|
+
}
|
|
1639
|
+
if a.Literal.Polarity == "negative" {
|
|
1640
|
+
ev.m.registerNegative(a.Literal.Predicate, row)
|
|
1641
|
+
} else {
|
|
1642
|
+
ev.m.register(a.Literal.Predicate, row)
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
ev.m.saturate()
|
|
1646
|
+
// DECISION-0239 G3: ошибки значения агрегатов в правилах — issues модели.
|
|
1647
|
+
ev.issues = append(ev.issues, ev.m.ruleIssues()...)
|
|
1648
|
+
// DECISION-0242: диагностики эффектов полномочий §127–§128.
|
|
1649
|
+
ev.issues = append(ev.issues, ev.m.normIssues()...)
|
|
1650
|
+
for n := ev.m.keyConflicts(); n > 0; n-- {
|
|
1651
|
+
ev.issues = append(ev.issues, "KEY_CONFLICT")
|
|
1652
|
+
}
|
|
1653
|
+
issuesJSON, _ := json.Marshal(ev.issues)
|
|
1654
|
+
qid := canonStr(query.QueryID)
|
|
1655
|
+
if query.Kind == "rows" && ev.api {
|
|
1656
|
+
return ev.rowsJSON(query.Predicate, qid, string(issuesJSON))
|
|
1657
|
+
}
|
|
1658
|
+
if query.Kind == "collect" || query.Kind == "term" {
|
|
1659
|
+
// Числа — json.Number: через float64 длинное целое потеряло бы знаки.
|
|
1660
|
+
generic := decodeGeneric(ev.queryRaw)
|
|
1661
|
+
if query.Kind == "collect" {
|
|
1662
|
+
parts := []string{}
|
|
1663
|
+
for _, e := range ev.materialize(field(generic, "comprehension")) {
|
|
1664
|
+
if s, ok := e.v.(string); ok && strings.HasPrefix(s, "urn:position:") {
|
|
1665
|
+
refuse("позиция в элементе коллекции вне среза раннера (DECISION-0242 K4)")
|
|
1666
|
+
}
|
|
1667
|
+
parts = append(parts, literalOf(e))
|
|
1668
|
+
}
|
|
1669
|
+
return `{"queryId":` + qid + `,"resultKind":"COLLECTION","value":[` + strings.Join(parts, ",") + `],"evaluationStatus":"COMPUTED","issues":` + string(issuesJSON) + `}`
|
|
1670
|
+
}
|
|
1671
|
+
value, code := func() (s string, code string) {
|
|
1672
|
+
defer func() {
|
|
1673
|
+
if rec := recover(); rec != nil {
|
|
1674
|
+
if e, ok := rec.(termError); ok {
|
|
1675
|
+
code = e.code
|
|
1676
|
+
return
|
|
1677
|
+
}
|
|
1678
|
+
panic(rec)
|
|
1679
|
+
}
|
|
1680
|
+
}()
|
|
1681
|
+
return literalOf(ev.evalTerm(field(generic, "term"), env{})), ""
|
|
1682
|
+
}()
|
|
1683
|
+
if code != "" {
|
|
1684
|
+
// §58/§175 (DECISION-0252 п. 8): ошибка значения вопроса — без значения;
|
|
1685
|
+
// статус по таблице `queries.query_calc`: несовместимость видов —
|
|
1686
|
+
// TYPE_ERROR, прочее — RUNTIME_ERROR.
|
|
1687
|
+
status := "RUNTIME_ERROR"
|
|
1688
|
+
switch code {
|
|
1689
|
+
case "TYPE_ERROR", "CURRENCY_MISMATCH", "DIMENSION_MISMATCH", "DATE_INSTANT_MISMATCH":
|
|
1690
|
+
status = "TYPE_ERROR"
|
|
1691
|
+
}
|
|
1692
|
+
failed, _ := json.Marshal(append(append([]string{}, ev.issues...), code))
|
|
1693
|
+
return `{"queryId":` + qid + `,"resultKind":"DATA","evaluationStatus":"` + status + `","issues":` + string(failed) + `}`
|
|
1694
|
+
}
|
|
1695
|
+
return `{"queryId":` + qid + `,"resultKind":"DATA","value":` + value + `,"evaluationStatus":"COMPUTED","issues":` + string(issuesJSON) + `}`
|
|
1696
|
+
}
|
|
1697
|
+
if query.Kind != "truth" {
|
|
1698
|
+
refuse("вид запроса вне среза раннера: " + query.Kind)
|
|
1699
|
+
}
|
|
1700
|
+
for _, x := range query.Literal.Args {
|
|
1701
|
+
if x.Kind == "entity_ref" && strings.HasPrefix(x.ID, "urn:position:") {
|
|
1702
|
+
refuse("позиция в аргументе вопроса вне среза раннера (DECISION-0242 K4)")
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
if LAYER == "L1" {
|
|
1706
|
+
// §62/§63: статус атома по опорам обеих полярностей; отрицательный
|
|
1707
|
+
// вопрос зеркалит стороны; несогласованный по сигнатуре вопрос — NEITHER.
|
|
1708
|
+
status := "NEITHER"
|
|
1709
|
+
evaluation := "COMPUTED"
|
|
1710
|
+
conflict := false
|
|
1711
|
+
if ev.queryTyped(query.Literal) {
|
|
1712
|
+
want := make([]Val, len(query.Literal.Args))
|
|
1713
|
+
for i, x := range query.Literal.Args {
|
|
1714
|
+
want[i] = ev.toVal(x)
|
|
1715
|
+
}
|
|
1716
|
+
status = ev.m.truthOf(query.Literal.Predicate, want)
|
|
1717
|
+
conflict = ev.m.blockedEffect(query.Literal.Predicate, want)
|
|
1718
|
+
}
|
|
1719
|
+
if query.Literal.Polarity == "negative" {
|
|
1720
|
+
status = map[string]string{"TRUE_ONLY": "FALSE_ONLY", "FALSE_ONLY": "TRUE_ONLY", "BOTH": "BOTH", "NEITHER": "NEITHER"}[status]
|
|
1721
|
+
}
|
|
1722
|
+
// §47.3 (DECISION-0274): судьи по лестнице §175 — конфликт старше судьи, судья старше ошибки терма.
|
|
1723
|
+
requests := ev.judgmentRequestsFor(status, conflict)
|
|
1724
|
+
if conflict {
|
|
1725
|
+
// §127: эффект заперт непобеждённой парой Power/Immunity.
|
|
1726
|
+
evaluation = "UNRESOLVED_NORMATIVE_CONFLICT"
|
|
1727
|
+
} else if ev.keyConflictBlocks(query.Literal.Predicate) {
|
|
1728
|
+
evaluation = "NON_EXECUTABLE"
|
|
1729
|
+
} else if len(requests) > 0 {
|
|
1730
|
+
evaluation = "REQUIRES_JUDGMENT"
|
|
1731
|
+
} else if st := ev.ruleErrorStatus(query.Literal.Predicate, status); st != "" {
|
|
1732
|
+
// E-0243 §181.2: носитель TYPE_ERROR блокирует своих читателей и у L1, как у py и ts.
|
|
1733
|
+
evaluation = st
|
|
1734
|
+
} else if status == "NEITHER" {
|
|
1735
|
+
// E-0105 (§175/§185): NEITHER над ошибкой терма правила-кандидата.
|
|
1736
|
+
if st := ev.termErrorStatus(); st != "" {
|
|
1737
|
+
evaluation = st
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if requests == nil {
|
|
1741
|
+
requests = []map[string]interface{}{}
|
|
1742
|
+
}
|
|
1743
|
+
out, _ := json.Marshal(map[string]interface{}{"queryId": query.QueryID,
|
|
1744
|
+
"truthStatus": status, "evaluationStatus": evaluation, "judgmentRequests": requests, "issues": ev.issues})
|
|
1745
|
+
return string(out)
|
|
1746
|
+
}
|
|
1747
|
+
// Ключ атома у оракула §208 включает ТИП значения: запрос с Integer к
|
|
1748
|
+
// утверждению Decimal той же величины атома не находит — NEITHER без issue.
|
|
1749
|
+
hit := false
|
|
1750
|
+
rows := [][]Val{}
|
|
1751
|
+
want := []Val{}
|
|
1752
|
+
if ev.queryTyped(query.Literal) {
|
|
1753
|
+
rows = ev.m.TABLES[query.Literal.Predicate]
|
|
1754
|
+
want = make([]Val, len(query.Literal.Args))
|
|
1755
|
+
for i, x := range query.Literal.Args {
|
|
1756
|
+
want[i] = ev.toVal(x)
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
for _, row := range rows {
|
|
1760
|
+
if len(row) != len(want) {
|
|
1761
|
+
continue
|
|
1762
|
+
}
|
|
1763
|
+
all := true
|
|
1764
|
+
for i := range row {
|
|
1765
|
+
if !eq(row[i], want[i]) {
|
|
1766
|
+
all = false
|
|
1767
|
+
break
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
if all {
|
|
1771
|
+
hit = true
|
|
1772
|
+
break
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
// Открытый мир: отсутствие строки — это NEITHER, а не FALSE_ONLY.
|
|
1776
|
+
// §47.3 (DECISION-0274): судьи у модели L0 — тот же обход по backchain.
|
|
1777
|
+
status := "NEITHER"
|
|
1778
|
+
if hit {
|
|
1779
|
+
status = "TRUE_ONLY"
|
|
1780
|
+
}
|
|
1781
|
+
requests := ev.judgmentRequestsFor(status, false)
|
|
1782
|
+
evaluation := "COMPUTED"
|
|
1783
|
+
if ev.keyConflictBlocks(query.Literal.Predicate) {
|
|
1784
|
+
evaluation = "NON_EXECUTABLE"
|
|
1785
|
+
} else if len(requests) > 0 {
|
|
1786
|
+
evaluation = "REQUIRES_JUDGMENT"
|
|
1787
|
+
} else if st := ev.ruleErrorStatus(query.Literal.Predicate, status); st != "" {
|
|
1788
|
+
evaluation = st
|
|
1789
|
+
}
|
|
1790
|
+
if requests == nil {
|
|
1791
|
+
requests = []map[string]interface{}{}
|
|
1792
|
+
}
|
|
1793
|
+
out, _ := json.Marshal(map[string]interface{}{"queryId": query.QueryID,
|
|
1794
|
+
"truthStatus": status, "evaluationStatus": evaluation, "judgmentRequests": requests, "issues": ev.issues})
|
|
1795
|
+
return string(out)
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// Внутренний вид API `rows` (DECISION-0423 §2.1): установленные строки вопроса
|
|
1799
|
+
// литерал-термами §53 в порядке канонических байтов; статус вычисления — как у
|
|
1800
|
+
// вопроса о том же отношении (конфликт ключа блокирует читателей, E-0242).
|
|
1801
|
+
func (ev *evaluation) rowsJSON(pred, qid, issuesJSON string) string {
|
|
1802
|
+
if _, ok := ev.m.PARAMS[pred]; !ok {
|
|
1803
|
+
refuse("предикат rows не объявлен в пакете: " + pred)
|
|
1804
|
+
}
|
|
1805
|
+
var src [][]Val
|
|
1806
|
+
if LAYER == "L1" {
|
|
1807
|
+
src = ev.m.establishedRows(pred)
|
|
1808
|
+
} else {
|
|
1809
|
+
src = ev.m.TABLES[pred]
|
|
1810
|
+
}
|
|
1811
|
+
rows := []string{}
|
|
1812
|
+
for _, r := range src {
|
|
1813
|
+
cells := make([]string, len(r))
|
|
1814
|
+
for col, v := range r {
|
|
1815
|
+
cells[col] = literalOf(ev.slotTyped(pred, col, v))
|
|
1816
|
+
}
|
|
1817
|
+
rows = append(rows, "["+strings.Join(cells, ",")+"]")
|
|
1818
|
+
}
|
|
1819
|
+
sort.Strings(rows)
|
|
1820
|
+
evaluation := "COMPUTED"
|
|
1821
|
+
if ev.keyConflictBlocks(pred) {
|
|
1822
|
+
evaluation = "NON_EXECUTABLE"
|
|
1823
|
+
}
|
|
1824
|
+
return `{"queryId":` + qid + `,"resultKind":"COLLECTION","rows":[` + strings.Join(rows, ",") +
|
|
1825
|
+
`],"evaluationStatus":"` + evaluation + `","judgmentRequests":[],"issues":` + issuesJSON + `}`
|
|
1826
|
+
}
|