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,1806 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Эмиттер Go. План тот же; отличаются синтаксис и приведение типов."""
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
CAST = {"money": "Money", "dec": "Dec", "text": "string", "bool": "bool", "date": "Date", "enum": "Sym",
|
|
7
|
+
"rat": "Rational", "qty": "Qty", "inst": "Instant"}
|
|
8
|
+
|
|
9
|
+
# Версия плана, которую этот эмиттер умеет печатать. Другая версия — отказ:
|
|
10
|
+
# незнакомое поле плана печаталось бы молча, а это другой ответ, не меньший.
|
|
11
|
+
SUPPORTED_PLAN = "law-backend-plan/0.7"
|
|
12
|
+
# План поражаемого слоя (DECISION-0237) — отдельная версия со своей печатью.
|
|
13
|
+
SUPPORTED_PLAN_L1 = "law-backend-plan/0.8"
|
|
14
|
+
# План с агрегатами §59 в правилах и замыканиями §70 (DECISION-0239).
|
|
15
|
+
SUPPORTED_PLAN_09 = "law-backend-plan/0.9"
|
|
16
|
+
# План с нормативными позициями L2 (DECISION-0242).
|
|
17
|
+
SUPPORTED_PLAN_10 = "law-backend-plan/0.10"
|
|
18
|
+
# План с календарными функциями §84–§86 (DECISION-0245).
|
|
19
|
+
SUPPORTED_PLAN_11 = "law-backend-plan/0.11"
|
|
20
|
+
# План с Rational §35/§58 (DECISION-0252).
|
|
21
|
+
SUPPORTED_PLAN_12 = "law-backend-plan/0.12"
|
|
22
|
+
# План с Quantity §49 v1 и счётчиком-связывателем §86.1 (DECISION-0256).
|
|
23
|
+
SUPPORTED_PLAN_13 = "law-backend-plan/0.13"
|
|
24
|
+
# План с политикой округления §50 (DECISION-0259).
|
|
25
|
+
SUPPORTED_PLAN_14 = "law-backend-plan/0.14"
|
|
26
|
+
# План с Boolean/Natural, границами месяца §84.2 и `_` §98 (DECISION-0266–0268).
|
|
27
|
+
SUPPORTED_PLAN_15 = "law-backend-plan/0.15"
|
|
28
|
+
# План с датированными приоритетами §92.3 (DECISION-0269).
|
|
29
|
+
SUPPORTED_PLAN_16 = "law-backend-plan/0.16"
|
|
30
|
+
# План с вычисляемым аргументом атома тела и условным термом §56 (DECISION-0270).
|
|
31
|
+
SUPPORTED_PLAN_17 = "law-backend-plan/0.17"
|
|
32
|
+
# План 0.18 (DECISION-0273): функции §46, параметры A3, `let`, `match`.
|
|
33
|
+
SUPPORTED_PLAN_18 = "law-backend-plan/0.18"
|
|
34
|
+
# План 0.19 (DECISION-0274): канал суждения §47.3.
|
|
35
|
+
SUPPORTED_PLAN_19 = "law-backend-plan/0.19"
|
|
36
|
+
# Возможности плана, которые цель печатает; поставка сверяет их до эмиссии.
|
|
37
|
+
CAPABILITIES = {"admission:current", "defeasible", "negation", "priority", "status:not_known", "status:unknown",
|
|
38
|
+
"function", "param:a3", "term:let", "term:match", "judgment",
|
|
39
|
+
"status:refuted", "status:monotone", "status:supported", "aggregate", "closure",
|
|
40
|
+
"calendar:days_between", "calendar:period", "calendar:business", "value:rational",
|
|
41
|
+
"value:quantity", "value:round", "value:div_round",
|
|
42
|
+
"value:boolean", "value:natural", "calendar:month_bound", "wildcard", "priority:dated", "term:body", "term:if",
|
|
43
|
+
"value:instant"}
|
|
44
|
+
PHASES = {"strict", "closure", "candidates", "defeat", "surviving", "status", "norms", "effects"}
|
|
45
|
+
# Поля фаз расписания: незнакомое поле печаталось бы молча. `waiting` —
|
|
46
|
+
# правила, ждущие замыкания §70 (errata E-0174).
|
|
47
|
+
PHASE_FIELDS = {"strict": {"phase", "waiting"}, "closure": {"phase", "policies"},
|
|
48
|
+
"candidates": {"phase", "stratum", "rules"}, "defeat": {"phase", "stratum"},
|
|
49
|
+
"surviving": {"phase", "stratum"}, "status": {"phase", "stratum"}}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_plan(plan: dict) -> None:
|
|
53
|
+
if "norms" in plan or "norm" in (plan.get("capabilities") or []):
|
|
54
|
+
raise ValueError("NORM_RANKS_UNAVAILABLE: norm runtime requires current ranks (W6)")
|
|
55
|
+
v = plan.get("basePlanVersion") if plan.get("planVersion") == "law-backend-plan/0.20" else plan.get("planVersion")
|
|
56
|
+
if v not in (SUPPORTED_PLAN, SUPPORTED_PLAN_L1, SUPPORTED_PLAN_09, SUPPORTED_PLAN_10, SUPPORTED_PLAN_11,
|
|
57
|
+
SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19):
|
|
58
|
+
raise ValueError(f"версия плана не поддержана эмиттером: {v!r} (поддержаны {SUPPORTED_PLAN}, "
|
|
59
|
+
f"{SUPPORTED_PLAN_L1}, {SUPPORTED_PLAN_09}, {SUPPORTED_PLAN_10}, {SUPPORTED_PLAN_11}, "
|
|
60
|
+
f"{SUPPORTED_PLAN_12}, {SUPPORTED_PLAN_13}, {SUPPORTED_PLAN_14}, {SUPPORTED_PLAN_15}, {SUPPORTED_PLAN_16}, {SUPPORTED_PLAN_17}, {SUPPORTED_PLAN_18}, {SUPPORTED_PLAN_19})")
|
|
61
|
+
missing = sorted(set(plan.get("capabilities") or ()) - CAPABILITIES)
|
|
62
|
+
if missing:
|
|
63
|
+
raise ValueError(f"возможности плана не поддержаны эмиттером: {', '.join(missing)}")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _is_l1(plan: dict) -> bool:
|
|
67
|
+
"""Модель L1: у плана 0.11 — по расписанию фаз (DECISION-0245 п. 1)."""
|
|
68
|
+
return plan["planVersion"] in (SUPPORTED_PLAN_L1, SUPPORTED_PLAN_09, SUPPORTED_PLAN_10) or \
|
|
69
|
+
(plan["planVersion"] in (SUPPORTED_PLAN_11, SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19) and "schedule" in plan)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _is_09(plan: dict) -> bool:
|
|
73
|
+
return plan["planVersion"] in (SUPPORTED_PLAN_09, SUPPORTED_PLAN_10) or \
|
|
74
|
+
(plan["planVersion"] in (SUPPORTED_PLAN_11, SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19) and "aggregates" in plan)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _judgments(L: list, plan: dict, l1: bool) -> None:
|
|
78
|
+
"""Отношения суждения §47.3 (DECISION-0274): орган и форма запроса, и доступы
|
|
79
|
+
модели для обхода запроса судье в рантайме (применения страт, опоры,
|
|
80
|
+
приоритеты — у L1; у L0 — заглушки)."""
|
|
81
|
+
L.append("// Отношения суждения §47.3 (DECISION-0274): орган и форма запроса.")
|
|
82
|
+
L.append("var JUDGMENTS = map[string]judgmentDecl{")
|
|
83
|
+
for pred, d in sorted((plan.get("judgments") or {}).items()):
|
|
84
|
+
schema = ("planJSON(" + json.dumps(json.dumps(d["requestSchema"], ensure_ascii=False)) + ")"
|
|
85
|
+
if d.get("requestSchema") is not None else "nil")
|
|
86
|
+
L.append(f"\t{json.dumps(pred)}: {{{json.dumps(d['authority'])}, {schema}}},")
|
|
87
|
+
L.append("}")
|
|
88
|
+
L.append("")
|
|
89
|
+
L.append("func judgmentDecls() map[string]judgmentDecl { return JUDGMENTS }")
|
|
90
|
+
L.append("")
|
|
91
|
+
L.append("// Доступы модели для обхода запроса судье в рантайме (DECISION-0274).")
|
|
92
|
+
L.append("func judgmentModel() jModel {")
|
|
93
|
+
L.append("\treturn jModel{truthOf, func() map[string][][]Val { return TABLES },")
|
|
94
|
+
L.append("\t\tfunc() ([]interface{}, map[string]interface{}) { r, c, _, _ := backchainEnv(); return r, c },")
|
|
95
|
+
L.append("\t\tjudgmentDecls, appliedCands, supportBits, prioHas, judgmentPositions, blockedImmunity, legalTime}")
|
|
96
|
+
L.append("}")
|
|
97
|
+
L.append("")
|
|
98
|
+
if not l1:
|
|
99
|
+
L.append("func appliedCands() []appliedCand { return nil }")
|
|
100
|
+
L.append("")
|
|
101
|
+
L.append("func supportBits(urn string, row []Val) int { return 0 }")
|
|
102
|
+
L.append("")
|
|
103
|
+
L.append("func prioHas(a, b string) bool { return false }")
|
|
104
|
+
L.append("")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _functions(L: list, plan: dict) -> None:
|
|
108
|
+
"""Функции §46 и параметры A3 (DECISION-0273). Функции печатаются
|
|
109
|
+
функциями цели по позиционным параметрам и доступны раннеру для
|
|
110
|
+
вопросов `term`; параметр A3 — значение на legalTime из prepare."""
|
|
111
|
+
L.append("// Функции §46 (DECISION-0273): чистые тела по позиционным параметрам.")
|
|
112
|
+
for f in plan.get("functions") or []:
|
|
113
|
+
params = ", ".join(f"a{i} Val" for i in range(len(f["params"])))
|
|
114
|
+
L.append(f'func f_{f["name"]}({params}) Val {{ return {term(f["body"])} }}')
|
|
115
|
+
L.append("")
|
|
116
|
+
L.append("type funcDecl struct {")
|
|
117
|
+
L.append("\tParams []string")
|
|
118
|
+
L.append("\tReturns string")
|
|
119
|
+
L.append("\tFn func([]Val) Val")
|
|
120
|
+
L.append("}")
|
|
121
|
+
L.append("")
|
|
122
|
+
L.append("var FUNCTIONS = map[string]funcDecl{")
|
|
123
|
+
for f in plan.get("functions") or []:
|
|
124
|
+
call = ", ".join(f"a[{i}]" for i in range(len(f["params"])))
|
|
125
|
+
params = "[]string{" + ", ".join(json.dumps(x) for x in f["paramTypes"]) + "}"
|
|
126
|
+
L.append(f'\t{json.dumps(f["id"])}: {{{params}, {json.dumps(f["returnType"])}, '
|
|
127
|
+
f'func(a []Val) Val {{ return f_{f["name"]}({call}) }}}},')
|
|
128
|
+
L.append("}")
|
|
129
|
+
L.append("")
|
|
130
|
+
L.append("// Параметры A3 §A3.2: окна [start, end) оси права со значением (пустая строка —")
|
|
131
|
+
L.append("// бесконечность); активное значение готовит prepare по legalTime, разрыв датасета —")
|
|
132
|
+
L.append("// MISSING_PARAMETER_VALUE правила.")
|
|
133
|
+
L.append("var A3_TYPES = map[string]string{" + ", ".join(
|
|
134
|
+
f'{json.dumps(p["id"])}: {json.dumps(p["type"])}' for p in plan.get("a3Params") or []) + "}")
|
|
135
|
+
if plan.get("a3Params"):
|
|
136
|
+
L.append("type a3Window struct {")
|
|
137
|
+
L.append("\tstart, end string")
|
|
138
|
+
L.append("\tvalue Val")
|
|
139
|
+
L.append("}")
|
|
140
|
+
L.append("var a3Windows = map[string][]a3Window{")
|
|
141
|
+
for p in plan["a3Params"]:
|
|
142
|
+
ws = ", ".join(f'{{{json.dumps(s or "")}, {json.dumps(e or "")}, {term(v)}}}' for s, e, v in p["windows"])
|
|
143
|
+
L.append(f'\t{json.dumps(p["id"])}: {{{ws}}},')
|
|
144
|
+
L.append("}")
|
|
145
|
+
L.append("var a3Active = map[string]Val{}")
|
|
146
|
+
L.append("")
|
|
147
|
+
L.append("func a3(id string) Val {")
|
|
148
|
+
L.append("\tv, ok := a3Active[id]")
|
|
149
|
+
L.append("\tif !ok {")
|
|
150
|
+
L.append('\t\tpanic(termError{"MISSING_PARAMETER_VALUE"})')
|
|
151
|
+
L.append("\t}")
|
|
152
|
+
L.append("\treturn v")
|
|
153
|
+
L.append("}")
|
|
154
|
+
L.append("")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _calendar_env(L: list, plan: dict) -> None:
|
|
158
|
+
"""Окружение календаря (DECISION-0245 п. 3–4) и обход E-0105; у модели
|
|
159
|
+
без календаря — заглушки для общего раннера."""
|
|
160
|
+
if "calendars" in plan:
|
|
161
|
+
L.append("// §85/§86 (DECISION-0245): версия семантики, политики и снимки программы.")
|
|
162
|
+
L.append(f'var CAL_POLICIES = planJSON({json.dumps(json.dumps(plan["deadlinePolicies"]))}).(map[string]interface{{}})')
|
|
163
|
+
L.append(f'var CAL_SNAPSHOTS = planSnapshots({json.dumps(json.dumps(plan["calendars"]))})')
|
|
164
|
+
L.append(f'func calendarEnv() (string, map[string]interface{{}}, []Snapshot, bool) {{ '
|
|
165
|
+
f'return {json.dumps(plan["semanticVersion"])}, CAL_POLICIES, CAL_SNAPSHOTS, true }}')
|
|
166
|
+
else:
|
|
167
|
+
L.append("func calendarEnv() (string, map[string]interface{}, []Snapshot, bool) { return \"\", nil, nil, false }")
|
|
168
|
+
if "backchain" in plan and not _is_09(plan):
|
|
169
|
+
L.append("var issueSeen = map[string]bool{}")
|
|
170
|
+
if "backchain" in plan:
|
|
171
|
+
L.append("// E-0105: правила для обхода ошибок терма от цели вопроса.")
|
|
172
|
+
L.append(f'var BACKCHAIN = planJSON({json.dumps(json.dumps(plan["backchain"]))}).([]interface{{}})')
|
|
173
|
+
L.append(f'var BACKCHAIN_CONSTS = planJSON({json.dumps(json.dumps(plan["backchainConsts"]))}).(map[string]interface{{}})')
|
|
174
|
+
L.append("func backchainEnv() ([]interface{}, map[string]interface{}, map[string]bool, bool) { "
|
|
175
|
+
"return BACKCHAIN, BACKCHAIN_CONSTS, issueSeen, true }")
|
|
176
|
+
else:
|
|
177
|
+
L.append("func backchainEnv() ([]interface{}, map[string]interface{}, map[string]bool, bool) { "
|
|
178
|
+
"return nil, nil, nil, false }")
|
|
179
|
+
L.append("")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def term(t: dict) -> str:
|
|
183
|
+
k = t["k"]
|
|
184
|
+
if k == "qty":
|
|
185
|
+
# Счётчик календарной функции (DECISION-0245 п. 2): число и единица.
|
|
186
|
+
return f'{t["v"]}, {json.dumps(t["unit"])}'
|
|
187
|
+
|
|
188
|
+
if k == "var":
|
|
189
|
+
# Ячейка вида text не приводится: в слот сущности дело вправе подать
|
|
190
|
+
# const_ref без объявления (`Sym`), и приведение `.(string)` роняло бы
|
|
191
|
+
# раннер паникой; функции рантайма принимают Val.
|
|
192
|
+
if t["t"] == "text":
|
|
193
|
+
return f'r{t["row"]}[{t["col"]}]'
|
|
194
|
+
return f'r{t["row"]}[{t["col"]}].({CAST[t["t"]]})'
|
|
195
|
+
if k == "lit":
|
|
196
|
+
if t["t"] == "money":
|
|
197
|
+
return f'money("{t["v"]}", "{t["cur"]}")'
|
|
198
|
+
if t["t"] == "dec":
|
|
199
|
+
return f'dec("{t["v"]}")'
|
|
200
|
+
if t["t"] == "date":
|
|
201
|
+
return f'date("{t["v"]}")'
|
|
202
|
+
if t["t"] == "inst":
|
|
203
|
+
return f'instant("{t["v"]}")'
|
|
204
|
+
if t["t"] == "enum":
|
|
205
|
+
return f'sym("{t["v"]}")'
|
|
206
|
+
if t["t"] == "rat":
|
|
207
|
+
return f'rat("{t["v"]}")'
|
|
208
|
+
if t["t"] == "qty":
|
|
209
|
+
return f'qty("{t["v"]}", {json.dumps(t["unit"], ensure_ascii=False)})'
|
|
210
|
+
return json.dumps(t["v"], ensure_ascii=False)
|
|
211
|
+
if k == "bin":
|
|
212
|
+
if "lk" in t:
|
|
213
|
+
# DECISION-0252 п. 3: пара §58 решается статическими видами операндов.
|
|
214
|
+
return f'arith("{t["op"]}", {term(t["l"])}, {term(t["r"])}, "{t["lk"]}", "{t["rk"]}")'
|
|
215
|
+
return f'{t["op"]}({term(t["l"])}, {term(t["r"])})'
|
|
216
|
+
if k == "call":
|
|
217
|
+
if any(a["k"] == "qtyvar" for a in t["args"]):
|
|
218
|
+
return _counter_call(t)
|
|
219
|
+
if "ak" in t:
|
|
220
|
+
# DECISION-0259 п. 2: политика §50 проверяется рантаймом; виды
|
|
221
|
+
# значения и точности — из плана (последний вид — Text режима).
|
|
222
|
+
name = "roundMode" if t["fn"] == "round" else "divRound"
|
|
223
|
+
return (f'{name}(' + ", ".join(term(a) for a in t["args"]) + ", "
|
|
224
|
+
+ ", ".join(json.dumps(c) for c in t["ak"][:-1]) + ")")
|
|
225
|
+
return f'{t["fn"]}(' + ", ".join(term(a) for a in t["args"]) + ")"
|
|
226
|
+
if k == "param":
|
|
227
|
+
return f'p{t["i"]}' if t["t"] == "text" else f'p{t["i"]}.({CAST[t["t"]]})'
|
|
228
|
+
if k == "fvar":
|
|
229
|
+
# DECISION-0273: параметр функции §46 — позиционный аргумент.
|
|
230
|
+
return f'a{t["i"]}' if t["t"] == "text" else f'a{t["i"]}.({CAST[t["t"]]})'
|
|
231
|
+
if k == "lvar":
|
|
232
|
+
return f'l{t["i"]}' if t["t"] == "text" else f'l{t["i"]}.({CAST[t["t"]]})'
|
|
233
|
+
if k == "fcall":
|
|
234
|
+
return f'f_{t["fn"]}(' + ", ".join(term(a) for a in t["args"]) + ")"
|
|
235
|
+
if k == "a3":
|
|
236
|
+
# Параметр A3: значение на legalTime готовит prepare; разрыв — termError.
|
|
237
|
+
return f'a3({json.dumps(t["id"])})'
|
|
238
|
+
if k == "let":
|
|
239
|
+
# §55: значение считается один раз, тело — с локальным связыванием.
|
|
240
|
+
return f'func(l{t["i"]} Val) Val {{ return {term(t["b"])} }}({term(t["v"])})'
|
|
241
|
+
if k == "match":
|
|
242
|
+
# §55.1: первая совпавшая ветвь; ни одна — NON_EXHAUSTIVE_MATCH.
|
|
243
|
+
parts = [f'func() Val {{ s := {term(t["s"])}; _ = s']
|
|
244
|
+
for arm in t["arms"]:
|
|
245
|
+
if arm["m"] is None:
|
|
246
|
+
parts.append(f'return {term(arm["v"])}')
|
|
247
|
+
break
|
|
248
|
+
parts.append(f'if eq(s, sym({json.dumps(arm["m"])})) {{ return {term(arm["v"])} }}')
|
|
249
|
+
else:
|
|
250
|
+
parts.append('panic(termError{"NON_EXHAUSTIVE_MATCH"})')
|
|
251
|
+
return "; ".join(parts) + " }()"
|
|
252
|
+
if k == "agg":
|
|
253
|
+
return f'{t["fn"]}(' + ", ".join(term(a) for a in t["args"]) + ")"
|
|
254
|
+
if k == "if":
|
|
255
|
+
# §56 (DECISION-0270): ленивая ветвь — замыкание, у Go тернарного оператора нет.
|
|
256
|
+
return f'func() Val {{ if {cond(t["c"])} {{ return {term(t["t"])} }}; return {term(t["e"])} }}()'
|
|
257
|
+
raise AssertionError(k)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def cond(c: dict) -> str:
|
|
261
|
+
"""Data-условие §56: boolean, сравнение, and, or."""
|
|
262
|
+
k = c["k"]
|
|
263
|
+
if k == "bool":
|
|
264
|
+
return "true" if c["v"] else "false"
|
|
265
|
+
if k == "cmp":
|
|
266
|
+
return f'cmp({term(c["l"])}, "{c["op"]}", {term(c["r"])})'
|
|
267
|
+
if k in ("and", "or"):
|
|
268
|
+
op = " && " if k == "and" else " || "
|
|
269
|
+
return "(" + op.join(cond(x) for x in c["items"]) + ")"
|
|
270
|
+
raise AssertionError(k)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _key_scope(L: list, plan: dict) -> None:
|
|
274
|
+
readers: dict[str, set[str]] = {}
|
|
275
|
+
for rule in plan["rules"]:
|
|
276
|
+
for atom in rule.get("atoms") or []:
|
|
277
|
+
readers.setdefault(atom["pred"], set()).add(rule["head"]["pred"])
|
|
278
|
+
for check in rule.get("checks") or []:
|
|
279
|
+
readers.setdefault(check["pred"], set()).add(rule["head"]["pred"])
|
|
280
|
+
L.append("// E-0210/E-0242: forward readers of a keyed relation.")
|
|
281
|
+
L.append("var KEY_READERS = map[string][]string{")
|
|
282
|
+
for source, consumers in sorted(readers.items()):
|
|
283
|
+
L.append(f'\t{json.dumps(source)}: {{' + ", ".join(json.dumps(value) for value in sorted(consumers)) + "},")
|
|
284
|
+
L.append("}")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _counter_call(t: dict) -> str:
|
|
288
|
+
"""Счётчик-связыватель §86.1 (DECISION-0256 п. 6): `counter` рантайма
|
|
289
|
+
возвращает число и единицу, а Go не раскрывает пару среди других
|
|
290
|
+
аргументов — вызов оборачивается замыканием. Порядок вычисления прежний:
|
|
291
|
+
аргументы слева направо, проверка счётчика — на его месте. Ячейка и
|
|
292
|
+
параметр не приводятся к Qty: вид проверяет `counter` (TYPE_ERROR)."""
|
|
293
|
+
out, args = [], []
|
|
294
|
+
for i, a in enumerate(t["args"]):
|
|
295
|
+
if a["k"] == "qtyvar":
|
|
296
|
+
inner = a["term"]
|
|
297
|
+
if inner["k"] == "var":
|
|
298
|
+
src = f'r{inner["row"]}[{inner["col"]}]'
|
|
299
|
+
elif inner["k"] == "param":
|
|
300
|
+
src = f'p{inner["i"]}'
|
|
301
|
+
else:
|
|
302
|
+
src = term(inner)
|
|
303
|
+
out.append(f'qn{i}, qu{i} := counter({src}, {json.dumps(a["fn"])})')
|
|
304
|
+
args += [f"qn{i}", f"qu{i}"]
|
|
305
|
+
else:
|
|
306
|
+
out.append(f"qa{i} := {term(a)}")
|
|
307
|
+
args.append(f"qa{i}")
|
|
308
|
+
return f'func() Date {{ {"; ".join(out)}; return {t["fn"]}({", ".join(args)}) }}()'
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# Виды ячеек, на которых `eq` рантайма не отказывает: проверку соединения
|
|
312
|
+
# можно поставить на уровень атома, который её связывает, — ответ и отказы
|
|
313
|
+
# прежние, а декартово произведение не строится (у `algo.*` шесть атомов
|
|
314
|
+
# тела давали минуты на пустом деле). Соединение над деньгами отказывает на
|
|
315
|
+
# разных валютах, поэтому при нём правило печатается прежним порядком.
|
|
316
|
+
_EARLY_KINDS = {"text", "enum", "date", "dec"}
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _early(r: dict) -> tuple[dict, list, list]:
|
|
320
|
+
"""Проверки по уровням атомов `{i: [(вид, проверка)]}` и оставшиеся во
|
|
321
|
+
внутреннем цикле соединения и фильтры (порядок печати прежний)."""
|
|
322
|
+
joins, filters = r["joins"], r["filters"]
|
|
323
|
+
if any(at not in _EARLY_KINDS or bt not in _EARLY_KINDS for (_ai, _aj, at), (_bi, _bj, bt) in joins):
|
|
324
|
+
return {}, joins, filters
|
|
325
|
+
levels: dict = {}
|
|
326
|
+
for j in joins:
|
|
327
|
+
levels.setdefault(j[1][0], []).append(("join", j))
|
|
328
|
+
for f in filters:
|
|
329
|
+
levels.setdefault(f["row"], []).append(("filter", f))
|
|
330
|
+
return levels, [], []
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _go_wild_open(L: list, ind: str, i: int, a: dict) -> None:
|
|
334
|
+
"""§98 (DECISION-0268): множество проекций строк атома с `_` — на уровне
|
|
335
|
+
цикла, то есть заново на каждой подстановке внешних атомов."""
|
|
336
|
+
if a.get("wild"):
|
|
337
|
+
L.append(f'{ind}w{i} := map[string]bool{{}}')
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _go_wild(L: list, ind: str, i: int, a: dict) -> None:
|
|
341
|
+
"""Строки, различающиеся только в столбцах `_`, дают одну подстановку."""
|
|
342
|
+
if a.get("wild"):
|
|
343
|
+
cells = ", ".join(f"r{i}[{c}]" for c in a["keep"])
|
|
344
|
+
L.append(f'{ind}wk{i} := key([]Val{{{cells}}})')
|
|
345
|
+
_block(L, ind, f'if w{i}[wk{i}] {{@continue@}}')
|
|
346
|
+
L.append(f'{ind}w{i}[wk{i}] = true')
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _go_checks(checks: list) -> list:
|
|
350
|
+
out = []
|
|
351
|
+
for kind, c in checks:
|
|
352
|
+
if kind == "join":
|
|
353
|
+
(ai, aj, _at), (bi, bj, _bt) = c
|
|
354
|
+
out.append(f'if !cmp(r{ai}[{aj}], "eq", r{bi}[{bj}]) {{@continue@}}')
|
|
355
|
+
else:
|
|
356
|
+
out.append(f'if !eq(r{c["row"]}[{c["col"]}], {term(c["term"])}) {{@continue@}}')
|
|
357
|
+
return out
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
# Хуки экземпляра модели для входа `evaluate` (DECISION-0423 §2.3): поля
|
|
361
|
+
# структуры `Model` в evaluate.go — те же имена и типы.
|
|
362
|
+
GO_HOOKS = ("NODE_IDS", "TABLES", "PARAMS", "CONSTS", "BASES", "FUNCTIONS", "A3_TYPES", "KEY_READERS", "CARRIER_EDGES",
|
|
363
|
+
"saturate", "register", "registerNegative", "packageFacts", "keyConflicts", "keyConflictPredicates",
|
|
364
|
+
"truthOf", "establishedRows", "ruleIssues", "normIssues", "normPositions", "blockedEffect",
|
|
365
|
+
"calendarEnv", "backchainEnv", "judgmentModel", "setStatusTime")
|
|
366
|
+
# Функции рантайма, читающие контекст вычисления: в фабрике их затеняют
|
|
367
|
+
# одноимённые замыкания над контекстом экземпляра.
|
|
368
|
+
GO_CONTEXT_FUNCS = ("legalTime", "add_calendar_period", "add_business_days", "add_legal_term")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _go_body_brace(line: str, start: int) -> int:
|
|
372
|
+
"""Позиция `{`, открывающей тело функции, после списка параметров,
|
|
373
|
+
начинающегося в `start`: первая ` {` на нулевой глубине скобок (тип
|
|
374
|
+
результата вроде `map[string]interface{}` пробела перед `{` не несёт)."""
|
|
375
|
+
depth, i = 0, start
|
|
376
|
+
while i < len(line):
|
|
377
|
+
ch = line[i]
|
|
378
|
+
if ch == '"':
|
|
379
|
+
i += 1
|
|
380
|
+
while line[i] != '"':
|
|
381
|
+
i += 2 if line[i] == "\\" else 1
|
|
382
|
+
elif ch in "([":
|
|
383
|
+
depth += 1
|
|
384
|
+
elif ch in ")]":
|
|
385
|
+
depth -= 1
|
|
386
|
+
elif ch == "{" and depth == 0 and line[i - 1] == " ":
|
|
387
|
+
return i
|
|
388
|
+
elif ch == "{":
|
|
389
|
+
depth += 1
|
|
390
|
+
elif ch == "}":
|
|
391
|
+
depth -= 1
|
|
392
|
+
i += 1
|
|
393
|
+
raise ValueError("сигнатура функции модели без тела: " + line[:80])
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _go_split_sig(sig: str) -> tuple[str, str]:
|
|
397
|
+
"""`(параметры) результат` → (параметры со скобками, результат)."""
|
|
398
|
+
depth = 0
|
|
399
|
+
for i, ch in enumerate(sig):
|
|
400
|
+
depth += ch == "("
|
|
401
|
+
depth -= ch == ")"
|
|
402
|
+
if depth == 0:
|
|
403
|
+
return sig[:i + 1], sig[i + 1:].strip()
|
|
404
|
+
raise ValueError("параметры функции модели без закрывающей скобки: " + sig[:80])
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _factory(src: str, plan: dict) -> str:
|
|
408
|
+
"""Модель — фабрика состояния на вызов (DECISION-0423 §2.3). `package`,
|
|
409
|
+
импорты, `const` и `type` остаются на уровне пакета; переменные и функции
|
|
410
|
+
модели становятся локальными переменными и замыканиями `newModel()`, тела
|
|
411
|
+
правил не переписываются. Порядок внутри фабрики: контекст вычисления и
|
|
412
|
+
затеняющие замыкания рантайма, объявления функций, переменные в порядке
|
|
413
|
+
текста (переменная, чей инициализатор зовёт функцию модели, получает тип
|
|
414
|
+
сразу, а значение — после замыканий), замыкания, отложенные значения, тела
|
|
415
|
+
`init`. Возвращается `*Model` с хуками для `evaluate`. Правило раскладки
|
|
416
|
+
эмиттера: объявление верхнего уровня начинается с нулевой колонки."""
|
|
417
|
+
src += "var NODE_IDS = map[string]bool{" + ", ".join(json.dumps(x) + ": true" for x in (plan.get("admission") or {}).get("reservedAssertionIds", [])) + "}\n"
|
|
418
|
+
lines = src.rstrip("\n").split("\n")
|
|
419
|
+
head, top, segs = [], [], []
|
|
420
|
+
cur = None
|
|
421
|
+
for ln in lines:
|
|
422
|
+
if ln.startswith(("func ", "var ", "const ", "type ", "import ")):
|
|
423
|
+
cur = [ln]
|
|
424
|
+
segs.append(cur)
|
|
425
|
+
elif cur is None:
|
|
426
|
+
head.append(ln)
|
|
427
|
+
else:
|
|
428
|
+
cur.append(ln)
|
|
429
|
+
for seg in segs:
|
|
430
|
+
for ln in seg:
|
|
431
|
+
if "`" in ln.split("//")[0]:
|
|
432
|
+
raise ValueError("сырая строка в модели: фабрика сдвигает отступ — " + ln[:80])
|
|
433
|
+
funcs = {}
|
|
434
|
+
for seg in segs:
|
|
435
|
+
if seg[0].startswith("func ") and not seg[0].startswith("func init()"):
|
|
436
|
+
name = seg[0][5:seg[0].index("(")]
|
|
437
|
+
funcs[name] = seg
|
|
438
|
+
sigs = {}
|
|
439
|
+
for name, seg in funcs.items():
|
|
440
|
+
start = 5 + len(name)
|
|
441
|
+
brace = _go_body_brace(seg[0], start)
|
|
442
|
+
sigs[name] = seg[0][start:brace].rstrip()
|
|
443
|
+
def ref(text, name):
|
|
444
|
+
# Embedded JSON and comments contain names such as "status" too.
|
|
445
|
+
# Only Go identifiers can depend on a model closure.
|
|
446
|
+
code = re.sub(r'"(?:\\.|[^"\\])*"|//[^\n]*|/\*.*?\*/', "", text, flags=re.S)
|
|
447
|
+
return re.search(r"(?<![\w.])" + re.escape(name) + r"\b", code) is not None
|
|
448
|
+
early, deferred_decl, deferred_set, assigns, inits, names = [], [], [], [], [], []
|
|
449
|
+
for seg in segs:
|
|
450
|
+
first = seg[0]
|
|
451
|
+
if first.startswith("import "):
|
|
452
|
+
head.extend(seg)
|
|
453
|
+
elif first.startswith(("const ", "type ")):
|
|
454
|
+
top.extend(seg)
|
|
455
|
+
elif first.startswith("var "):
|
|
456
|
+
m = re.match(r"var ([A-Za-z_]\w*)(.*)$", first)
|
|
457
|
+
name, rest = m.group(1), m.group(2)
|
|
458
|
+
if name != "_":
|
|
459
|
+
names.append(name)
|
|
460
|
+
text = "\n".join([rest] + seg[1:])
|
|
461
|
+
called = [f for f in funcs if ref(text, f)]
|
|
462
|
+
if not called:
|
|
463
|
+
early.extend("\t" + ln if ln.strip() else "" for ln in seg)
|
|
464
|
+
continue
|
|
465
|
+
eq = rest.find("=")
|
|
466
|
+
typ = rest[:eq].strip() if eq > 0 else ""
|
|
467
|
+
init = (rest[eq + 1:] if eq >= 0 else "").strip()
|
|
468
|
+
if not typ:
|
|
469
|
+
cm = re.match(r"([\w\[\]\*\.]+(?:\([^)]*\))?(?:\s*[\w\[\]\*\.]+)*)\{", init)
|
|
470
|
+
call = re.match(r"([A-Za-z_]\w*)\(", init)
|
|
471
|
+
if call and call.group(1) in funcs:
|
|
472
|
+
typ = _go_split_sig(sigs[call.group(1)])[1]
|
|
473
|
+
elif cm:
|
|
474
|
+
typ = init[:init.index("{")].strip()
|
|
475
|
+
if not typ or typ.startswith("(") or "," in typ:
|
|
476
|
+
raise ValueError(f"тип отложенной переменной модели не выводится: {name}")
|
|
477
|
+
deferred_decl.append(f"\tvar {name} {typ}")
|
|
478
|
+
body = [f"\t{name} = {init}"] + ["\t" + ln if ln.strip() else "" for ln in seg[1:]]
|
|
479
|
+
deferred_set.extend(body)
|
|
480
|
+
elif first.startswith("func init()"):
|
|
481
|
+
# тело init — до закрывающей `}` в нулевой колонке; после неё в
|
|
482
|
+
# сегменте могут стоять комментарии следующего объявления
|
|
483
|
+
close = max((k for k, ln in enumerate(seg) if ln == "}"), default=None)
|
|
484
|
+
if close is None:
|
|
485
|
+
raise ValueError("тело init без закрывающей скобки в нулевой колонке")
|
|
486
|
+
inits.append("\t{")
|
|
487
|
+
inits.extend("\t" + ln if ln.strip() else "" for ln in seg[1:close + 1])
|
|
488
|
+
inits.extend("\t" + ln if ln.strip() else "" for ln in seg[close + 1:])
|
|
489
|
+
else:
|
|
490
|
+
name = first[5:first.index("(")]
|
|
491
|
+
start = 5 + len(name)
|
|
492
|
+
brace = _go_body_brace(first, start)
|
|
493
|
+
names.append(name)
|
|
494
|
+
assigns.append(f"\t{name} = func{sigs[name]} " + first[brace:])
|
|
495
|
+
assigns.extend("\t" + ln if ln.strip() else "" for ln in seg[1:])
|
|
496
|
+
used_ctx = [f for f in GO_CONTEXT_FUNCS if any(ref("\n".join(seg), f) for seg in segs)]
|
|
497
|
+
out = head + top + ["",
|
|
498
|
+
"// DECISION-0423 §2.3: новое состояние модели на одно вычисление.",
|
|
499
|
+
"func newModel() *Model {",
|
|
500
|
+
"\tctx := &evalContext{}"]
|
|
501
|
+
out += [f"\t{f} := ctx.{f}" for f in used_ctx]
|
|
502
|
+
out += [f"\tvar {name} func{sigs[name]}" for name in funcs]
|
|
503
|
+
out += early + deferred_decl + assigns + deferred_set + inits
|
|
504
|
+
out += [f"\t_ = {n}" for n in ["ctx"] + used_ctx + names]
|
|
505
|
+
hooks = [h for h in GO_HOOKS if h in names]
|
|
506
|
+
out.append("\treturn &Model{ctx: ctx, " + ", ".join(f"{h}: {h}" for h in hooks) + "}")
|
|
507
|
+
out.append("}")
|
|
508
|
+
return "\n".join(out) + "\n"
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def emit(plan: dict) -> str:
|
|
512
|
+
check_plan(plan)
|
|
513
|
+
if plan.get("planVersion") == "law-backend-plan/0.20":
|
|
514
|
+
plan = dict(plan, planVersion=plan["basePlanVersion"],
|
|
515
|
+
capabilities=[c for c in plan["capabilities"] if c != "admission:current"])
|
|
516
|
+
if _is_l1(plan):
|
|
517
|
+
return emit_l1(plan)
|
|
518
|
+
L = ["package main", "", "var TABLES = map[string][][]Val{"]
|
|
519
|
+
for t in plan["tables"]:
|
|
520
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{}},')
|
|
521
|
+
L.append("}")
|
|
522
|
+
_key_scope(L, plan)
|
|
523
|
+
L.append("")
|
|
524
|
+
# The shared runner accepts a decision time for L1 normative positions.
|
|
525
|
+
# L0 models have no positions, but export the same harmless hook so a
|
|
526
|
+
# runner may execute either plan family without a generated API gap.
|
|
527
|
+
L.append("// E-0212 §135.1: L0 has no positions; keep the runner API uniform.")
|
|
528
|
+
L.append("func setStatusTime(t string) {}")
|
|
529
|
+
L.append("")
|
|
530
|
+
L.append("// Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
|
|
531
|
+
L.append("var PARAMS = map[string][]string{")
|
|
532
|
+
for t in plan["tables"]:
|
|
533
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{' + ", ".join(json.dumps(x, ensure_ascii=False) for x in t["params"]) + "},")
|
|
534
|
+
L.append("}")
|
|
535
|
+
L.append("")
|
|
536
|
+
L.append("// Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
|
|
537
|
+
L.append("type constDecl struct {")
|
|
538
|
+
L.append("\tType string")
|
|
539
|
+
L.append("\tValue Val")
|
|
540
|
+
L.append("}")
|
|
541
|
+
L.append("")
|
|
542
|
+
L.append("var CONSTS = map[string]constDecl{")
|
|
543
|
+
for cid, c in plan["consts"].items():
|
|
544
|
+
L.append(f'\t{json.dumps(cid)}: {{{json.dumps(c["type"])}, {term(c["term"])}}},')
|
|
545
|
+
L.append("}")
|
|
546
|
+
L.append("")
|
|
547
|
+
L.append("// Наследование сущностей: подтип подходит параметру базового типа.")
|
|
548
|
+
_functions(L, plan)
|
|
549
|
+
_judgments(L, plan, l1=False)
|
|
550
|
+
_calendar_env(L, plan)
|
|
551
|
+
L.append("var BASES = map[string]string{")
|
|
552
|
+
for sub, base in plan["bases"].items():
|
|
553
|
+
L.append(f'\t{json.dumps(sub)}: {json.dumps(base)},')
|
|
554
|
+
L.append("}")
|
|
555
|
+
L.append("")
|
|
556
|
+
L.append("// Ключи отношений §45.1: позиции столбцов каждого ключа.")
|
|
557
|
+
L.append("var KEYS = map[string][][]int{")
|
|
558
|
+
for t in plan["tables"]:
|
|
559
|
+
if t["keys"]:
|
|
560
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{' + ", ".join("{" + ", ".join(str(i) for i in k) + "}" for k in t["keys"]) + "},")
|
|
561
|
+
L.append("}")
|
|
562
|
+
L.append("")
|
|
563
|
+
L.append("// E-0243 §181.2: forward predicate dependencies for carrier issues.")
|
|
564
|
+
L.append("var CARRIER_EDGES = map[string][]string{")
|
|
565
|
+
edges = {}
|
|
566
|
+
for r in plan["rules"]:
|
|
567
|
+
edges.setdefault(r["head"]["pred"], []).extend(atom["pred"] for atom in r.get("atoms", []))
|
|
568
|
+
for head, deps in sorted(edges.items()):
|
|
569
|
+
L.append(f' {json.dumps(head)}: []string{{' + ", ".join(json.dumps(x) for x in dict.fromkeys(deps)) + "},")
|
|
570
|
+
L.append("}")
|
|
571
|
+
L.append("")
|
|
572
|
+
L.append("var seen = map[string]bool{}")
|
|
573
|
+
L.append("")
|
|
574
|
+
L.append("func addRow(urn string, row []Val) bool {")
|
|
575
|
+
L.append('\tk := urn + "#" + key(row)')
|
|
576
|
+
L.append("\tif seen[k] {")
|
|
577
|
+
L.append("\t\treturn false")
|
|
578
|
+
L.append("\t}")
|
|
579
|
+
L.append("\tseen[k] = true")
|
|
580
|
+
L.append("\tTABLES[urn] = append(TABLES[urn], row)")
|
|
581
|
+
L.append("\treturn true")
|
|
582
|
+
L.append("}")
|
|
583
|
+
L.append("")
|
|
584
|
+
for r in plan["rules"]:
|
|
585
|
+
L.append(f'// {r["id"]}')
|
|
586
|
+
L.append(f'func rule_{r["name"]}() bool {{')
|
|
587
|
+
L.append("\tgrew := false")
|
|
588
|
+
if r["window"]:
|
|
589
|
+
if r["window"].get("start"):
|
|
590
|
+
L.append(f'\tif legalTime() < "{r["window"]["start"]}" {{\n\t\treturn false\n\t}}')
|
|
591
|
+
if r["window"].get("end"):
|
|
592
|
+
L.append(f'\tif legalTime() >= "{r["window"]["end"]}" {{\n\t\treturn false\n\t}}')
|
|
593
|
+
ind = "\t"
|
|
594
|
+
# Go запрещает неиспользуемую переменную цикла: если строка таблицы
|
|
595
|
+
# ни в гарде, ни в голове не встречается, переменная гасится `_ =`.
|
|
596
|
+
inner: list = []
|
|
597
|
+
levels, late_joins, late_filters = _early(r)
|
|
598
|
+
for (ai, aj, _at), (bi, bj, _bt) in late_joins:
|
|
599
|
+
inner.append(f'if !cmp(r{ai}[{aj}], "eq", r{bi}[{bj}]) {{@continue@}}')
|
|
600
|
+
for f in late_filters:
|
|
601
|
+
inner.append(f'if !eq(r{f["row"]}[{f["col"]}], {term(f["term"])}) {{@continue@}}')
|
|
602
|
+
for g in r["guards"]:
|
|
603
|
+
inner.append(f'if !cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])}) {{@continue@}}')
|
|
604
|
+
args = ", ".join(term(a) for a in r["head"]["args"])
|
|
605
|
+
inner.append(f'if addRow({json.dumps(r["head"]["pred"])}, []Val{{{args}}}) {{@grew = true@}}')
|
|
606
|
+
early = {i: _go_checks(levels.get(i, [])) for i in range(len(r["atoms"]))}
|
|
607
|
+
body_text = "".join(inner) + "".join(x for v in early.values() for x in v)
|
|
608
|
+
for i, a in enumerate(r["atoms"]):
|
|
609
|
+
_go_wild_open(L, ind, i, a)
|
|
610
|
+
L.append(f'{ind}for _, r{i} := range TABLES[{json.dumps(a["pred"])}] {{')
|
|
611
|
+
ind += "\t"
|
|
612
|
+
if f"r{i}[" not in body_text:
|
|
613
|
+
L.append(f'{ind}_ = r{i}')
|
|
614
|
+
for line in early[i]:
|
|
615
|
+
_block(L, ind, line)
|
|
616
|
+
_go_wild(L, ind, i, a)
|
|
617
|
+
for line in inner:
|
|
618
|
+
head_line, _, rest = line.partition("@")
|
|
619
|
+
mid, _, tail = rest.partition("@")
|
|
620
|
+
L.append(ind + head_line)
|
|
621
|
+
L.append(ind + "\t" + mid)
|
|
622
|
+
L.append(ind + tail)
|
|
623
|
+
for _ in r["atoms"]:
|
|
624
|
+
ind = ind[:-1]
|
|
625
|
+
L.append(ind + "}")
|
|
626
|
+
L.append("\treturn grew")
|
|
627
|
+
L.append("}")
|
|
628
|
+
L.append("")
|
|
629
|
+
names = [r["name"] for r in plan["rules"]]
|
|
630
|
+
L.append("func saturate() {")
|
|
631
|
+
L.append("\tfor grew := true; grew; {")
|
|
632
|
+
if names:
|
|
633
|
+
L.append("\t\tgrew = false")
|
|
634
|
+
for n in names:
|
|
635
|
+
L.append(f"\t\tif rule_{n}() {{\n\t\t\tgrew = true\n\t\t}}")
|
|
636
|
+
else:
|
|
637
|
+
L.append("\t\tgrew = false")
|
|
638
|
+
L.append("\t}")
|
|
639
|
+
L.append("}")
|
|
640
|
+
L.append("")
|
|
641
|
+
L.append("func register(urn string, row []Val) { addRow(urn, row) }")
|
|
642
|
+
L.append("")
|
|
643
|
+
L.append("// Общий с L1 интерфейс раннера (DECISION-0237): слой, установленные строки,")
|
|
644
|
+
L.append("// статус строки и отказ на отрицательный факт.")
|
|
645
|
+
L.append('const LAYER = "L0"')
|
|
646
|
+
L.append("")
|
|
647
|
+
L.append("func establishedRows(urn string) [][]Val { return TABLES[urn] }")
|
|
648
|
+
L.append("")
|
|
649
|
+
L.append("func truthOf(urn string, row []Val) string {")
|
|
650
|
+
L.append("\tfor _, r := range TABLES[urn] {")
|
|
651
|
+
L.append("\t\tif len(r) != len(row) {")
|
|
652
|
+
L.append("\t\t\tcontinue")
|
|
653
|
+
L.append("\t\t}")
|
|
654
|
+
L.append("\t\tok := true")
|
|
655
|
+
L.append("\t\tfor i := range r {")
|
|
656
|
+
L.append("\t\t\tif !eq(r[i], row[i]) {")
|
|
657
|
+
L.append("\t\t\t\tok = false")
|
|
658
|
+
L.append("\t\t\t\tbreak")
|
|
659
|
+
L.append("\t\t\t}")
|
|
660
|
+
L.append("\t\t}")
|
|
661
|
+
L.append("\t\tif ok {")
|
|
662
|
+
L.append('\t\t\treturn "TRUE_ONLY"')
|
|
663
|
+
L.append("\t\t}")
|
|
664
|
+
L.append("\t}")
|
|
665
|
+
L.append('\treturn "NEITHER"')
|
|
666
|
+
L.append("}")
|
|
667
|
+
L.append("")
|
|
668
|
+
L.append('func registerNegative(urn string, row []Val) { refuse("§113: отрицательный факт вне L0-среза") }')
|
|
669
|
+
L.append("")
|
|
670
|
+
L.append("// Ошибок значения агрегатов в правилах у L0-модели нет (DECISION-0239).")
|
|
671
|
+
L.append("func ruleIssues() []string { return nil }")
|
|
672
|
+
L.append("")
|
|
673
|
+
L += NORM_STUBS
|
|
674
|
+
L.append("// KEY_CONFLICT §45.1/§196: группы строк одной таблицы с одинаковой проекцией на ключ; строки уникальны по ключу строки, поэтому группа из двух и более строк — различные accepted-кортежи. Считается после насыщения.")
|
|
675
|
+
L.append("func keyConflicts() int {")
|
|
676
|
+
L.append("\tn := 0")
|
|
677
|
+
L.append("\tfor urn, keys := range KEYS {")
|
|
678
|
+
L.append("\t\tfor _, k := range keys {")
|
|
679
|
+
L.append("\t\t\tgroups := map[string]int{}")
|
|
680
|
+
L.append("\t\t\tfor _, row := range TABLES[urn] {")
|
|
681
|
+
L.append("\t\t\t\tproj := make([]Val, len(k))")
|
|
682
|
+
L.append("\t\t\t\tfor i, c := range k {")
|
|
683
|
+
L.append("\t\t\t\t\tproj[i] = row[c]")
|
|
684
|
+
L.append("\t\t\t\t}")
|
|
685
|
+
L.append("\t\t\t\tgroups[key(proj)]++")
|
|
686
|
+
L.append("\t\t\t}")
|
|
687
|
+
L.append("\t\t\tfor _, c := range groups {")
|
|
688
|
+
L.append("\t\t\t\tif c > 1 {")
|
|
689
|
+
L.append("\t\t\t\t\tn++")
|
|
690
|
+
L.append("\t\t\t\t}")
|
|
691
|
+
L.append("\t\t\t}")
|
|
692
|
+
L.append("\t\t}")
|
|
693
|
+
L.append("\t}")
|
|
694
|
+
L.append("\treturn n")
|
|
695
|
+
L.append("}")
|
|
696
|
+
L.append("func keyConflictPredicates() map[string]bool {")
|
|
697
|
+
L.append("\tout := map[string]bool{}")
|
|
698
|
+
L.append("\tfor urn, keys := range KEYS { for _, k := range keys { groups := map[string]int{}; for _, row := range TABLES[urn] { proj := make([]Val, len(k)); for i, c := range k { proj[i] = row[c] }; groups[key(proj)]++ }; for _, count := range groups { if count > 1 { out[urn] = true } } } }")
|
|
699
|
+
L.append("\treturn out")
|
|
700
|
+
L.append("}")
|
|
701
|
+
L.append("")
|
|
702
|
+
L.append("// Собственные факты пакета; reset возвращает модель к ним (чистое Evaluate API).")
|
|
703
|
+
L.append("func packageFacts() {")
|
|
704
|
+
for f in plan["facts"]:
|
|
705
|
+
L.append(f'\taddRow({json.dumps(f["pred"])}, []Val{{' + ", ".join(term(a) for a in f["args"]) + "})")
|
|
706
|
+
L.append("}")
|
|
707
|
+
L.append("")
|
|
708
|
+
L.append("func reset() {")
|
|
709
|
+
L.append("\tfor urn := range TABLES {")
|
|
710
|
+
L.append("\t\tTABLES[urn] = TABLES[urn][:0]")
|
|
711
|
+
L.append("\t}")
|
|
712
|
+
L.append("\tseen = map[string]bool{}")
|
|
713
|
+
L.append("\tpackageFacts()")
|
|
714
|
+
L.append("}")
|
|
715
|
+
return _factory("\n".join(L) + "\n", plan)
|
|
716
|
+
|
|
717
|
+
# --- поражаемый слой L1 (DECISION-0237) ---------------------------------------
|
|
718
|
+
# Та же печать, что у Python и TypeScript: опоры битами происхождения на ключ
|
|
719
|
+
# строки, кандидаты с идентичностью «правило + подстановка», defeat —
|
|
720
|
+
# локальный helper над полным снимком кандидатов, страты и фазы §111 — из плана.
|
|
721
|
+
|
|
722
|
+
def _accepts(expr: str, accept: list) -> str:
|
|
723
|
+
return f"!accepts({expr}, " + ", ".join(json.dumps(a) for a in accept) + ")"
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def _block(L: list, ind: str, line: str) -> None:
|
|
727
|
+
"""Однострочная запись `if cond {@тело@}` в три строки Go."""
|
|
728
|
+
head_line, _, rest = line.partition("@")
|
|
729
|
+
mid, _, tail = rest.partition("@")
|
|
730
|
+
L.append(ind + head_line)
|
|
731
|
+
L.append(ind + "\t" + mid)
|
|
732
|
+
L.append(ind + tail)
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _steps09(L: list, body: dict, ind: str) -> tuple:
|
|
736
|
+
"""Шаги тела плана 0.9 в порядке `solve` (как у Python)."""
|
|
737
|
+
L.append(f"{ind}for once := 0; once < 1; once++ {{")
|
|
738
|
+
ind += "\t"
|
|
739
|
+
depth = 1
|
|
740
|
+
for step in body["steps"]:
|
|
741
|
+
if "gen" in step:
|
|
742
|
+
i = step["gen"]
|
|
743
|
+
a = body["atoms"][i]
|
|
744
|
+
p = json.dumps(a["pred"])
|
|
745
|
+
for f in body["filters"]:
|
|
746
|
+
if f["row"] == i and f.get("pre"):
|
|
747
|
+
# DECISION-0270: вычисляемый аргумент считается ДО перебора
|
|
748
|
+
# таблицы (`solver.solve`); ошибка значения — код по подстановке.
|
|
749
|
+
name = f'pre{i}_{f["col"]}'
|
|
750
|
+
if f.get("err"):
|
|
751
|
+
L.append(f'{ind}{name}, code{name} := tryRow(func() []Val {{ return []Val{{{term(f["term"])}}} }})')
|
|
752
|
+
L.append(f'{ind}if code{name} != "" {{')
|
|
753
|
+
L.append(f'{ind}\tcodes[code{name}] = true')
|
|
754
|
+
L.append(f'{ind}\tcontinue')
|
|
755
|
+
L.append(f'{ind}}}')
|
|
756
|
+
else:
|
|
757
|
+
L.append(f'{ind}{name} := []Val{{{term(f["term"])}}}')
|
|
758
|
+
_go_wild_open(L, ind, i, a)
|
|
759
|
+
L.append(f'{ind}for i{i}, r{i} := range TABLES[{p}] {{')
|
|
760
|
+
ind += "\t"
|
|
761
|
+
depth += 1
|
|
762
|
+
L.append(f"{ind}_ = r{i}")
|
|
763
|
+
_block(L, ind, f'if {_accepts(f"maskStatus(SUP[{p}][RK[{p}][i{i}]])", a["accept"])} {{@continue@}}')
|
|
764
|
+
for (ai, aj, _at), (bi, bj, _bt) in body["joins"]:
|
|
765
|
+
if bi == i:
|
|
766
|
+
_block(L, ind, f'if !cmp(r{ai}[{aj}], "eq", r{bi}[{bj}]) {{@continue@}}')
|
|
767
|
+
for f in body["filters"]:
|
|
768
|
+
if f["row"] == i:
|
|
769
|
+
value = f'pre{i}_{f["col"]}[0]' if f.get("pre") else term(f["term"])
|
|
770
|
+
_block(L, ind, f'if !eq(r{f["row"]}[{f["col"]}], {value}) {{@continue@}}')
|
|
771
|
+
_go_wild(L, ind, i, a)
|
|
772
|
+
elif "check" in step:
|
|
773
|
+
c = body["checks"][step["check"]]
|
|
774
|
+
args = ", ".join(term(a) for a in c["args"])
|
|
775
|
+
predicate = json.dumps(c["pred"])
|
|
776
|
+
if c.get("err"):
|
|
777
|
+
# DECISION-0270: ошибка значения аргумента проверки — код по подстановке.
|
|
778
|
+
ci = step["check"]
|
|
779
|
+
L.append(f'{ind}a{ci}, codeA{ci} := tryRow(func() []Val {{ return []Val{{{args}}} }})')
|
|
780
|
+
L.append(f'{ind}if codeA{ci} != "" {{')
|
|
781
|
+
L.append(f'{ind}\tcodes[codeA{ci}] = true')
|
|
782
|
+
L.append(f'{ind}\tcontinue')
|
|
783
|
+
L.append(f'{ind}}}')
|
|
784
|
+
_block(L, ind, f'if {_accepts(f"truthOf({predicate}, a{ci})", c["accept"])} {{@continue@}}')
|
|
785
|
+
else:
|
|
786
|
+
_block(L, ind, f'if {_accepts(f"truthOf({predicate}, []Val{{{args}}})", c["accept"])} {{@continue@}}')
|
|
787
|
+
else:
|
|
788
|
+
gi = step["guard"]
|
|
789
|
+
g = body["guards"][gi]
|
|
790
|
+
if g.get("cal") or g.get("err"):
|
|
791
|
+
# DECISION-0245 п. 6, DECISION-0252 п. 4: код ошибки значения — по подстановкам.
|
|
792
|
+
L.append(f'{ind}ok{gi}, code{gi} := tryBool(func() bool {{ return cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])}) }})')
|
|
793
|
+
L.append(f'{ind}if code{gi} != "" {{')
|
|
794
|
+
L.append(f'{ind}\tcodes[code{gi}] = true')
|
|
795
|
+
L.append(f'{ind}\tcontinue')
|
|
796
|
+
L.append(f'{ind}}}')
|
|
797
|
+
_block(L, ind, f'if !ok{gi} {{@continue@}}')
|
|
798
|
+
else:
|
|
799
|
+
_block(L, ind, f'if !cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])}) {{@continue@}}')
|
|
800
|
+
return ind, depth
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _close09(L: list, ind: str, depth: int) -> None:
|
|
804
|
+
for _ in range(depth):
|
|
805
|
+
ind = ind[:-1]
|
|
806
|
+
L.append(ind + "}")
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
def _aggregates09(L: list, plan: dict) -> None:
|
|
810
|
+
for a in plan.get("aggregates") or []:
|
|
811
|
+
params = ", ".join(f"p{i} Val" for i in range(len(a["params"])))
|
|
812
|
+
call = ", ".join(f"p{i}" for i in range(len(a["params"])))
|
|
813
|
+
L.append(f'func items_{a["name"]}({params}) []Val {{')
|
|
814
|
+
L.append("\titems := []Val{}")
|
|
815
|
+
L.append("\tseen := map[string]bool{}")
|
|
816
|
+
ind, depth = _steps09(L, a, "\t")
|
|
817
|
+
L.append(f'{ind}var v Val = {term(a["element"])}')
|
|
818
|
+
if a["distinct"]:
|
|
819
|
+
L.append(f"{ind}k := key([]Val{{v}})")
|
|
820
|
+
L.append(f"{ind}if seen[k] {{")
|
|
821
|
+
L.append(f"{ind}\tcontinue")
|
|
822
|
+
L.append(f"{ind}}}")
|
|
823
|
+
L.append(f"{ind}seen[k] = true")
|
|
824
|
+
L.append(f"{ind}items = append(items, v)")
|
|
825
|
+
_close09(L, ind, depth)
|
|
826
|
+
L.append("\t_ = seen")
|
|
827
|
+
L.append("\treturn items")
|
|
828
|
+
L.append("}")
|
|
829
|
+
L.append("")
|
|
830
|
+
L.append(f'func {a["name"]}({params}) Val {{')
|
|
831
|
+
L.append(f'\titems := items_{a["name"]}({call})')
|
|
832
|
+
if a["op"] == "count":
|
|
833
|
+
L.append("\treturn dec(strconv.Itoa(len(items)))")
|
|
834
|
+
else:
|
|
835
|
+
L.append("\tif len(items) == 0 {")
|
|
836
|
+
if a["empty"] is not None:
|
|
837
|
+
L.append(f'\t\treturn {term(a["empty"])}')
|
|
838
|
+
else:
|
|
839
|
+
L.append('\t\tpanic(aggError{"EMPTY_AGGREGATE"})')
|
|
840
|
+
L.append("\t}")
|
|
841
|
+
if a["op"] == "only":
|
|
842
|
+
# §51 (errata E-0256): единственное РАЗЛИЧНОЕ значение по ключу.
|
|
843
|
+
L.append("\tdistinct := map[string]bool{}")
|
|
844
|
+
L.append("\tfor _, x := range items {")
|
|
845
|
+
L.append("\t\tdistinct[key([]Val{x})] = true")
|
|
846
|
+
L.append("\t}")
|
|
847
|
+
L.append("\tif len(distinct) > 1 {")
|
|
848
|
+
L.append('\t\tpanic(aggError{"CARDINALITY_CONFLICT"})')
|
|
849
|
+
L.append("\t}")
|
|
850
|
+
L.append("\treturn items[0]")
|
|
851
|
+
else:
|
|
852
|
+
fn = {"sum": "add", "min": "min", "max": "max"}[a["op"]]
|
|
853
|
+
L.append("\tacc := items[0]")
|
|
854
|
+
L.append("\tfor _, x := range items[1:] {")
|
|
855
|
+
L.append(f"\t\tacc = {fn}(acc, x)")
|
|
856
|
+
L.append("\t}")
|
|
857
|
+
L.append("\treturn acc")
|
|
858
|
+
L.append("}")
|
|
859
|
+
L.append("")
|
|
860
|
+
L.append("// E-0116: отрицательное слагаемое под монотонным гардом sum")
|
|
861
|
+
L.append(f'func neg_{a["name"]}({params}) bool {{')
|
|
862
|
+
L.append(f'\tfor _, x := range items_{a["name"]}({call}) {{')
|
|
863
|
+
L.append('\t\tif cmp(x, "lt", sub(x, x)) {')
|
|
864
|
+
L.append("\t\t\treturn true")
|
|
865
|
+
L.append("\t\t}")
|
|
866
|
+
L.append("\t}")
|
|
867
|
+
L.append("\treturn false")
|
|
868
|
+
L.append("}")
|
|
869
|
+
L.append("")
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
def _rule09(L: list, r: dict, value: bool = False) -> None:
|
|
873
|
+
"""Правило плана 0.9: фоллибельное копит выводы и применяет их без ошибки.
|
|
874
|
+
`value` — план 0.12 (DECISION-0252): ошибки значения §58 и сверка вида."""
|
|
875
|
+
L.append(f'// {r["id"]} ({r["strength"]})')
|
|
876
|
+
L.append(f'func rule_{r["name"]}() bool {{')
|
|
877
|
+
L.append("\tgrew := false")
|
|
878
|
+
if r["window"]:
|
|
879
|
+
if r["window"].get("start"):
|
|
880
|
+
L.append(f'\tif legalTime() < "{r["window"]["start"]}" {{\n\t\treturn false\n\t}}')
|
|
881
|
+
if r["window"].get("end"):
|
|
882
|
+
L.append(f'\tif legalTime() >= "{r["window"]["end"]}" {{\n\t\treturn false\n\t}}')
|
|
883
|
+
L.append("\tout := [][2][]Val{}")
|
|
884
|
+
L.append("\tfailed := false")
|
|
885
|
+
cal = r.get("headFallible") or any(g.get("cal") or g.get("err") for g in r["guards"]) \
|
|
886
|
+
or any(f.get("err") for f in r["filters"]) or any(c.get("err") for c in r["checks"])
|
|
887
|
+
if cal:
|
|
888
|
+
L.append("\tcodes, hcodes := map[string]bool{}, map[string]bool{}")
|
|
889
|
+
L.append("\tfunc() {")
|
|
890
|
+
L.append("\t\tdefer func() {")
|
|
891
|
+
L.append("\t\t\tif rec := recover(); rec != nil {")
|
|
892
|
+
L.append("\t\t\t\tif e, ok := rec.(aggError); ok {")
|
|
893
|
+
L.append(f'\t\t\t\t\tissue({json.dumps(r["name"])}, e.code)')
|
|
894
|
+
L.append("\t\t\t\t\tfailed = true")
|
|
895
|
+
L.append("\t\t\t\t\treturn")
|
|
896
|
+
L.append("\t\t\t\t}")
|
|
897
|
+
L.append("\t\t\t\tpanic(rec)")
|
|
898
|
+
L.append("\t\t\t}")
|
|
899
|
+
L.append("\t\t}()")
|
|
900
|
+
ind, depth = _steps09(L, r, "\t\t")
|
|
901
|
+
for a in r.get("addends") or []:
|
|
902
|
+
args = ", ".join(term(x) for x in a["args"])
|
|
903
|
+
L.append(f'{ind}if neg_{a["fn"]}({args}) {{')
|
|
904
|
+
L.append(f'{ind}\tissue({json.dumps(r["name"])}, "MONOTONE_GUARD_NEGATIVE_ADDEND")')
|
|
905
|
+
L.append(f"{ind}\tcontinue")
|
|
906
|
+
L.append(f"{ind}}}")
|
|
907
|
+
args = ", ".join(term(a) for a in r["head"]["args"])
|
|
908
|
+
subst = ", ".join(term(a) for a in r["subst"])
|
|
909
|
+
if r.get("headFallible"):
|
|
910
|
+
if r.get("headAgg"):
|
|
911
|
+
# Ошибка агрегата головы — ошибка этой подстановки, как ошибка терма (errata E-0265).
|
|
912
|
+
L.append(f"{ind}row, hcode := tryRow(func() []Val {{")
|
|
913
|
+
L.append(f"{ind}\tdefer func() {{")
|
|
914
|
+
L.append(f"{ind}\t\tif rec := recover(); rec != nil {{")
|
|
915
|
+
L.append(f"{ind}\t\t\tif e, ok := rec.(aggError); ok {{")
|
|
916
|
+
L.append(f"{ind}\t\t\t\tpanic(termError{{e.code}})")
|
|
917
|
+
L.append(f"{ind}\t\t\t}}")
|
|
918
|
+
L.append(f"{ind}\t\t\tpanic(rec)")
|
|
919
|
+
L.append(f"{ind}\t\t}}")
|
|
920
|
+
L.append(f"{ind}\t}}()")
|
|
921
|
+
L.append(f"{ind}\treturn []Val{{{args}}}")
|
|
922
|
+
L.append(f"{ind}}})")
|
|
923
|
+
else:
|
|
924
|
+
L.append(f"{ind}row, hcode := tryRow(func() []Val {{ return []Val{{{args}}} }})")
|
|
925
|
+
L.append(f'{ind}if hcode != "" {{')
|
|
926
|
+
L.append(f"{ind}\thcodes[hcode] = true")
|
|
927
|
+
L.append(f"{ind}\tcontinue")
|
|
928
|
+
L.append(f"{ind}}}")
|
|
929
|
+
elif r.get("kindChecks"):
|
|
930
|
+
L.append(f"{ind}row := []Val{{{args}}}")
|
|
931
|
+
for pos, want in r.get("kindChecks") or []:
|
|
932
|
+
# E-0077 (DECISION-0252 п. 5): вид значения против слота — вывод не принят.
|
|
933
|
+
L.append(f'{ind}if kindOf(row[{pos}]) != "{want}" {{')
|
|
934
|
+
L.append(f'{ind}\tkindIssue({json.dumps(r["name"])}, {json.dumps(r["head"]["pred"])}, {pos}, row[{pos}])')
|
|
935
|
+
L.append(f"{ind}\tcontinue")
|
|
936
|
+
L.append(f"{ind}}}")
|
|
937
|
+
if r.get("headFallible") or r.get("kindChecks"):
|
|
938
|
+
L.append(f"{ind}out = append(out, [2][]Val{{row, {{{subst}}}}})")
|
|
939
|
+
else:
|
|
940
|
+
L.append(f"{ind}out = append(out, [2][]Val{{{{{args}}}, {{{subst}}}}})")
|
|
941
|
+
_close09(L, ind, depth)
|
|
942
|
+
L.append("\t}()")
|
|
943
|
+
L.append("\tif failed {")
|
|
944
|
+
L.append("\t\treturn false")
|
|
945
|
+
L.append("\t}")
|
|
946
|
+
if cal:
|
|
947
|
+
name = json.dumps(r["name"])
|
|
948
|
+
L.append("\t// §58: ошибка гарда снимает правило на проход; ошибка головы снимает только")
|
|
949
|
+
L.append("\t// свою подстановку, issue — по каждому коду однажды (errata E-0265).")
|
|
950
|
+
what, why = ("значения", "DECISION-0252 п. 4") if value else ("календаря", "DECISION-0245 п. 6")
|
|
951
|
+
L.append("\tif len(codes) > 0 {")
|
|
952
|
+
L.append("\t\tif len(codes) > 1 {")
|
|
953
|
+
L.append(f'\t\t\trefuse("коды ошибок {what} в гарде зависят от порядка подстановок ({why}): " + {name})')
|
|
954
|
+
L.append("\t\t}")
|
|
955
|
+
L.append("\t\tfor c := range codes {")
|
|
956
|
+
L.append(f"\t\t\tissue({name}, c)")
|
|
957
|
+
L.append("\t\t}")
|
|
958
|
+
L.append("\t\treturn false")
|
|
959
|
+
L.append("\t}")
|
|
960
|
+
L.append("\thkeys := make([]string, 0, len(hcodes))")
|
|
961
|
+
L.append("\tfor c := range hcodes {")
|
|
962
|
+
L.append("\t\thkeys = append(hkeys, c)")
|
|
963
|
+
L.append("\t}")
|
|
964
|
+
L.append("\tsort.Strings(hkeys)")
|
|
965
|
+
L.append("\tfor _, c := range hkeys {")
|
|
966
|
+
L.append(f"\t\tissue({name}, c)")
|
|
967
|
+
L.append("\t}")
|
|
968
|
+
pos = r["head"]["polarity"] == "positive"
|
|
969
|
+
L.append("\tfor _, o := range out {")
|
|
970
|
+
if r["strength"] == "strict":
|
|
971
|
+
L.append(f'\t\tif addRow({json.dumps(r["head"]["pred"])}, o[0], {"bitSP" if pos else "bitSN"}) {{')
|
|
972
|
+
L.append("\t\t\tgrew = true")
|
|
973
|
+
L.append("\t\t}")
|
|
974
|
+
else:
|
|
975
|
+
L.append(f'\t\tcand({json.dumps(r["name"])}, {str(r["strength"] == "defeater").lower()}, '
|
|
976
|
+
f'{json.dumps(r["head"]["pred"])}, {str(pos).lower()}, o[0], o[1])')
|
|
977
|
+
L.append("\t}")
|
|
978
|
+
L.append("\treturn grew")
|
|
979
|
+
L.append("}")
|
|
980
|
+
L.append("")
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
def _closures09(L: list, plan: dict) -> None:
|
|
984
|
+
for c in plan.get("closures") or []:
|
|
985
|
+
d, pr = json.dumps(c["domain"]), json.dumps(c["pred"])
|
|
986
|
+
L.append(f'// {c["id"]}: замыкание §70 — негатив члену домена без strict/asserted positive-опоры')
|
|
987
|
+
L.append(f'func {c["name"]}() {{')
|
|
988
|
+
if c["window"]:
|
|
989
|
+
if c["window"].get("start"):
|
|
990
|
+
L.append(f'\tif legalTime() < "{c["window"]["start"]}" {{\n\t\treturn\n\t}}')
|
|
991
|
+
if c["window"].get("end"):
|
|
992
|
+
L.append(f'\tif legalTime() >= "{c["window"]["end"]}" {{\n\t\treturn\n\t}}')
|
|
993
|
+
L.append("\tmembers := [][]Val{}")
|
|
994
|
+
L.append(f"\tfor i, row := range TABLES[{d}] {{")
|
|
995
|
+
L.append(f'\t\tif maskStatus(SUP[{d}][RK[{d}][i]]) == "TRUE_ONLY" {{')
|
|
996
|
+
L.append("\t\t\tmembers = append(members, row)")
|
|
997
|
+
L.append("\t\t}")
|
|
998
|
+
L.append("\t}")
|
|
999
|
+
L.append("\tfor _, row := range members {")
|
|
1000
|
+
L.append(f"\t\tif SUP[{pr}][key(row)]&bitSP != 0 {{")
|
|
1001
|
+
L.append("\t\t\tcontinue")
|
|
1002
|
+
L.append("\t\t}")
|
|
1003
|
+
L.append(f"\t\taddRow({pr}, append([]Val{{}}, row...), bitSN)")
|
|
1004
|
+
L.append("\t}")
|
|
1005
|
+
L.append("}")
|
|
1006
|
+
L.append("")
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def _waiting_strata(L: list, plan: dict, line, comment: str) -> dict:
|
|
1010
|
+
"""Страты strict-прохода без ждущих правил (errata E-0174): у каждого
|
|
1011
|
+
набора `waiting` расписания — своя константа. Кто ждёт, решил план;
|
|
1012
|
+
эмиттер лишь вычёркивает имена. Возвращает набор → имя константы."""
|
|
1013
|
+
out: dict = {}
|
|
1014
|
+
for ph in plan["schedule"]:
|
|
1015
|
+
if ph["phase"] in PHASE_FIELDS and set(ph) - PHASE_FIELDS[ph["phase"]]:
|
|
1016
|
+
raise ValueError(f"поле фазы расписания не поддержано эмиттером: "
|
|
1017
|
+
f"{sorted(set(ph) - PHASE_FIELDS[ph['phase']])}")
|
|
1018
|
+
key = tuple(ph.get("waiting") or ())
|
|
1019
|
+
if not key or key in out:
|
|
1020
|
+
continue
|
|
1021
|
+
if not set(key) <= {n for s in plan["strata"]["strict"] for n in s}:
|
|
1022
|
+
raise ValueError(f"ждущее правило вне strict-страт плана: {', '.join(key)}")
|
|
1023
|
+
out[key] = f"STRICT_WAIT_{len(out)}"
|
|
1024
|
+
L.append(f"{comment} §70/§231.1 (E-0174): проход до замыкания — без {', '.join(key)}")
|
|
1025
|
+
L.append(line(out[key], [[n for n in s if n not in key] for s in plan["strata"]["strict"]]))
|
|
1026
|
+
return out
|
|
1027
|
+
|
|
1028
|
+
# Интерфейс позиций для раннера (DECISION-0242): у модели без норм — заглушки;
|
|
1029
|
+
# раннер печатает канонический JSON сам, модель его функций не зовёт (харнесс
|
|
1030
|
+
# API собирается без раннера).
|
|
1031
|
+
NORM_TYPES = [
|
|
1032
|
+
"type normValue struct {", "\tV Val", "\tPred string", "\tCol int", "}", "",
|
|
1033
|
+
]
|
|
1034
|
+
NORM_STUBS = NORM_TYPES + [
|
|
1035
|
+
"type normPosition struct {", "\tTemplate, Modality, Status, Holder string", "\tBeneficiary *string",
|
|
1036
|
+
"\tPayload interface{}", "\tVars []string", "\tValues []normValue", "}", "",
|
|
1037
|
+
"func normPositions() ([]normPosition, bool) { return nil, false }", "",
|
|
1038
|
+
"func normIssues() []string { return nil }", "",
|
|
1039
|
+
"func blockedEffect(pred string, row []Val) bool { return false }", "",
|
|
1040
|
+
"func judgmentPositions() []jPos { return nil }", "",
|
|
1041
|
+
"func blockedImmunity(pred string, row []Val) bool { return false }", "",
|
|
1042
|
+
]
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
def _gv(t, rule: str = "") -> str:
|
|
1046
|
+
if t is None:
|
|
1047
|
+
return "nil"
|
|
1048
|
+
if t["k"] == "calbound":
|
|
1049
|
+
# DECISION-0245 п. 8: ошибка границы — issue правила и граница «нет».
|
|
1050
|
+
if "error" in t:
|
|
1051
|
+
return f'boundError({json.dumps(rule)}, {json.dumps(t["error"])})'
|
|
1052
|
+
return f'bound({json.dumps(rule)}, func() Val {{ return {term(t["term"])} }})'
|
|
1053
|
+
|
|
1054
|
+
if t["k"] == "fallback":
|
|
1055
|
+
return '"<fallback>"'
|
|
1056
|
+
if t["k"] == "inf":
|
|
1057
|
+
return json.dumps(t["v"])
|
|
1058
|
+
return term(t)
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
def _gl(t) -> str:
|
|
1062
|
+
if t is None:
|
|
1063
|
+
return "nil"
|
|
1064
|
+
return (f'&nLit{{{json.dumps(t["pred"])}, {str(t["polarity"] == "positive").lower()}, []Val{{'
|
|
1065
|
+
+ ", ".join(term(a) for a in t["args"]) + "}}")
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _gw(w, rule: str = "") -> str:
|
|
1069
|
+
if w is None:
|
|
1070
|
+
return "nil"
|
|
1071
|
+
return (f'&nWin{{{_gv(w["start"], rule)}, {_gv(w["end"], rule)}, {str(w["startInclusive"]).lower()}, '
|
|
1072
|
+
f'{str(w["endInclusive"]).lower()}}}')
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
def emit_l1(plan: dict) -> str:
|
|
1076
|
+
L = ["package main", "",
|
|
1077
|
+
"// Модель поражаемого слоя L1 (DECISION-0237): опоры с происхождением,",
|
|
1078
|
+
"// кандидаты и defeat по стратам §111; статус §62 выводится при чтении.",
|
|
1079
|
+
"", 'import "sort"', "", 'const LAYER = "L1"', "", "var TABLES = map[string][][]Val{"]
|
|
1080
|
+
v10 = "norms" in plan
|
|
1081
|
+
v09 = _is_09(plan)
|
|
1082
|
+
if v10:
|
|
1083
|
+
L[L.index('import "sort"')] = 'import (\n\t"encoding/json"\n\t"sort"\n\t"strconv"\n\t"strings"\n)'
|
|
1084
|
+
elif v09:
|
|
1085
|
+
L[L.index('import "sort"')] = 'import (\n\t"sort"\n\t"strconv"\n)'
|
|
1086
|
+
|
|
1087
|
+
for t in plan["tables"]:
|
|
1088
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{}},')
|
|
1089
|
+
L.append("}")
|
|
1090
|
+
_key_scope(L, plan)
|
|
1091
|
+
L.append("")
|
|
1092
|
+
if not v10:
|
|
1093
|
+
# L1 without normative positions shares runner.go with normative L1.
|
|
1094
|
+
# It has no status clock but must keep the generated runner API.
|
|
1095
|
+
L.append("// E-0212 §135.1: this L1 model has no normative positions.")
|
|
1096
|
+
L.append("func setStatusTime(t string) {}")
|
|
1097
|
+
L.append("")
|
|
1098
|
+
L.append("// Ключи строк — параллельно строкам; опоры — биты на ключ строки.")
|
|
1099
|
+
L.append("var RK = map[string][]string{}")
|
|
1100
|
+
L.append("var SUP = map[string]map[string]int{}")
|
|
1101
|
+
L.append("")
|
|
1102
|
+
L.append("func init() {")
|
|
1103
|
+
L.append("\tfor urn := range TABLES {")
|
|
1104
|
+
L.append("\t\tRK[urn] = []string{}")
|
|
1105
|
+
L.append("\t\tSUP[urn] = map[string]int{}")
|
|
1106
|
+
L.append("\t}")
|
|
1107
|
+
L.append("}")
|
|
1108
|
+
L.append("")
|
|
1109
|
+
L.append("var CARRIER_EDGES = map[string][]string{")
|
|
1110
|
+
edges = {}
|
|
1111
|
+
for r in plan["rules"]:
|
|
1112
|
+
edges.setdefault(r["head"]["pred"], []).extend(atom["pred"] for atom in r.get("atoms", []))
|
|
1113
|
+
for head, deps in sorted(edges.items()):
|
|
1114
|
+
L.append(f' {json.dumps(head)}: []string{{' + ", ".join(json.dumps(x) for x in dict.fromkeys(deps)) + "},")
|
|
1115
|
+
L.append("}")
|
|
1116
|
+
L.append("")
|
|
1117
|
+
L.append("// strict/asserted +/−, surviving defeasible +/−")
|
|
1118
|
+
L.append("const (")
|
|
1119
|
+
L.append("\tbitSP = 1")
|
|
1120
|
+
L.append("\tbitSN = 2")
|
|
1121
|
+
L.append("\tbitDP = 4")
|
|
1122
|
+
L.append("\tbitDN = 8")
|
|
1123
|
+
L.append(")")
|
|
1124
|
+
L.append("")
|
|
1125
|
+
L.append("// Объявленные типы параметров: раннер сверяет с ними аргументы дела (§189).")
|
|
1126
|
+
L.append("var PARAMS = map[string][]string{")
|
|
1127
|
+
for t in plan["tables"]:
|
|
1128
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{' + ", ".join(json.dumps(x, ensure_ascii=False) for x in t["params"]) + "},")
|
|
1129
|
+
L.append("}")
|
|
1130
|
+
L.append("")
|
|
1131
|
+
L.append("// Константы §54: тип для проверки §189 и значение для разыменования аргументов дела.")
|
|
1132
|
+
L.append("type constDecl struct {")
|
|
1133
|
+
L.append("\tType string")
|
|
1134
|
+
L.append("\tValue Val")
|
|
1135
|
+
L.append("}")
|
|
1136
|
+
L.append("")
|
|
1137
|
+
L.append("var CONSTS = map[string]constDecl{")
|
|
1138
|
+
for cid, c in plan["consts"].items():
|
|
1139
|
+
L.append(f'\t{json.dumps(cid)}: {{{json.dumps(c["type"])}, {term(c["term"])}}},')
|
|
1140
|
+
L.append("}")
|
|
1141
|
+
L.append("")
|
|
1142
|
+
L.append("// Наследование сущностей: подтип подходит параметру базового типа.")
|
|
1143
|
+
_functions(L, plan)
|
|
1144
|
+
_judgments(L, plan, l1=True)
|
|
1145
|
+
_calendar_env(L, plan)
|
|
1146
|
+
L.append("var BASES = map[string]string{")
|
|
1147
|
+
for sub, base in plan["bases"].items():
|
|
1148
|
+
L.append(f'\t{json.dumps(sub)}: {json.dumps(base)},')
|
|
1149
|
+
L.append("}")
|
|
1150
|
+
L.append("")
|
|
1151
|
+
L.append("// Ключи отношений §45.1: позиции столбцов каждого ключа.")
|
|
1152
|
+
L.append("var KEYS = map[string][][]int{")
|
|
1153
|
+
for t in plan["tables"]:
|
|
1154
|
+
if t["keys"]:
|
|
1155
|
+
L.append(f'\t{json.dumps(t["pred"])}: {{' + ", ".join("{" + ", ".join(str(i) for i in k) + "}" for k in t["keys"]) + "},")
|
|
1156
|
+
L.append("}")
|
|
1157
|
+
L.append("")
|
|
1158
|
+
L.append("func addRow(urn string, row []Val, bit int) bool {")
|
|
1159
|
+
L.append("\tk := key(row)")
|
|
1160
|
+
L.append("\tsup := SUP[urn]")
|
|
1161
|
+
L.append("\tm, ok := sup[k]")
|
|
1162
|
+
L.append("\tif !ok {")
|
|
1163
|
+
L.append("\t\tsup[k] = bit")
|
|
1164
|
+
L.append("\t\tTABLES[urn] = append(TABLES[urn], row)")
|
|
1165
|
+
L.append("\t\tRK[urn] = append(RK[urn], k)")
|
|
1166
|
+
L.append("\t\treturn true")
|
|
1167
|
+
L.append("\t}")
|
|
1168
|
+
L.append("\tif m&bit != 0 {")
|
|
1169
|
+
L.append("\t\treturn false")
|
|
1170
|
+
L.append("\t}")
|
|
1171
|
+
L.append("\tsup[k] = m | bit")
|
|
1172
|
+
L.append("\treturn true")
|
|
1173
|
+
L.append("}")
|
|
1174
|
+
L.append("")
|
|
1175
|
+
L.append("// §62: пара опор (t, f) → статус; опоры — объединение strict и surviving.")
|
|
1176
|
+
L.append("func maskStatus(m int) string {")
|
|
1177
|
+
L.append("\tpos, neg := m&(bitSP|bitDP) != 0, m&(bitSN|bitDN) != 0")
|
|
1178
|
+
L.append("\tswitch {")
|
|
1179
|
+
L.append("\tcase pos && neg:")
|
|
1180
|
+
L.append('\t\treturn "BOTH"')
|
|
1181
|
+
L.append("\tcase pos:")
|
|
1182
|
+
L.append('\t\treturn "TRUE_ONLY"')
|
|
1183
|
+
L.append("\tcase neg:")
|
|
1184
|
+
L.append('\t\treturn "FALSE_ONLY"')
|
|
1185
|
+
L.append("\t}")
|
|
1186
|
+
L.append('\treturn "NEITHER"')
|
|
1187
|
+
L.append("}")
|
|
1188
|
+
L.append("")
|
|
1189
|
+
L.append("func accepts(s string, allowed ...string) bool {")
|
|
1190
|
+
L.append("\tfor _, a := range allowed {")
|
|
1191
|
+
L.append("\t\tif s == a {")
|
|
1192
|
+
L.append("\t\t\treturn true")
|
|
1193
|
+
L.append("\t\t}")
|
|
1194
|
+
L.append("\t}")
|
|
1195
|
+
L.append("\treturn false")
|
|
1196
|
+
L.append("}")
|
|
1197
|
+
L.append("")
|
|
1198
|
+
L.append("func truthOf(urn string, row []Val) string {")
|
|
1199
|
+
L.append("\tsup, ok := SUP[urn]")
|
|
1200
|
+
L.append("\tif !ok {")
|
|
1201
|
+
L.append('\t\treturn "NEITHER"')
|
|
1202
|
+
L.append("\t}")
|
|
1203
|
+
L.append("\treturn maskStatus(sup[key(row)])")
|
|
1204
|
+
L.append("}")
|
|
1205
|
+
L.append("")
|
|
1206
|
+
L.append("// Установленные строки (TRUE_ONLY) — перечисление collect и API, без BOTH.")
|
|
1207
|
+
L.append("func establishedRows(urn string) [][]Val {")
|
|
1208
|
+
L.append("\tout := [][]Val{}")
|
|
1209
|
+
L.append("\tfor i, r := range TABLES[urn] {")
|
|
1210
|
+
L.append('\t\tif maskStatus(SUP[urn][RK[urn][i]]) == "TRUE_ONLY" {')
|
|
1211
|
+
L.append("\t\t\tout = append(out, r)")
|
|
1212
|
+
L.append("\t\t}")
|
|
1213
|
+
L.append("\t}")
|
|
1214
|
+
L.append("\treturn out")
|
|
1215
|
+
L.append("}")
|
|
1216
|
+
L.append("")
|
|
1217
|
+
if v09:
|
|
1218
|
+
L.append("// Ошибка значения агрегата в правиле (DECISION-0239 G3): issue по правилу и коду однажды.")
|
|
1219
|
+
L.append("type aggError struct{ code string }")
|
|
1220
|
+
L.append("")
|
|
1221
|
+
L.append("var RULE_ISSUES = []string{}")
|
|
1222
|
+
L.append("var issueSeen = map[string]bool{}")
|
|
1223
|
+
L.append("")
|
|
1224
|
+
L.append("func issue(rule, code string) {")
|
|
1225
|
+
L.append(' k := rule + "|" + code')
|
|
1226
|
+
L.append(" if !issueSeen[k] {")
|
|
1227
|
+
L.append(" issueSeen[k] = true")
|
|
1228
|
+
L.append(" RULE_ISSUES = append(RULE_ISSUES, code)")
|
|
1229
|
+
L.append(" }")
|
|
1230
|
+
L.append("}")
|
|
1231
|
+
L.append("")
|
|
1232
|
+
L.append("func ruleIssues() []string { return RULE_ISSUES }")
|
|
1233
|
+
L.append("")
|
|
1234
|
+
if plan["planVersion"] in (SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19):
|
|
1235
|
+
L.append("// E-0077 (DECISION-0252 п. 5): TYPE_ERROR по правилу, предикату, позиции и")
|
|
1236
|
+
L.append("// фактическому виду однажды; связывается с правилом для статуса вопроса §175.")
|
|
1237
|
+
L.append("var kindSeen = map[string]bool{}")
|
|
1238
|
+
L.append("")
|
|
1239
|
+
L.append("func kindIssue(rule, pred string, pos int, v Val) {")
|
|
1240
|
+
L.append('\tk := rule + "\\x00" + pred + "\\x00" + strconv.Itoa(pos) + "\\x00" + kindOf(v)')
|
|
1241
|
+
L.append("\tif !kindSeen[k] {")
|
|
1242
|
+
L.append("\t\tkindSeen[k] = true")
|
|
1243
|
+
L.append('\t\tissue(rule, "TYPE_ERROR")')
|
|
1244
|
+
L.append("\t}")
|
|
1245
|
+
L.append("}")
|
|
1246
|
+
L.append("")
|
|
1247
|
+
L.append("// strconv нужен только агрегату count; у плана без него импорт иначе мёртв.")
|
|
1248
|
+
L.append("var _ = strconv.Itoa")
|
|
1249
|
+
L.append("")
|
|
1250
|
+
_aggregates09(L, plan)
|
|
1251
|
+
else:
|
|
1252
|
+
L.append("func ruleIssues() []string { return nil }")
|
|
1253
|
+
L.append("")
|
|
1254
|
+
L.append("// Кандидаты §104 страты: идентичность — правило и типизированная подстановка.")
|
|
1255
|
+
L.append("type candidate struct {")
|
|
1256
|
+
L.append("\trule string")
|
|
1257
|
+
L.append("\tdefeater bool")
|
|
1258
|
+
L.append("\turn string")
|
|
1259
|
+
L.append("\tpositive bool")
|
|
1260
|
+
L.append("\trow []Val")
|
|
1261
|
+
L.append("\tkey string")
|
|
1262
|
+
L.append("}")
|
|
1263
|
+
L.append("")
|
|
1264
|
+
L.append("var cands = map[string]candidate{}")
|
|
1265
|
+
L.append("")
|
|
1266
|
+
L.append("func cand(rule string, defeater bool, urn string, positive bool, row []Val, subst []Val) {")
|
|
1267
|
+
L.append('\tk := rule + "|" + key(subst)')
|
|
1268
|
+
L.append("\tif _, ok := cands[k]; !ok {")
|
|
1269
|
+
L.append("\t\tcands[k] = candidate{rule, defeater, urn, positive, row, key(row)}")
|
|
1270
|
+
L.append("\t}")
|
|
1271
|
+
L.append("}")
|
|
1272
|
+
L.append("")
|
|
1273
|
+
L.append("// Приоритет §106: транзитивное замыкание безусловных рёбер «выше>ниже».")
|
|
1274
|
+
L.append("var PRIO = map[string]bool{" + ", ".join(json.dumps(f"{a}>{b}") + ": true" for a, b in plan["priorities"]) + "}")
|
|
1275
|
+
dated = "datedPriorities" in plan
|
|
1276
|
+
prep = dated or bool(plan.get("a3Params"))
|
|
1277
|
+
if prep:
|
|
1278
|
+
L.append("")
|
|
1279
|
+
L.append("type window struct{ start, end string }")
|
|
1280
|
+
L.append("")
|
|
1281
|
+
L.append("func inWindow(w window, t string) bool {")
|
|
1282
|
+
L.append('\treturn (w.start == "" || t >= w.start) && (w.end == "" || t < w.end)')
|
|
1283
|
+
L.append("}")
|
|
1284
|
+
if dated:
|
|
1285
|
+
L.append("")
|
|
1286
|
+
L.append("// DECISION-0269: датированные рёбра §92.3 — окна [start, end) оси права (пустая строка —")
|
|
1287
|
+
L.append("// бесконечность); активное множество готовится один раз на дело по legalTime (prepare).")
|
|
1288
|
+
L.append("type datedEdge struct {")
|
|
1289
|
+
L.append("\tpair string")
|
|
1290
|
+
L.append("\twindows []window")
|
|
1291
|
+
L.append("}")
|
|
1292
|
+
L.append("var datedPrio = []datedEdge{" + ", ".join(
|
|
1293
|
+
"{" + json.dumps(f"{a}>{b}") + ", []window{" + ", ".join(
|
|
1294
|
+
"{" + json.dumps(w[0] or "") + ", " + json.dumps(w[1] or "") + "}" for w in ws) + "}}"
|
|
1295
|
+
for a, b, ws in plan["datedPriorities"]) + "}")
|
|
1296
|
+
L.append("var prioActive = map[string]bool{}")
|
|
1297
|
+
L.append("")
|
|
1298
|
+
L.append("func activeEdges(base map[string]bool, dated []datedEdge, t string) map[string]bool {")
|
|
1299
|
+
L.append("\tout := map[string]bool{}")
|
|
1300
|
+
L.append("\tfor k := range base {")
|
|
1301
|
+
L.append("\t\tout[k] = true")
|
|
1302
|
+
L.append("\t}")
|
|
1303
|
+
L.append("\tfor _, e := range dated {")
|
|
1304
|
+
L.append("\t\tfor _, w := range e.windows {")
|
|
1305
|
+
L.append("\t\t\tif inWindow(w, t) {")
|
|
1306
|
+
L.append("\t\t\t\tout[e.pair] = true")
|
|
1307
|
+
L.append("\t\t\t\tbreak")
|
|
1308
|
+
L.append("\t\t\t}")
|
|
1309
|
+
L.append("\t\t}")
|
|
1310
|
+
L.append("\t}")
|
|
1311
|
+
L.append("\treturn out")
|
|
1312
|
+
L.append("}")
|
|
1313
|
+
L.append("")
|
|
1314
|
+
if prep:
|
|
1315
|
+
L.append("func prepare() {")
|
|
1316
|
+
L.append("\tt := legalTime()")
|
|
1317
|
+
L.append("\t_ = t")
|
|
1318
|
+
if dated:
|
|
1319
|
+
L.append("\tprioActive = activeEdges(PRIO, datedPrio, t)")
|
|
1320
|
+
if "datedNormPriorities" in plan:
|
|
1321
|
+
L.append("\tnormPrioActive = activeEdges(normPrio, datedNormPrio, t)")
|
|
1322
|
+
if plan.get("a3Params"):
|
|
1323
|
+
L.append("\t// DECISION-0273: значение параметра A3 на legalTime (подготовленный контекст).")
|
|
1324
|
+
L.append("\ta3Active = map[string]Val{}")
|
|
1325
|
+
L.append("\tfor id, ws := range a3Windows {")
|
|
1326
|
+
L.append("\t\tfor _, w := range ws {")
|
|
1327
|
+
L.append("\t\t\tif inWindow(window{w.start, w.end}, t) {")
|
|
1328
|
+
L.append("\t\t\t\ta3Active[id] = w.value")
|
|
1329
|
+
L.append("\t\t\t}")
|
|
1330
|
+
L.append("\t\t}")
|
|
1331
|
+
L.append("\t}")
|
|
1332
|
+
if prep:
|
|
1333
|
+
L.append("}")
|
|
1334
|
+
prio = "prioActive" if dated else "PRIO"
|
|
1335
|
+
L.append("")
|
|
1336
|
+
L.append("// Defeat §107 по ПОЛНОМУ снимку кандидатов страты; группа — ground-кортеж,")
|
|
1337
|
+
L.append("// побеждённые не удаляются по ходу обхода. Возвращает выживших §108 в")
|
|
1338
|
+
L.append("// порядке ключей: обход map в Go случаен, а порядок обязан быть устойчивым.")
|
|
1339
|
+
L.append("// DECISION-0274: применения страт для пути запроса судье — дефитеры, давшие")
|
|
1340
|
+
L.append("// применение (E-0103), и поражённые/уцелевшие поражаемые кандидаты (E-0100).")
|
|
1341
|
+
L.append("var applied []appliedCand")
|
|
1342
|
+
L.append("")
|
|
1343
|
+
L.append("func appliedCands() []appliedCand { return applied }")
|
|
1344
|
+
L.append("")
|
|
1345
|
+
L.append("func supportBits(urn string, row []Val) int {")
|
|
1346
|
+
L.append("\tm, ok := SUP[urn]")
|
|
1347
|
+
L.append("\tif !ok {")
|
|
1348
|
+
L.append("\t\treturn 0")
|
|
1349
|
+
L.append("\t}")
|
|
1350
|
+
L.append("\treturn m[key(row)]")
|
|
1351
|
+
L.append("}")
|
|
1352
|
+
L.append("")
|
|
1353
|
+
L.append(f'func prioHas(a, b string) bool {{ return {prio}[a+">"+b] }}')
|
|
1354
|
+
L.append("")
|
|
1355
|
+
L.append("func defeat() []candidate {")
|
|
1356
|
+
L.append("\tids := make([]string, 0, len(cands))")
|
|
1357
|
+
L.append("\tfor k := range cands {")
|
|
1358
|
+
L.append("\t\tids = append(ids, k)")
|
|
1359
|
+
L.append("\t}")
|
|
1360
|
+
L.append("\tsort.Strings(ids)")
|
|
1361
|
+
L.append("\tgroups := map[string][]candidate{}")
|
|
1362
|
+
L.append("\torder := []string{}")
|
|
1363
|
+
L.append("\tfor _, id := range ids {")
|
|
1364
|
+
L.append("\t\tc := cands[id]")
|
|
1365
|
+
L.append('\t\tg := c.urn + "\\x00" + c.key')
|
|
1366
|
+
L.append("\t\tif _, ok := groups[g]; !ok {")
|
|
1367
|
+
L.append("\t\t\torder = append(order, g)")
|
|
1368
|
+
L.append("\t\t}")
|
|
1369
|
+
L.append("\t\tgroups[g] = append(groups[g], c)")
|
|
1370
|
+
L.append("\t}")
|
|
1371
|
+
L.append("\tout := []candidate{}")
|
|
1372
|
+
L.append("\tsurvived := map[string]bool{}")
|
|
1373
|
+
L.append("\tfor _, gk := range order {")
|
|
1374
|
+
L.append("\t\tg := groups[gk]")
|
|
1375
|
+
L.append("\t\tfor _, c := range g {")
|
|
1376
|
+
L.append("\t\t\tif c.defeater {")
|
|
1377
|
+
L.append("\t\t\t\tcontinue")
|
|
1378
|
+
L.append("\t\t\t}")
|
|
1379
|
+
L.append("\t\t\t// §107.1: strict/asserted опора комплемента побеждает безусловно")
|
|
1380
|
+
L.append("\t\t\topp := bitSP")
|
|
1381
|
+
L.append("\t\t\tif c.positive {")
|
|
1382
|
+
L.append("\t\t\t\topp = bitSN")
|
|
1383
|
+
L.append("\t\t\t}")
|
|
1384
|
+
L.append("\t\t\tif SUP[c.urn][c.key]&opp != 0 {")
|
|
1385
|
+
L.append("\t\t\t\tcontinue")
|
|
1386
|
+
L.append("\t\t\t}")
|
|
1387
|
+
L.append("\t\t\tbeaten := false")
|
|
1388
|
+
L.append("\t\t\tfor _, o := range g {")
|
|
1389
|
+
L.append("\t\t\t\t// §107.2: противоположный обычный кандидат сильнее")
|
|
1390
|
+
L.append(f'\t\t\t\tif !o.defeater && o.positive != c.positive && {prio}[o.rule+">"+c.rule] {{')
|
|
1391
|
+
L.append("\t\t\t\t\tbeaten = true")
|
|
1392
|
+
L.append("\t\t\t\t}")
|
|
1393
|
+
L.append("\t\t\t\t// §107.3: дефитер на ту же полярность, не преодолённый приоритетом")
|
|
1394
|
+
L.append(f'\t\t\t\tif o.defeater && o.positive == c.positive && !{prio}[c.rule+">"+o.rule] {{')
|
|
1395
|
+
L.append("\t\t\t\t\tbeaten = true")
|
|
1396
|
+
L.append("\t\t\t\t}")
|
|
1397
|
+
L.append("\t\t\t}")
|
|
1398
|
+
L.append("\t\t\tif !beaten {")
|
|
1399
|
+
L.append("\t\t\t\tout = append(out, c)")
|
|
1400
|
+
L.append('\t\t\t\tsurvived[candKey(c.rule, c.urn, c.key, c.positive)] = true')
|
|
1401
|
+
L.append("\t\t\t}")
|
|
1402
|
+
L.append("\t\t}")
|
|
1403
|
+
L.append("\t}")
|
|
1404
|
+
L.append("\tfor _, id := range ids {")
|
|
1405
|
+
L.append("\t\tc := cands[id]")
|
|
1406
|
+
L.append('\t\tapplied = append(applied, appliedCand{c.rule, c.defeater, c.urn, c.positive, c.key, !c.defeater && !survived[candKey(c.rule, c.urn, c.key, c.positive)]})')
|
|
1407
|
+
L.append("\t}")
|
|
1408
|
+
L.append("\treturn out")
|
|
1409
|
+
L.append("}")
|
|
1410
|
+
L.append("")
|
|
1411
|
+
for r in plan["rules"]:
|
|
1412
|
+
if "steps" in r:
|
|
1413
|
+
_rule09(L, r, plan["planVersion"] in (SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19))
|
|
1414
|
+
continue
|
|
1415
|
+
L.append(f'// {r["id"]} ({r["strength"]})')
|
|
1416
|
+
L.append(f'func rule_{r["name"]}() bool {{')
|
|
1417
|
+
L.append("\tgrew := false")
|
|
1418
|
+
if r["window"]:
|
|
1419
|
+
if r["window"].get("start"):
|
|
1420
|
+
L.append(f'\tif legalTime() < "{r["window"]["start"]}" {{\n\t\treturn false\n\t}}')
|
|
1421
|
+
if r["window"].get("end"):
|
|
1422
|
+
L.append(f'\tif legalTime() >= "{r["window"]["end"]}" {{\n\t\treturn false\n\t}}')
|
|
1423
|
+
inner: list = []
|
|
1424
|
+
levels, late_joins, late_filters = _early(r)
|
|
1425
|
+
for (ai, aj, _at), (bi, bj, _bt) in late_joins:
|
|
1426
|
+
inner.append(f'if !cmp(r{ai}[{aj}], "eq", r{bi}[{bj}]) {{@continue@}}')
|
|
1427
|
+
for f in late_filters:
|
|
1428
|
+
inner.append(f'if !eq(r{f["row"]}[{f["col"]}], {term(f["term"])}) {{@continue@}}')
|
|
1429
|
+
for c in r["checks"]:
|
|
1430
|
+
args = ", ".join(term(a) for a in c["args"])
|
|
1431
|
+
predicate = json.dumps(c["pred"])
|
|
1432
|
+
inner.append(f'if {_accepts(f"truthOf({predicate}, []Val{{{args}}})", c["accept"])} {{@continue@}}')
|
|
1433
|
+
for g in r["guards"]:
|
|
1434
|
+
inner.append(f'if !cmp({term(g["l"])}, "{g["op"]}", {term(g["r"])}) {{@continue@}}')
|
|
1435
|
+
args = ", ".join(term(a) for a in r["head"]["args"])
|
|
1436
|
+
pos = r["head"]["polarity"] == "positive"
|
|
1437
|
+
if r["strength"] == "strict":
|
|
1438
|
+
inner.append(f'if addRow({json.dumps(r["head"]["pred"])}, []Val{{{args}}}, {"bitSP" if pos else "bitSN"}) {{@grew = true@}}')
|
|
1439
|
+
else:
|
|
1440
|
+
subst = ", ".join(term(a) for a in r["subst"])
|
|
1441
|
+
inner.append(f'cand({json.dumps(r["name"])}, {str(r["strength"] == "defeater").lower()}, '
|
|
1442
|
+
f'{json.dumps(r["head"]["pred"])}, {str(pos).lower()}, []Val{{{args}}}, []Val{{{subst}}})')
|
|
1443
|
+
early = {i: _go_checks(levels.get(i, [])) for i in range(len(r["atoms"]))}
|
|
1444
|
+
body_text = "".join(inner) + "".join(x for v in early.values() for x in v)
|
|
1445
|
+
ind = "\t"
|
|
1446
|
+
depth = 0
|
|
1447
|
+
if not r["atoms"]:
|
|
1448
|
+
L.append(f"{ind}for once := 0; once < 1; once++ {{")
|
|
1449
|
+
ind += "\t"
|
|
1450
|
+
depth += 1
|
|
1451
|
+
for i, a in enumerate(r["atoms"]):
|
|
1452
|
+
p = json.dumps(a["pred"])
|
|
1453
|
+
_go_wild_open(L, ind, i, a)
|
|
1454
|
+
L.append(f'{ind}for i{i}, r{i} := range TABLES[{p}] {{')
|
|
1455
|
+
ind += "\t"
|
|
1456
|
+
depth += 1
|
|
1457
|
+
if f"r{i}[" not in body_text:
|
|
1458
|
+
L.append(f'{ind}_ = r{i}')
|
|
1459
|
+
_block(L, ind, f'if {_accepts(f"maskStatus(SUP[{p}][RK[{p}][i{i}]])", a["accept"])} {{@continue@}}')
|
|
1460
|
+
for line in early[i]:
|
|
1461
|
+
_block(L, ind, line)
|
|
1462
|
+
_go_wild(L, ind, i, a)
|
|
1463
|
+
for line in inner:
|
|
1464
|
+
if "@" in line:
|
|
1465
|
+
_block(L, ind, line)
|
|
1466
|
+
else:
|
|
1467
|
+
L.append(ind + line)
|
|
1468
|
+
for _ in range(depth):
|
|
1469
|
+
ind = ind[:-1]
|
|
1470
|
+
L.append(ind + "}")
|
|
1471
|
+
L.append("\treturn grew")
|
|
1472
|
+
L.append("}")
|
|
1473
|
+
L.append("")
|
|
1474
|
+
L.append("// Страты строгого слоя §110 (DECISION-0088): каждая — наименьшая неподвижная точка.")
|
|
1475
|
+
L.append("var STRICT = [][]func() bool{" + ", ".join(
|
|
1476
|
+
"{" + ", ".join(f"rule_{n}" for n in s) + "}" for s in plan["strata"]["strict"]) + "}")
|
|
1477
|
+
waiting = _waiting_strata(L, plan, lambda name, strata: f"var {name} = [][]func() bool{{" + ", ".join(
|
|
1478
|
+
"{" + ", ".join(f"rule_{n}" for n in s) + "}" for s in strata) + "}", "//")
|
|
1479
|
+
L.append("")
|
|
1480
|
+
L.append("func strictClosure(strata [][]func() bool) {")
|
|
1481
|
+
L.append("\tfor _, stratum := range strata {")
|
|
1482
|
+
L.append("\t\tfor grew := true; grew; {")
|
|
1483
|
+
L.append("\t\t\tgrew = false")
|
|
1484
|
+
L.append("\t\t\tfor _, rule := range stratum {")
|
|
1485
|
+
L.append("\t\t\t\tif rule() {")
|
|
1486
|
+
L.append("\t\t\t\t\tgrew = true")
|
|
1487
|
+
L.append("\t\t\t\t}")
|
|
1488
|
+
L.append("\t\t\t}")
|
|
1489
|
+
L.append("\t\t}")
|
|
1490
|
+
L.append("\t}")
|
|
1491
|
+
L.append("}")
|
|
1492
|
+
L.append("")
|
|
1493
|
+
_closures09(L, plan)
|
|
1494
|
+
if v10:
|
|
1495
|
+
raise ValueError("NORM_RANKS_UNAVAILABLE")
|
|
1496
|
+
else:
|
|
1497
|
+
L += NORM_STUBS
|
|
1498
|
+
L.append("// Расписание фаз §111, решённое планом.")
|
|
1499
|
+
L.append("func saturate() {")
|
|
1500
|
+
if prep:
|
|
1501
|
+
L.append("\tprepare()")
|
|
1502
|
+
L.append("\tsurviving := []candidate{}")
|
|
1503
|
+
for ph in plan["schedule"]:
|
|
1504
|
+
kind = ph["phase"]
|
|
1505
|
+
if kind not in PHASES:
|
|
1506
|
+
raise ValueError(f"фаза расписания не поддержана эмиттером: {kind!r}")
|
|
1507
|
+
if kind == "strict":
|
|
1508
|
+
L.append(f"\tstrictClosure({waiting[tuple(ph['waiting'])] if ph.get('waiting') else 'STRICT'})")
|
|
1509
|
+
elif kind == "closure":
|
|
1510
|
+
if not ph.get("policies"):
|
|
1511
|
+
L.append("\t// §70: фаза closure зарезервирована (DECISION-0237 F7), политик в срезе нет")
|
|
1512
|
+
for name in ph.get("policies") or []:
|
|
1513
|
+
L.append(f"\t{name}()")
|
|
1514
|
+
elif kind == "candidates":
|
|
1515
|
+
L.append(f'\t// страта {ph["stratum"]}: кандидаты §111 шаги 2–3')
|
|
1516
|
+
L.append("\tcands = map[string]candidate{}")
|
|
1517
|
+
for n in ph["rules"]:
|
|
1518
|
+
L.append(f"\trule_{n}()")
|
|
1519
|
+
elif kind == "defeat":
|
|
1520
|
+
L.append("\tsurviving = defeat()")
|
|
1521
|
+
elif kind == "norms":
|
|
1522
|
+
L.append("\t// §135: позиции норм-правил после поражаемого слоя (DECISION-0242 K3)")
|
|
1523
|
+
for n in plan.get("norms") or []:
|
|
1524
|
+
L.append(f"\tnorm_{n['name']}()")
|
|
1525
|
+
elif kind == "effects":
|
|
1526
|
+
L.append("\tmaterializeEffects() // §127–§128")
|
|
1527
|
+
elif kind == "surviving":
|
|
1528
|
+
L.append("\tfor _, c := range surviving {")
|
|
1529
|
+
L.append("\t\tbit := bitDN")
|
|
1530
|
+
L.append("\t\tif c.positive {")
|
|
1531
|
+
L.append("\t\t\tbit = bitDP")
|
|
1532
|
+
L.append("\t\t}")
|
|
1533
|
+
L.append("\t\taddRow(c.urn, c.row, bit)")
|
|
1534
|
+
L.append("\t}")
|
|
1535
|
+
else:
|
|
1536
|
+
L.append(f'\t// страта {ph["stratum"]}: статус фиксирован, выводится при чтении (§111 шаг 7)')
|
|
1537
|
+
L.append("\t_ = surviving")
|
|
1538
|
+
L.append("}")
|
|
1539
|
+
L.append("")
|
|
1540
|
+
L.append("func register(urn string, row []Val) { addRow(urn, row, bitSP) }")
|
|
1541
|
+
L.append("")
|
|
1542
|
+
L.append("func registerNegative(urn string, row []Val) { addRow(urn, row, bitSN) }")
|
|
1543
|
+
L.append("")
|
|
1544
|
+
L.append("// KEY_CONFLICT §45.1/§196: считаются accepted-кортежи — strict/asserted positive-опоры.")
|
|
1545
|
+
L.append("func keyConflicts() int {")
|
|
1546
|
+
L.append("\tn := 0")
|
|
1547
|
+
L.append("\tfor urn, keys := range KEYS {")
|
|
1548
|
+
L.append("\t\tfor _, k := range keys {")
|
|
1549
|
+
L.append("\t\t\tgroups := map[string]int{}")
|
|
1550
|
+
L.append("\t\t\tfor i, row := range TABLES[urn] {")
|
|
1551
|
+
L.append("\t\t\t\tif SUP[urn][RK[urn][i]]&bitSP == 0 {")
|
|
1552
|
+
L.append("\t\t\t\t\tcontinue")
|
|
1553
|
+
L.append("\t\t\t\t}")
|
|
1554
|
+
L.append("\t\t\t\tproj := make([]Val, len(k))")
|
|
1555
|
+
L.append("\t\t\t\tfor j, c := range k {")
|
|
1556
|
+
L.append("\t\t\t\t\tproj[j] = row[c]")
|
|
1557
|
+
L.append("\t\t\t\t}")
|
|
1558
|
+
L.append("\t\t\t\tgroups[key(proj)]++")
|
|
1559
|
+
L.append("\t\t\t}")
|
|
1560
|
+
L.append("\t\t\tfor _, c := range groups {")
|
|
1561
|
+
L.append("\t\t\t\tif c > 1 {")
|
|
1562
|
+
L.append("\t\t\t\t\tn++")
|
|
1563
|
+
L.append("\t\t\t\t}")
|
|
1564
|
+
L.append("\t\t\t}")
|
|
1565
|
+
L.append("\t\t}")
|
|
1566
|
+
L.append("\t}")
|
|
1567
|
+
L.append("\treturn n")
|
|
1568
|
+
L.append("}")
|
|
1569
|
+
L.append("func keyConflictPredicates() map[string]bool {")
|
|
1570
|
+
L.append("\tout := map[string]bool{}")
|
|
1571
|
+
L.append("\tfor urn, keys := range KEYS { rows := [][]Val{}; for i, row := range TABLES[urn] { if SUP[urn][RK[urn][i]]&bitSP != 0 { rows = append(rows, row) } }; for _, k := range keys { groups := map[string]int{}; for _, row := range rows { proj := make([]Val, len(k)); for i, c := range k { proj[i] = row[c] }; groups[key(proj)]++ }; for _, count := range groups { if count > 1 { out[urn] = true } } } }")
|
|
1572
|
+
L.append("\treturn out")
|
|
1573
|
+
L.append("}")
|
|
1574
|
+
L.append("")
|
|
1575
|
+
L.append("// Собственные факты пакета; reset возвращает модель к ним (чистое Evaluate API).")
|
|
1576
|
+
L.append("func packageFacts() {")
|
|
1577
|
+
for f in plan["facts"]:
|
|
1578
|
+
bit = "bitSP" if f.get("polarity", "positive") == "positive" else "bitSN"
|
|
1579
|
+
L.append(f'\taddRow({json.dumps(f["pred"])}, []Val{{' + ", ".join(term(a) for a in f["args"]) + f"}}, {bit})")
|
|
1580
|
+
L.append("}")
|
|
1581
|
+
L.append("")
|
|
1582
|
+
L.append("func reset() {")
|
|
1583
|
+
L.append("\tfor urn := range TABLES {")
|
|
1584
|
+
L.append("\t\tTABLES[urn] = TABLES[urn][:0]")
|
|
1585
|
+
L.append("\t\tRK[urn] = RK[urn][:0]")
|
|
1586
|
+
L.append("\t\tSUP[urn] = map[string]int{}")
|
|
1587
|
+
L.append("\t}")
|
|
1588
|
+
L.append("\tcands = map[string]candidate{}")
|
|
1589
|
+
L.append("\tapplied = nil")
|
|
1590
|
+
if v09:
|
|
1591
|
+
L.append("\tRULE_ISSUES = []string{}")
|
|
1592
|
+
L.append("\tissueSeen = map[string]bool{}")
|
|
1593
|
+
if plan["planVersion"] in (SUPPORTED_PLAN_12, SUPPORTED_PLAN_13, SUPPORTED_PLAN_14, SUPPORTED_PLAN_15, SUPPORTED_PLAN_16, SUPPORTED_PLAN_17, SUPPORTED_PLAN_18, SUPPORTED_PLAN_19):
|
|
1594
|
+
L.append("\tkindSeen = map[string]bool{}")
|
|
1595
|
+
if v10:
|
|
1596
|
+
L.append("\tpositions = nil")
|
|
1597
|
+
L.append("\tblocked = map[string]bool{}")
|
|
1598
|
+
L.append("\tblockedImm = map[string]bool{}")
|
|
1599
|
+
L.append("\tnormIssueList = nil")
|
|
1600
|
+
L.append("\tnormSeen = map[string]bool{}")
|
|
1601
|
+
L.append("\tnormSeq = map[string]int{}")
|
|
1602
|
+
L.append("\tpackageFacts()")
|
|
1603
|
+
L.append("}")
|
|
1604
|
+
return _factory("\n".join(L) + "\n", plan)
|
|
1605
|
+
|
|
1606
|
+
# --- поверхность API (DECISION-0234) ----------------------------------------
|
|
1607
|
+
# Вопрос — pub-отношение с производящим правилом (план решает, какие);
|
|
1608
|
+
# вход — эмпирический предикат или предикат без производителя, достижимый
|
|
1609
|
+
# из вопросов. Обёртка семантики не несёт: она строит факты, зовёт модель
|
|
1610
|
+
# и читает таблицу вопроса — то же, что делает раннер.
|
|
1611
|
+
|
|
1612
|
+
def _api(plan: dict) -> dict:
|
|
1613
|
+
return plan.get("api") or {"questions": [], "inputs": []}
|
|
1614
|
+
|
|
1615
|
+
|
|
1616
|
+
def api_doc(entry: dict) -> list:
|
|
1617
|
+
"""Строки комментария над функцией API: метки отношения, затем по
|
|
1618
|
+
каждому производящему правилу — его метка и якоря `локатор · редакция`.
|
|
1619
|
+
Текст — из CLIR как есть; комментарий не переводится и не сокращается."""
|
|
1620
|
+
lines = []
|
|
1621
|
+
for l in entry.get("labels", [])[:2]:
|
|
1622
|
+
lines.append(f'{l["text"]} [{l["language"]}, {l["status"]}]')
|
|
1623
|
+
for src in entry.get("sources", []):
|
|
1624
|
+
head = src["rule"]
|
|
1625
|
+
if src["labels"]:
|
|
1626
|
+
head += ": " + src["labels"][0]["text"]
|
|
1627
|
+
lines.append(head)
|
|
1628
|
+
for a in src["anchors"]:
|
|
1629
|
+
where = a.get("locator") or a["id"].split("#")[-1]
|
|
1630
|
+
lines.append(" @source " + where + (f" · {a['edition']}" if a.get("edition") else ""))
|
|
1631
|
+
return lines
|
|
1632
|
+
|
|
1633
|
+
|
|
1634
|
+
def _api_words(name: str) -> list:
|
|
1635
|
+
"""Слова нейтрального имени API: части между `_` и горбы camelCase с учётом
|
|
1636
|
+
регистра букв Unicode — основа имени на языке цели (DECISION-0423 §2.7)."""
|
|
1637
|
+
words = []
|
|
1638
|
+
for part in name.split("_"):
|
|
1639
|
+
if not part:
|
|
1640
|
+
continue
|
|
1641
|
+
cur = part[0]
|
|
1642
|
+
for prev, ch, nxt in zip(part, part[1:], part[2:] + " "):
|
|
1643
|
+
if ch.isupper() and (prev.islower() or prev.isdigit() or (prev.isupper() and nxt.islower())):
|
|
1644
|
+
words.append(cur)
|
|
1645
|
+
cur = ch
|
|
1646
|
+
else:
|
|
1647
|
+
cur += ch
|
|
1648
|
+
words.append(cur)
|
|
1649
|
+
return words
|
|
1650
|
+
|
|
1651
|
+
|
|
1652
|
+
def go_name(name: str) -> str:
|
|
1653
|
+
"""Основа имени Go в PascalCase: только буквы Unicode и десятичные цифры —
|
|
1654
|
+
идентификатор Go не принимает комбинирующие знаки и прочие цифры."""
|
|
1655
|
+
import unicodedata
|
|
1656
|
+
base = "".join(w[:1].upper() + w[1:] for w in _api_words(name))
|
|
1657
|
+
return "".join(c for c in base if unicodedata.category(c)[0] == "L" or unicodedata.category(c) == "Nd") or "X"
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def api_names(entries: list) -> dict:
|
|
1661
|
+
"""Нейтральное имя плана → основа имени функции Go (`Fact`/`Ask`/`Rows`
|
|
1662
|
+
+ основа). Совпадения после преобразования разводятся суффиксом `2`, `3`
|
|
1663
|
+
в порядке плана (отсортирован по StableId), поэтому имена детерминированы."""
|
|
1664
|
+
out, used = {}, set()
|
|
1665
|
+
for e in entries:
|
|
1666
|
+
base = go_name(e["name"])
|
|
1667
|
+
name, n = base, 1
|
|
1668
|
+
while name in used:
|
|
1669
|
+
n += 1
|
|
1670
|
+
name = f"{base}{n}"
|
|
1671
|
+
used.add(name)
|
|
1672
|
+
out[e["name"]] = name
|
|
1673
|
+
return out
|
|
1674
|
+
|
|
1675
|
+
|
|
1676
|
+
def emit_api(plan: dict) -> str:
|
|
1677
|
+
"""API порождённого модуля (DECISION-0234, DECISION-0423): конструкторы
|
|
1678
|
+
фактов по входам, функции вопросов и реестры по имени поверх `apiBase.go`,
|
|
1679
|
+
общего с единым входом `evaluate`; ответ — `Answer` одной формы у L0 и L1,
|
|
1680
|
+
отказ и ошибка формы — значения `error`. Глобального состояния и мьютекса
|
|
1681
|
+
нет. Над каждой функцией — метки отношения и якоря `@source`."""
|
|
1682
|
+
check_plan(plan)
|
|
1683
|
+
api = _api(plan)
|
|
1684
|
+
negation = "negation" in (plan.get("capabilities") or ())
|
|
1685
|
+
L = ["package main", "",
|
|
1686
|
+
"// Поверхность API порождённого модуля (DECISION-0234, DECISION-0423).",
|
|
1687
|
+
"//",
|
|
1688
|
+
"// Факты строятся функциями Fact* (и Negated* у модели L1) из литерал-термов §53 —",
|
|
1689
|
+
"// конструкторов IntegerOf, DecimalOf, MoneyOf, TextOf, DateOf, EntityOf и других — либо из",
|
|
1690
|
+
"// значений языка, однозначных по объявленному типу параметра. Вопрос Ask* —",
|
|
1691
|
+
"// чистая функция: каждый вызов строит своё состояние модели и возвращает Answer",
|
|
1692
|
+
"// либо ошибку (*InputError, *RefusedError). Вызовы из горутин независимы.", ""]
|
|
1693
|
+
def meta(entry):
|
|
1694
|
+
return (f'{json.dumps(entry["pred"])}, []string{{{", ".join(json.dumps(t) for t in entry["params"])}}}, '
|
|
1695
|
+
f'[]string{{{", ".join(json.dumps(k) for k in entry["kinds"])}}}')
|
|
1696
|
+
inp, qs = api_names(api["inputs"]), api_names(api["questions"])
|
|
1697
|
+
for i in api["inputs"]:
|
|
1698
|
+
n = len(i["params"])
|
|
1699
|
+
params = ", ".join(f"a{j}" for j in range(n)) + (" interface{}" if n else "")
|
|
1700
|
+
call = ", ".join(f"a{j}" for j in range(n))
|
|
1701
|
+
for d in api_doc(i):
|
|
1702
|
+
L.append("// " + d)
|
|
1703
|
+
L.append(f"func Fact{inp[i['name']]}({params}) Fact {{")
|
|
1704
|
+
L.append(f'\treturn makeFact({meta(i)}, []interface{{}}{{{call}}}, "Fact{inp[i["name"]]}", false)')
|
|
1705
|
+
L.append("}")
|
|
1706
|
+
L.append("")
|
|
1707
|
+
if negation:
|
|
1708
|
+
L.append("// Отрицательный факт §113 того же входа.")
|
|
1709
|
+
L.append(f"func Negated{inp[i['name']]}({params}) Fact {{")
|
|
1710
|
+
L.append(f'\treturn makeFact({meta(i)}, []interface{{}}{{{call}}}, "Negated{inp[i["name"]]}", true)')
|
|
1711
|
+
L.append("}")
|
|
1712
|
+
L.append("")
|
|
1713
|
+
for q in api["questions"]:
|
|
1714
|
+
n = len(q["params"])
|
|
1715
|
+
params = "".join(f", a{j}" for j in range(n)) + (" interface{}" if n else "")
|
|
1716
|
+
call = ", ".join(f"a{j}" for j in range(n))
|
|
1717
|
+
for d in api_doc(q):
|
|
1718
|
+
L.append("// " + d)
|
|
1719
|
+
L.append(f"func Ask{qs[q['name']]}(facts []Fact, on string{params}) (Answer, error) {{")
|
|
1720
|
+
L.append(f'\treturn askAPI({meta(q)}, facts, on, []interface{{}}{{{call}}}, {json.dumps(q["name"])}, CallOptions{{}})')
|
|
1721
|
+
L.append("}")
|
|
1722
|
+
L.append("")
|
|
1723
|
+
L.append(f"// Установленные строки {q['name']} литерал-термами §53.")
|
|
1724
|
+
L.append(f"func Rows{qs[q['name']]}(facts []Fact, on string) (Answer, error) {{")
|
|
1725
|
+
L.append(f'\treturn rowsAPI({json.dumps(q["pred"])}, facts, on, {json.dumps(q["name"])}, CallOptions{{}})')
|
|
1726
|
+
L.append("}")
|
|
1727
|
+
L.append("")
|
|
1728
|
+
L.append("// Реестры по имени — для обвязки и проверки: вопрос и вход по строке.")
|
|
1729
|
+
L.append("var Questions = map[string]func(facts []Fact, on string, args []interface{}, opts CallOptions) (Answer, error){")
|
|
1730
|
+
for q in api["questions"]:
|
|
1731
|
+
L.append(f'\t{json.dumps(q["name"])}: func(facts []Fact, on string, args []interface{{}}, opts CallOptions) (Answer, error) {{')
|
|
1732
|
+
L.append(f'\t\treturn askAPI({meta(q)}, facts, on, args, {json.dumps(q["name"])}, opts)')
|
|
1733
|
+
L.append("\t},")
|
|
1734
|
+
L.append("}")
|
|
1735
|
+
L.append("")
|
|
1736
|
+
L.append("var Inputs = map[string]func(args ...interface{}) Fact{")
|
|
1737
|
+
for i in api["inputs"]:
|
|
1738
|
+
L.append(f'\t{json.dumps(i["name"])}: func(args ...interface{{}}) Fact {{ return makeFact({meta(i)}, args, "Inputs[{i["name"]}]", false) }},')
|
|
1739
|
+
L.append("}")
|
|
1740
|
+
L.append("")
|
|
1741
|
+
L.append("var Negated = map[string]func(args ...interface{}) Fact{")
|
|
1742
|
+
if negation:
|
|
1743
|
+
for i in api["inputs"]:
|
|
1744
|
+
L.append(f'\t{json.dumps(i["name"])}: func(args ...interface{{}}) Fact {{ return makeFact({meta(i)}, args, "Negated[{i["name"]}]", true) }},')
|
|
1745
|
+
L.append("}")
|
|
1746
|
+
return "\n".join(L) + "\n"
|
|
1747
|
+
|
|
1748
|
+
# --- дескриптор цели ---------------------------------------------------------
|
|
1749
|
+
import shutil as _shutil
|
|
1750
|
+
from pathlib import Path as _Path
|
|
1751
|
+
|
|
1752
|
+
HERE = _Path(__file__).resolve().parent
|
|
1753
|
+
EXT = "go"
|
|
1754
|
+
MODEL = "model.go"
|
|
1755
|
+
# Минимальная версия Go (строка `go` в go.mod, DECISION-0423 §2.7) — версия
|
|
1756
|
+
# проверок CI (`.github/workflows/candidate.yml`), а не машины генератора.
|
|
1757
|
+
TOOLCHAIN_MIN = "1.25"
|
|
1758
|
+
API = "api.go"
|
|
1759
|
+
# Виды вопросов, которые исполняет раннер цели; ворота исполняют вектор
|
|
1760
|
+
# только когда его вид поддержан ВСЕМИ активными целями.
|
|
1761
|
+
QUERY_KINDS = {"truth", "term", "collect"}
|
|
1762
|
+
|
|
1763
|
+
|
|
1764
|
+
def available():
|
|
1765
|
+
import shutil as _sh
|
|
1766
|
+
go = _sh.which("go")
|
|
1767
|
+
return (True, go) if go else (False, "нет go в PATH")
|
|
1768
|
+
|
|
1769
|
+
MAINS = {"runner": "runner.go", "cert": "cert.go", "apicheck": "apicheck.go"}
|
|
1770
|
+
|
|
1771
|
+
|
|
1772
|
+
def layout(proj, main: str) -> list:
|
|
1773
|
+
proj = _Path(proj)
|
|
1774
|
+
proj.mkdir(parents=True, exist_ok=True)
|
|
1775
|
+
out = []
|
|
1776
|
+
names = ("runtime.go", MAINS[main]) if main == "cert" else ("runtime.go", "evaluate.go", "apiBase.go", MAINS[main])
|
|
1777
|
+
for name in names:
|
|
1778
|
+
_shutil.copyfile(HERE / name, proj / name)
|
|
1779
|
+
out.append(proj / name)
|
|
1780
|
+
(proj / "go.mod").write_text(f"module lawgen\n\ngo {TOOLCHAIN_MIN}\n", encoding="utf-8")
|
|
1781
|
+
out.append(proj / "go.mod")
|
|
1782
|
+
return out
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
def build(proj):
|
|
1786
|
+
return ["go", "build", "-o", "app", "."], str(proj)
|
|
1787
|
+
|
|
1788
|
+
|
|
1789
|
+
def build_api(proj):
|
|
1790
|
+
"""Харнесс API — второй бинарь того же каталога под тегом `apicheck`."""
|
|
1791
|
+
return ["go", "build", "-tags", "apicheck", "-o", "apicheck", "."], str(proj)
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
def run(proj, main: str):
|
|
1795
|
+
if main == "apicheck":
|
|
1796
|
+
return [str(_Path(proj) / "apicheck")], None
|
|
1797
|
+
return [str(_Path(proj) / "app")], None
|
|
1798
|
+
|
|
1799
|
+
|
|
1800
|
+
if __name__ == "__main__":
|
|
1801
|
+
import sys
|
|
1802
|
+
from pathlib import Path
|
|
1803
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
|
1804
|
+
import plan as P
|
|
1805
|
+
ir = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
1806
|
+
Path(sys.argv[2]).write_text(emit(P.build_plan(ir)), encoding="utf-8")
|