kairos-lang 1.0.0
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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +260 -0
- package/dist/ast.d.ts +142 -0
- package/dist/ast.js +1 -0
- package/dist/cli.d.ts +55 -0
- package/dist/cli.js +309 -0
- package/dist/eval.d.ts +438 -0
- package/dist/eval.js +3198 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +64 -0
- package/dist/lexer.d.ts +33 -0
- package/dist/lexer.js +171 -0
- package/dist/parser.d.ts +11 -0
- package/dist/parser.js +644 -0
- package/dist/tz.d.ts +57 -0
- package/dist/tz.js +216 -0
- package/package.json +63 -0
- package/stdlib/fiscal.kairos +12 -0
- package/stdlib/gregorian.kairos +36 -0
- package/stdlib/isoweek.kairos +16 -0
package/dist/eval.js
ADDED
|
@@ -0,0 +1,3198 @@
|
|
|
1
|
+
// Kairos 評価器(プロトタイプ)
|
|
2
|
+
//
|
|
3
|
+
// 設計:
|
|
4
|
+
// - core 演算子だけを実装し、Gregorian は stdlib/gregorian.kairos(Kairos ソース)を評価して立てる。
|
|
5
|
+
// - 時間ストリームは計算範囲 [紀元 1970-01-01, to+マージン) で有界に実体化する(I7 の遅延・無限は
|
|
6
|
+
// プロトタイプでは近似。範囲外・1970 以前は未対応)。
|
|
7
|
+
// - TZ は固定オフセットのみ(DST 未対応)。市民日=24h に縮退する。
|
|
8
|
+
// - カレンダー実体(ADR-35)と整列検査(ADR-36)は実装済み——整列タグは評価時伝播(静的検査の近似)で、
|
|
9
|
+
// tz 成分は名札(幾何はグローバル固定オフセット)。
|
|
10
|
+
// - 範囲外出自(ADR-37)は実装済み——値随伴の区間註釈(輸送表 §4.10)・実効被覆域の分類器・
|
|
11
|
+
// 被覆主張・被覆サマリ。クリップは runProgram の結果組み立てで一度だけ(判断 8)。
|
|
12
|
+
// - 窓所属の述語 coincides(仮称)と stride の入力相対(ADR-38)は実装済み——証人規則の三分岐・
|
|
13
|
+
// tz 静的検査・filter 輸送の逆像拡幅(F75)・stride の n 域検査。
|
|
14
|
+
// - 日付ラベル保存の再錨 rebase(仮称・ADR-40)と免除系の tz 名検査(ADR-36 改訂 2)は実装済み。
|
|
15
|
+
// - 営業時間の供給規約と標準導出(ADR-41)は実装済み——sessionOpens/sessionCloses の対宣言・整合性検査
|
|
16
|
+
// (結合実効被覆域∩実体化範囲の局所交互・端の切り欠き・同時刻対の文脈順序)・実体相対の
|
|
17
|
+
// bizOpen/bizClose/isOpen(証人三分岐・実体キーのメモ化)。時刻付き anchor/from: の日内
|
|
18
|
+
// オフセットは壁時計ラベル読み(ADR-31 改訂 2=F87 の修正)。
|
|
19
|
+
// - 未実装: Modified ロール・everyInstant の一般合成・前文 tz: の写像評価。詳細は README。
|
|
20
|
+
import { getTz } from "./tz.js";
|
|
21
|
+
import { parseCoveringText } from "./parser.js";
|
|
22
|
+
export class KairosError extends Error {
|
|
23
|
+
}
|
|
24
|
+
/** 供給エラー(ADR-46 判断 7 (a)): 解決失敗の機械可読な部分類。実装系(発報層等)は本分類を
|
|
25
|
+
* boot throw から除外して劣化運転に落としてよい——契約違反(KairosError)とは区別される */
|
|
26
|
+
export class SupplyError extends KairosError {
|
|
27
|
+
}
|
|
28
|
+
const DAY_MS = 86_400_000;
|
|
29
|
+
/** 範囲外分類の失敗(ADR-37 判断 6): 失敗した参照点が依存の実効被覆域の外。
|
|
30
|
+
* ストリーム文脈(filter の述語)は捕まえて「落として註釈」、純値文脈はそのまま
|
|
31
|
+
* 範囲外分類の明示エラーとして表面化する(「値なし」スロットは導入しない) */
|
|
32
|
+
export class OutOfCoverageSignal extends KairosError {
|
|
33
|
+
entries;
|
|
34
|
+
/** 述語が読んだ窓の区間(窓越し参照=coincides 等)。filter の輸送はこの逆像へ拡幅する(F75・ADR-37 改訂 2) */
|
|
35
|
+
window;
|
|
36
|
+
constructor(msg, entries, window) {
|
|
37
|
+
super(msg);
|
|
38
|
+
this.entries = entries;
|
|
39
|
+
this.window = window;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** 空テーブルの整列=空虚適合(ADR-45・F98): 違反しうる点が無いため全ての整列に空虚に適合する
|
|
43
|
+
* 第三状態。「なし」(主張できない=検査に落ちる)と別で、検査には**通り**、結合では相手の整列を
|
|
44
|
+
* 継承する。tz 名検査(checkTzMembership)は kind が civil でないため自動で素通し(点ゼロに
|
|
45
|
+
* 1 日ずれは起き得ない) */
|
|
46
|
+
const VACUOUS_GRAIN = { kind: 'vacuous', step: 0, phase: 0, off: 0, tz: '' };
|
|
47
|
+
const isVacuous = (a) => a?.kind === 'vacuous';
|
|
48
|
+
function gridEq(a, b) {
|
|
49
|
+
if (isVacuous(a) || isVacuous(b))
|
|
50
|
+
return true; // 空虚適合は相手が「なし」でも通す(ADR-45)
|
|
51
|
+
return !!a && !!b && a.kind === b.kind && a.step === b.step && a.phase === b.phase
|
|
52
|
+
&& a.off === b.off && a.tz === b.tz;
|
|
53
|
+
}
|
|
54
|
+
const gridDesc = (a) => isVacuous(a) ? '空虚適合(空テーブル)'
|
|
55
|
+
: a ? `${a.kind === 'civil' ? `市民日 ${a.step}d` : `経過 ${a.step}ms`} 位相 ${a.phase}`
|
|
56
|
+
+ `${a.off ? ` 日内 +${a.off}ms` : ''}${a.tz ? ` tz "${a.tz}"` : ''}` : 'なし';
|
|
57
|
+
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
58
|
+
/** 値型(数値・列挙名/文字列・論理)か——適用の引数型 dispatch(ADR-42 判断 4) */
|
|
59
|
+
const isValueV = (v) => typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean';
|
|
60
|
+
const kindOf = (v) => Array.isArray(v) ? 'list' : typeof v === 'object' && v !== null ? v.k : typeof v;
|
|
61
|
+
export class PremiseInstance {
|
|
62
|
+
name;
|
|
63
|
+
members;
|
|
64
|
+
defs;
|
|
65
|
+
base;
|
|
66
|
+
constructor(name, members, defs, base) {
|
|
67
|
+
this.name = name;
|
|
68
|
+
this.members = members;
|
|
69
|
+
this.defs = defs;
|
|
70
|
+
this.base = base;
|
|
71
|
+
}
|
|
72
|
+
findDef(name) {
|
|
73
|
+
return this.defs.get(name) ?? this.base?.findDef(name);
|
|
74
|
+
}
|
|
75
|
+
/** 定義側 premise の特定(external のスナップショット単位=ADR-46 判断 5。member 解決規則の
|
|
76
|
+
* 「定義側優先」〈ADR-35 判断 8〉と同じ向きの探索) */
|
|
77
|
+
findDefOwner(name) {
|
|
78
|
+
const def = this.defs.get(name);
|
|
79
|
+
if (def)
|
|
80
|
+
return { def, owner: this };
|
|
81
|
+
return this.base?.findDefOwner(name);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function childEnv(env, locals) {
|
|
85
|
+
return { ...env, locals, parent: env };
|
|
86
|
+
}
|
|
87
|
+
/** 区間註釈の正準一行表示。CLI と doctest(`#~>` 照合)で共有——書式のズレを封じる */
|
|
88
|
+
export function formatAnnotation(a) {
|
|
89
|
+
return `範囲外 ${a.from}..${a.to}(${a.source} covering ${a.covering}${a.asof ? `, asof ${a.asof}` : ''})`;
|
|
90
|
+
}
|
|
91
|
+
export class Runtime {
|
|
92
|
+
premises = new Map();
|
|
93
|
+
topBindings = new Map();
|
|
94
|
+
warnings = [];
|
|
95
|
+
/** external の解決子(ADR-46。RunOptions.resolve から) */
|
|
96
|
+
resolver;
|
|
97
|
+
/** 被覆サマリの収集(ADR-37 判断 7 (b)): 評価が参照した各データ源・被覆主張 */
|
|
98
|
+
coverage = new Map();
|
|
99
|
+
vocab = new Set(['Following', 'Preceding', 'Modified', 'latest',
|
|
100
|
+
'clip', 'drop', 'error', 'keep']);
|
|
101
|
+
tzName; // 実行既定 tz(CLI/API 引数。前文 tz: が在圏で上書きする)
|
|
102
|
+
tz; // 実行既定 tz の幾何(表示・from/to 解決)
|
|
103
|
+
epoch; // 1970-01-01T00:00(実行既定 tz)の UTC ms=実体化の下限
|
|
104
|
+
computeEnd;
|
|
105
|
+
fromMs;
|
|
106
|
+
toMs;
|
|
107
|
+
constructor(fromMs, toMs, tz) {
|
|
108
|
+
this.fromMs = fromMs;
|
|
109
|
+
this.toMs = toMs;
|
|
110
|
+
this.tzName = tz;
|
|
111
|
+
this.tz = getTz(tz);
|
|
112
|
+
this.epoch = this.tz.civilDayStart(1970, 1, 1);
|
|
113
|
+
this.computeEnd = toMs + 400 * DAY_MS;
|
|
114
|
+
if (fromMs < this.epoch)
|
|
115
|
+
throw new KairosError('プロトタイプの評価範囲は 1970-01-01 以降');
|
|
116
|
+
}
|
|
117
|
+
/** 実行既定 tz での錨打ち(表示・asof 等の寛容な文脈。隙間→最初の瞬間・重複→最初の候補) */
|
|
118
|
+
dateToMs(d) {
|
|
119
|
+
const local = Date.UTC(d.y, d.mo - 1, d.d, d.h, d.mi, Math.floor(d.s), Math.round((d.s % 1) * 1000));
|
|
120
|
+
return this.tz.anchor(local).ms;
|
|
121
|
+
}
|
|
122
|
+
/** 表示形(実行既定 tz の市民ラベル) */
|
|
123
|
+
fmt(ms) {
|
|
124
|
+
return this.tz.format(ms);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// ---- 補助: 区間・点列 ----
|
|
128
|
+
/** start でソート済みの区間列から ms を含む区間の添字(なければ -1) */
|
|
129
|
+
function ivIndexOf(iv, ms) {
|
|
130
|
+
let lo = 0, hi = iv.length - 1, ans = -1;
|
|
131
|
+
while (lo <= hi) {
|
|
132
|
+
const mid = (lo + hi) >> 1;
|
|
133
|
+
if (iv[mid].start <= ms) {
|
|
134
|
+
ans = mid;
|
|
135
|
+
lo = mid + 1;
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
hi = mid - 1;
|
|
139
|
+
}
|
|
140
|
+
return ans >= 0 && ms < iv[ans].end ? ans : -1;
|
|
141
|
+
}
|
|
142
|
+
function ptIndexOf(pts, ms) {
|
|
143
|
+
let lo = 0, hi = pts.length - 1;
|
|
144
|
+
while (lo <= hi) {
|
|
145
|
+
const mid = (lo + hi) >> 1;
|
|
146
|
+
if (pts[mid] === ms)
|
|
147
|
+
return mid;
|
|
148
|
+
if (pts[mid] < ms)
|
|
149
|
+
lo = mid + 1;
|
|
150
|
+
else
|
|
151
|
+
hi = mid - 1;
|
|
152
|
+
}
|
|
153
|
+
return -1;
|
|
154
|
+
}
|
|
155
|
+
/** ソート済み点列で ms より大きい最初の添字(なければ length) */
|
|
156
|
+
function ptUpperBound(pts, ms) {
|
|
157
|
+
let lo = 0, hi = pts.length;
|
|
158
|
+
while (lo < hi) {
|
|
159
|
+
const mid = (lo + hi) >> 1;
|
|
160
|
+
if (pts[mid] <= ms)
|
|
161
|
+
lo = mid + 1;
|
|
162
|
+
else
|
|
163
|
+
hi = mid;
|
|
164
|
+
}
|
|
165
|
+
return lo;
|
|
166
|
+
}
|
|
167
|
+
/** ソート済み点列の集合演算 */
|
|
168
|
+
function ptUnion(a, b) {
|
|
169
|
+
const out = [];
|
|
170
|
+
let i = 0, j = 0;
|
|
171
|
+
while (i < a.length || j < b.length) {
|
|
172
|
+
if (j >= b.length || (i < a.length && a[i] < b[j]))
|
|
173
|
+
out.push(a[i++]);
|
|
174
|
+
else if (i >= a.length || b[j] < a[i])
|
|
175
|
+
out.push(b[j++]);
|
|
176
|
+
else {
|
|
177
|
+
out.push(a[i]);
|
|
178
|
+
i++;
|
|
179
|
+
j++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
function ptInter(a, b) {
|
|
185
|
+
const out = [];
|
|
186
|
+
let i = 0, j = 0;
|
|
187
|
+
while (i < a.length && j < b.length) {
|
|
188
|
+
if (a[i] < b[j])
|
|
189
|
+
i++;
|
|
190
|
+
else if (b[j] < a[i])
|
|
191
|
+
j++;
|
|
192
|
+
else {
|
|
193
|
+
out.push(a[i]);
|
|
194
|
+
i++;
|
|
195
|
+
j++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
function ptDiff(a, b) {
|
|
201
|
+
const out = [];
|
|
202
|
+
let i = 0, j = 0;
|
|
203
|
+
while (i < a.length) {
|
|
204
|
+
while (j < b.length && b[j] < a[i])
|
|
205
|
+
j++;
|
|
206
|
+
if (j >= b.length || b[j] !== a[i])
|
|
207
|
+
out.push(a[i]);
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
// ---- 補助: 区間列(±Infinity 可)と評価註釈(ADR-37) ----
|
|
213
|
+
/** 区間列を整列・併合した正規形 */
|
|
214
|
+
function mergeIv(iv) {
|
|
215
|
+
const s = iv.filter(x => x.end > x.start).sort((a, b) => a.start - b.start);
|
|
216
|
+
const out = [];
|
|
217
|
+
for (const x of s) {
|
|
218
|
+
const last = out[out.length - 1];
|
|
219
|
+
if (last && x.start <= last.end)
|
|
220
|
+
last.end = Math.max(last.end, x.end);
|
|
221
|
+
else
|
|
222
|
+
out.push({ ...x });
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
/** 全時間 (-∞, +∞) に対する区間列の補集合 */
|
|
227
|
+
function complementIv(iv) {
|
|
228
|
+
const m = mergeIv(iv);
|
|
229
|
+
const out = [];
|
|
230
|
+
let cur = -Infinity;
|
|
231
|
+
for (const x of m) {
|
|
232
|
+
if (x.start > cur)
|
|
233
|
+
out.push({ start: cur, end: x.start });
|
|
234
|
+
cur = Math.max(cur, x.end);
|
|
235
|
+
}
|
|
236
|
+
if (cur < Infinity)
|
|
237
|
+
out.push({ start: cur, end: Infinity });
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
function ivContainsPoint(iv, ms) {
|
|
241
|
+
return iv.some(x => x.start <= ms && ms < x.end);
|
|
242
|
+
}
|
|
243
|
+
/** c ⊆ ∪iv か(被覆主張の必要条件検査・ADR-37 判断 5) */
|
|
244
|
+
function ivCovers(iv, c) {
|
|
245
|
+
return mergeIv(iv).some(x => x.start <= c.start && c.end <= x.end);
|
|
246
|
+
}
|
|
247
|
+
const annKey = (a) => `${a.source}#${a.covering}#${a.asof ?? ''}`;
|
|
248
|
+
/** 同一属性(源・covering・asof)の註釈区間を整列・併合した正規形 */
|
|
249
|
+
function normAnn(list) {
|
|
250
|
+
const groups = new Map();
|
|
251
|
+
for (const a of list) {
|
|
252
|
+
if (a.to <= a.from)
|
|
253
|
+
continue;
|
|
254
|
+
const k = annKey(a);
|
|
255
|
+
const g = groups.get(k);
|
|
256
|
+
if (g)
|
|
257
|
+
g.push(a);
|
|
258
|
+
else
|
|
259
|
+
groups.set(k, [a]);
|
|
260
|
+
}
|
|
261
|
+
const out = [];
|
|
262
|
+
for (const g of groups.values()) {
|
|
263
|
+
g.sort((x, y) => x.from - y.from);
|
|
264
|
+
let cur = { ...g[0] };
|
|
265
|
+
for (let i = 1; i < g.length; i++) {
|
|
266
|
+
if (g[i].from <= cur.to)
|
|
267
|
+
cur.to = Math.max(cur.to, g[i].to);
|
|
268
|
+
else {
|
|
269
|
+
out.push(cur);
|
|
270
|
+
cur = { ...g[i] };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
out.push(cur);
|
|
274
|
+
}
|
|
275
|
+
return out.sort((x, y) => x.from - y.from || x.to - y.to);
|
|
276
|
+
}
|
|
277
|
+
/** 註釈の和(結合子の輸送規則。自動相殺なし=ADR-37 判断 4) */
|
|
278
|
+
function annUnion(...lists) {
|
|
279
|
+
return normAnn(lists.flat());
|
|
280
|
+
}
|
|
281
|
+
/** ms を含む註釈エントリ(分類器の基準=実効被覆域の外か) */
|
|
282
|
+
function annAt(ann, ms) {
|
|
283
|
+
return ann.filter(a => a.from <= ms && ms < a.to);
|
|
284
|
+
}
|
|
285
|
+
/** 評価範囲へのクリップ(表面で一度だけ=ADR-37 判断 8) */
|
|
286
|
+
function clipAnn(ann, from, to) {
|
|
287
|
+
return normAnn(ann.map(a => ({ ...a, from: Math.max(a.from, from), to: Math.min(a.to, to) })));
|
|
288
|
+
}
|
|
289
|
+
/** roll による入力註釈の像(過大近似・単調): 区間内の点の着地はこの包に収まる。
|
|
290
|
+
* 軸が空のときの早期 return は健全(F99 検証で確認)——未知入力の着地は既知軸点が無ければ
|
|
291
|
+
* 成立せず、未知の軸点への着地依存は dependImageAnn(軸註釈の依存像)が引き受ける */
|
|
292
|
+
function rollImageAnn(ann, axis, conv) {
|
|
293
|
+
if (axis.length === 0)
|
|
294
|
+
return normAnn(ann);
|
|
295
|
+
return normAnn(ann.map(a => {
|
|
296
|
+
if (conv === 'Following') {
|
|
297
|
+
if (a.to === Infinity)
|
|
298
|
+
return { ...a };
|
|
299
|
+
const j = ptUpperBound(axis, a.to - 1); // a.to 以上の最初の軸点=着地の上界
|
|
300
|
+
return j < axis.length ? { ...a, to: Math.max(a.to, axis[j] + 1) } : { ...a, to: Infinity };
|
|
301
|
+
}
|
|
302
|
+
if (a.from === -Infinity)
|
|
303
|
+
return { ...a };
|
|
304
|
+
const j = ptUpperBound(axis, a.from) - 1; // a.from 以下の最後の軸点=着地の下界
|
|
305
|
+
return j >= 0 ? { ...a, from: Math.min(a.from, axis[j]) } : { ...a, from: -Infinity };
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
/** 軸の註釈区間の依存像(ADR-37 判断 4 roll 行): 規約の逆方向へ直前(Following)/
|
|
309
|
+
* 直後(Preceding)の既知軸点まで拡張——その帯の入力は未知の軸点へ着地し得た。
|
|
310
|
+
* 軸が空(点ゼロのテーブル等・F99)でも一般則がそのまま働く——既知軸点が無ければ帯は ±∞ へ
|
|
311
|
+
* 広がり、開端 covering(完結主張)なら axisAnn が空で拡張対象なし=註釈なしの空が保存される */
|
|
312
|
+
function dependImageAnn(axisAnn, axis, conv) {
|
|
313
|
+
return normAnn(axisAnn.map(a => {
|
|
314
|
+
if (conv === 'Following') {
|
|
315
|
+
if (a.from === -Infinity)
|
|
316
|
+
return { ...a };
|
|
317
|
+
const j = ptUpperBound(axis, a.from - 1) - 1; // 直前の既知軸点(< a.from の最後)
|
|
318
|
+
return j >= 0 ? { ...a, from: Math.min(a.from, axis[j]) } : { ...a, from: -Infinity };
|
|
319
|
+
}
|
|
320
|
+
if (a.to === Infinity)
|
|
321
|
+
return { ...a };
|
|
322
|
+
const j = ptUpperBound(axis, a.to - 1); // 直後の既知軸点(≥ a.to の最初)
|
|
323
|
+
return j < axis.length ? { ...a, to: Math.max(a.to, axis[j] + 1) } : { ...a, to: Infinity };
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
/** shift(窓語 unit)の平行移動像: 区間端を同じ窓添字ずらしで写す(過大近似可)。
|
|
327
|
+
* 実体化範囲外の端は ±∞ でなく端別に挟む(F106)——旧形の「外→安全側 ±∞」は、紀元より前へ
|
|
328
|
+
* 伸びる covering の補集合(遠い過去の註釈端)を全軸註釈に爆発させ(from=-∞ の区間の to 端だけが
|
|
329
|
+
* +∞ へ飛ぶ)、覆域を広げるほど註釈が増える単調性の逆転を生んでいた。窓単位 shift は順序保存
|
|
330
|
+
* (窓添字 +n・窓内オフセット保存)なので、範囲外の端も「その値自身」(n の逆方向は動かない)と
|
|
331
|
+
* 「範囲内最寄り窓からの n 歩」で抑えられる——from は下界・to は上界のまま、いずれも過大近似 */
|
|
332
|
+
function shiftAnnByWindows(ann, iv, n) {
|
|
333
|
+
const moveIn = (ms, i) => {
|
|
334
|
+
const j = i + n;
|
|
335
|
+
if (j < 0)
|
|
336
|
+
return -Infinity;
|
|
337
|
+
if (j >= iv.length)
|
|
338
|
+
return Infinity;
|
|
339
|
+
return iv[j].start + (ms - iv[i].start);
|
|
340
|
+
};
|
|
341
|
+
const lo = (ms) => {
|
|
342
|
+
if (!isFinite(ms))
|
|
343
|
+
return ms;
|
|
344
|
+
const i = ivIndexOf(iv, ms);
|
|
345
|
+
if (i >= 0)
|
|
346
|
+
return moveIn(ms, i);
|
|
347
|
+
if (n >= 0)
|
|
348
|
+
return ms;
|
|
349
|
+
if (iv.length > 0 && ms >= iv[iv.length - 1].end) {
|
|
350
|
+
const j = iv.length + n; // 右外: 範囲内最終窓の次からの n 歩
|
|
351
|
+
return j >= 0 && j < iv.length ? iv[j].start : -Infinity;
|
|
352
|
+
}
|
|
353
|
+
return -Infinity;
|
|
354
|
+
};
|
|
355
|
+
const hi = (ms) => {
|
|
356
|
+
if (!isFinite(ms))
|
|
357
|
+
return ms;
|
|
358
|
+
const i = ivIndexOf(iv, ms);
|
|
359
|
+
if (i >= 0)
|
|
360
|
+
return moveIn(ms, i);
|
|
361
|
+
if (n <= 0)
|
|
362
|
+
return ms;
|
|
363
|
+
if (iv.length > 0 && ms < iv[0].start) { // 左外: 範囲内先頭窓からの n 歩
|
|
364
|
+
return n < iv.length ? iv[n].start : Infinity;
|
|
365
|
+
}
|
|
366
|
+
return Infinity;
|
|
367
|
+
};
|
|
368
|
+
return normAnn(ann.map(a => ({ ...a, from: lo(a.from), to: hi(a.to) })));
|
|
369
|
+
}
|
|
370
|
+
/** shift(点列軸 unit)の平行移動像: 区間端を軸上の n 歩で写す(過大近似可) */
|
|
371
|
+
function shiftAnnByAxis(ann, axis, n) {
|
|
372
|
+
if (axis.length === 0)
|
|
373
|
+
return normAnn(ann);
|
|
374
|
+
const step = (k) => (k < 0 ? -Infinity : k >= axis.length ? Infinity : axis[k]);
|
|
375
|
+
return normAnn(ann.map(a => {
|
|
376
|
+
const from = a.from === -Infinity ? -Infinity
|
|
377
|
+
: step(ptUpperBound(axis, a.from - 1) + n); // 区間内最初の軸点から n 歩
|
|
378
|
+
const toBase = a.to === Infinity ? Infinity
|
|
379
|
+
: step(ptUpperBound(axis, a.to - 1) - 1 + n); // 区間内最後の軸点から n 歩
|
|
380
|
+
return { ...a, from, to: toBase === Infinity ? Infinity : toBase + 1 };
|
|
381
|
+
}));
|
|
382
|
+
}
|
|
383
|
+
/** snapTo の註釈の像: 端点を属する窓の先頭へ床処理(覆域の端を窓粒度で解決する操作形) */
|
|
384
|
+
function snapAnn(ann, w) {
|
|
385
|
+
const floor = (ms) => {
|
|
386
|
+
if (!isFinite(ms))
|
|
387
|
+
return ms;
|
|
388
|
+
const i = ivIndexOf(w, ms);
|
|
389
|
+
return i >= 0 ? w[i].start : ms;
|
|
390
|
+
};
|
|
391
|
+
return normAnn(ann.map(a => ({ ...a, from: floor(a.from), to: floor(a.to) })));
|
|
392
|
+
}
|
|
393
|
+
// ---- 評価器 ----
|
|
394
|
+
const CORE_STAGES = new Set(['within', 'segmentBy', 'first', 'nth', 'last', 'roll', 'shift',
|
|
395
|
+
'snapTo', 'rebase', 'filter', 'stride', 'strideBy', 'take', 'takeLast']);
|
|
396
|
+
const CORE_WORDS = new Set([...CORE_STAGES, 'everyDay', 'everyInstant', 'chronos',
|
|
397
|
+
'grid', 'span', 'split', 'cycle', 'ordinalIn', 'epochOrdinal', 'coincides', 'external']);
|
|
398
|
+
/** 実在日の検査(ADR-43 の字句検査の解決値向け再執行——external は字句層を経ないため。ADR-46) */
|
|
399
|
+
function isRealDate(y, mo, d) {
|
|
400
|
+
if (mo < 1 || mo > 12 || d < 1)
|
|
401
|
+
return false;
|
|
402
|
+
const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
|
|
403
|
+
return d <= [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo - 1];
|
|
404
|
+
}
|
|
405
|
+
/** external 呼び出しの構造判定(位置検査・tz 必須執行が共用) */
|
|
406
|
+
function isExternalCall(x) {
|
|
407
|
+
return !!x && typeof x === 'object' && x.t === 'call'
|
|
408
|
+
&& x.callee?.t === 'name' && x.callee.name === 'external';
|
|
409
|
+
}
|
|
410
|
+
export class Evaluator {
|
|
411
|
+
/** premise 公開語のメモ(参照しうる文脈メンバー wkst・tz・calendar-system をキーに含める。I6)。
|
|
412
|
+
* tzChecks=右辺評価中に踏んだ predicateAlign 文脈の tz 名検査の記録(ADR-53=還流第 16 便 §2:
|
|
413
|
+
* ヒット時にも現在の predicateAlign で再実行——検査の順序独立性と値の同一性を両立) */
|
|
414
|
+
defCache = new Map();
|
|
415
|
+
/** 評価中の束縛の tz 名検査ログ(ネスト束縛は全層へ記録——外側ヒット時は内側評価が走らないため)。
|
|
416
|
+
* baseAlign=束縛評価開始時の predicateAlign(記録条件②の基準。還流第 17 便 §2) */
|
|
417
|
+
tzCheckLogs = [];
|
|
418
|
+
/** 評価中の label: 付与式(再入ガード。自己参照=定義中の束縛名のラベル射影を検出。ADR-34) */
|
|
419
|
+
labelStack = new Set();
|
|
420
|
+
/** 導出中のカレンダー実体(自己・相互循環の検出。ADR-35 判断 8——ADR-41 の導出鎖
|
|
421
|
+
* isOpen → bizOpen → C.nonWorking 越しの自己・相互参照もこの集合で捕まる) */
|
|
422
|
+
derivingEntities = new Set();
|
|
423
|
+
/** 解決中の束縛(引数なし束縛の自己・相互循環の検出=§4.8 の依存解析。F110) */
|
|
424
|
+
resolvingDefs = [];
|
|
425
|
+
/** 適用評価中のラムダ本体(糖衣定義の再帰適用の検出=§4.8「展開は有限」。F110。
|
|
426
|
+
* 鍵は本体 Expr の参照同一性——lambda 値は解決ごとに再生成されるが本体 AST は同一。
|
|
427
|
+
* 正当な再適用(f(a) | f(b))は逐次・ネスト f(g(s)) は先に引数を評価するため再入しない) */
|
|
428
|
+
applyingBodies = new Map();
|
|
429
|
+
/** 細粒度導出(ADR-41)のメモ——実体名+文脈キー(defCache と同じ面。ADR-41 帰結) */
|
|
430
|
+
fineCache = new Map();
|
|
431
|
+
/** external のスナップショット(ADR-46 判断 5): 一評価一解決。キー=定義側 premise#束縛#解決後 source
|
|
432
|
+
* ——with 派生・多文脈・修飾参照は同一スナップショットを見る。変換(錨打ち・covering 端)は
|
|
433
|
+
* defCache 側=評価文脈ごと(リテラルの再錨と同型) */
|
|
434
|
+
socketCache = new Map();
|
|
435
|
+
/** 評価中の premise 束縛(external の合法位置の文脈。null=本体層・top-level=external 不可) */
|
|
436
|
+
externalCtx = null;
|
|
437
|
+
/** filter 述語に流れている点列の整列(免除系 tz 検査の評価時近似=ADR-36 改訂 2/ADR-40。
|
|
438
|
+
* 点はデータを運ばない(ADR-30/33)ため、静的検査の近似として評価文脈で運ぶ) */
|
|
439
|
+
predicateAlign = null;
|
|
440
|
+
/** 束縛右辺の評価中、遮断した呼び出し側 locals 鎖(誘導文言用。ADR-53=還流第 16 便 §1)。
|
|
441
|
+
* 束縛右辺の名前解決は「束縛・premise 語・前文メンバー・列挙」で閉じる——呼び出し側の
|
|
442
|
+
* ラムダ変数には落とさない(定義の意味が使用箇所の変数名に依存する捕獲は静的エラー) */
|
|
443
|
+
severedLocalsEnv = null;
|
|
444
|
+
rt;
|
|
445
|
+
constructor(rt) { this.rt = rt; }
|
|
446
|
+
// ---------- プログラム ----------
|
|
447
|
+
runProgram(program, defaultMembers) {
|
|
448
|
+
const results = [];
|
|
449
|
+
let env = { rt: this.rt, premise: null, members: defaultMembers, locals: null, parent: null };
|
|
450
|
+
const execStatements = (stmts, env0) => {
|
|
451
|
+
let cur = env0;
|
|
452
|
+
for (const st of stmts) {
|
|
453
|
+
switch (st.t) {
|
|
454
|
+
case 'premiseDef':
|
|
455
|
+
this.registerPremise(st);
|
|
456
|
+
break;
|
|
457
|
+
case 'preamble': {
|
|
458
|
+
const members = new Map(defaultMembers);
|
|
459
|
+
let premise = null;
|
|
460
|
+
if (st.name) {
|
|
461
|
+
premise = this.rt.premises.get(st.name)
|
|
462
|
+
?? this.err(`未定義の premise: @${st.name}`);
|
|
463
|
+
for (const [k, v] of premise.members)
|
|
464
|
+
members.set(k, v);
|
|
465
|
+
}
|
|
466
|
+
if (st.members.some(m => m.key === 'epoch')) {
|
|
467
|
+
this.err('epoch: は原始的定義のメンバー——利用側の前文には置けない(ADR-31)');
|
|
468
|
+
}
|
|
469
|
+
for (const m of st.members)
|
|
470
|
+
members.set(m.key, m.value);
|
|
471
|
+
const next = { rt: this.rt, premise, members, locals: null, parent: null };
|
|
472
|
+
if (st.block) {
|
|
473
|
+
execStatements(st.block, next);
|
|
474
|
+
}
|
|
475
|
+
else
|
|
476
|
+
cur = next; // 次の前文まで以降の文を統べる(§3.2)
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case 'binding': {
|
|
480
|
+
if (CORE_WORDS.has(st.name))
|
|
481
|
+
this.err(`core 語 ${st.name} の再定義は静的エラー(§4.8)`);
|
|
482
|
+
this.rt.topBindings.set(st.name, { params: st.params, rhs: st.rhs, covering: st.covering });
|
|
483
|
+
this.scanVocab(st.rhs);
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
case 'streamExpr': {
|
|
487
|
+
const v = this.evalExpr(st.expr, cur);
|
|
488
|
+
const s = this.toStream(v);
|
|
489
|
+
const trimmed = s.pts.filter(p => p >= this.rt.fromMs && p < this.rt.toMs);
|
|
490
|
+
// 区間註釈は評価範囲 [from, to) にクリップして結果と同格に返す(表面で一度だけ=ADR-37 判断 8)。
|
|
491
|
+
// 表示形は閉端→半開端変換の ε(+1ms。輸送の依存像で生じる)を正規化する——正規化しないと
|
|
492
|
+
// 真夜中の端が「2026-01-02T00:00」と印字され日付裸の端と不統一になる。fromMs/toMs は正確なまま
|
|
493
|
+
const disp = (ms) => this.rt.fmt(ms % 1000 === 1 ? ms - 1 : ms);
|
|
494
|
+
const annotations = clipAnn(s.ann, this.rt.fromMs, this.rt.toMs).map(a => ({
|
|
495
|
+
kind: 'out-of-coverage',
|
|
496
|
+
from: disp(a.from), to: disp(a.to), fromMs: a.from, toMs: a.to,
|
|
497
|
+
source: a.source, covering: a.covering, ...(a.asof ? { asof: a.asof } : {}),
|
|
498
|
+
}));
|
|
499
|
+
results.push({ source: '', points: trimmed, dates: trimmed.map(p => this.rt.fmt(p)), annotations });
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return cur;
|
|
505
|
+
};
|
|
506
|
+
execStatements(program.statements, env);
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
registerPremise(st) {
|
|
510
|
+
const members = new Map();
|
|
511
|
+
const defs = new Map();
|
|
512
|
+
let base = null;
|
|
513
|
+
const absorb = (block) => {
|
|
514
|
+
for (const m of block.members)
|
|
515
|
+
members.set(m.key, m.value);
|
|
516
|
+
for (const b of block.bindings) {
|
|
517
|
+
if (CORE_WORDS.has(b.name))
|
|
518
|
+
this.err(`core 語 ${b.name} の再定義は静的エラー(§4.8)`);
|
|
519
|
+
defs.set(b.name, { params: b.params, rhs: b.rhs, covering: b.covering });
|
|
520
|
+
this.scanVocab(b.rhs);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
if (st.block)
|
|
524
|
+
absorb(st.block);
|
|
525
|
+
if (st.expr) {
|
|
526
|
+
base = this.rt.premises.get(st.expr.base) ?? this.err(`未定義の premise: ${st.expr.base}`);
|
|
527
|
+
if (st.expr.withBlock)
|
|
528
|
+
absorb(st.expr.withBlock);
|
|
529
|
+
for (const stage of st.expr.stages) {
|
|
530
|
+
if (stage.name !== 'rephase')
|
|
531
|
+
this.err(`premise 段 ${stage.name} は未対応(プロトタイプ)`);
|
|
532
|
+
const { d, W, U } = this.rephaseArgs(stage);
|
|
533
|
+
defs.set(W, this.expandShiftBoundary(base, d, W, U));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// tz: 宣言必須の執行(ADR-35 判断 1 / ADR-37 判断 1・執行点=宣言時):
|
|
537
|
+
// covering: または日付テーブルを持つ premise は、覆域の端と錨打ちの市民日が利用側 tz で
|
|
538
|
+
// 動かないよう tz: を宣言する(base 連鎖の宣言でもよい=member 解決規則で内側固定)
|
|
539
|
+
if (!members.has('tz') && ![...(function* (b) {
|
|
540
|
+
for (; b; b = b.base)
|
|
541
|
+
yield b;
|
|
542
|
+
})(base)].some(b => b.members.has('tz'))) {
|
|
543
|
+
for (const [dn, decl] of defs) {
|
|
544
|
+
if (this.hasDateTable(decl.rhs)) {
|
|
545
|
+
this.err(`covering:/日付テーブルを持つ premise は tz: を宣言する(覆域の端と錨打ちの市民日の内側固定`
|
|
546
|
+
+ `——ADR-33/35/37): ${st.name}(束縛 ${dn})`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// epoch: メンバー(ADR-31)——プロトタイプは言語既定(1970-01-01)のみ対応
|
|
551
|
+
const epochE = members.get('epoch');
|
|
552
|
+
if (epochE !== undefined) {
|
|
553
|
+
const env = { rt: this.rt, premise: null, members: new Map(), locals: null, parent: null };
|
|
554
|
+
const v = typeof epochE === 'string' ? undefined : this.evalExpr(epochE, env);
|
|
555
|
+
if (!(v && typeof v === 'object' && !Array.isArray(v) && v.k === 'point' && v.ms === this.rt.epoch)) {
|
|
556
|
+
this.err('プロトタイプは既定紀元(1970-01-01)のみ対応(epoch: の別値は未実装。ADR-31)');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
this.rt.premises.set(st.name, new PremiseInstance(st.name, members, defs, base));
|
|
560
|
+
}
|
|
561
|
+
/** rephase(δ, on: W, unit: U) → W = U span (_ => k) phase: φ₀+δ(§3.7) */
|
|
562
|
+
rephaseArgs(stage) {
|
|
563
|
+
let d, W, U;
|
|
564
|
+
for (const a of stage.args) {
|
|
565
|
+
if (!a.name)
|
|
566
|
+
d = this.constNum(a.value);
|
|
567
|
+
else if (a.name === 'on')
|
|
568
|
+
W = a.value.t === 'name' ? a.value.name : undefined;
|
|
569
|
+
else if (a.name === 'unit')
|
|
570
|
+
U = a.value.t === 'name' ? a.value.name : undefined;
|
|
571
|
+
}
|
|
572
|
+
if (d === undefined || !W || !U)
|
|
573
|
+
this.err('rephase(δ, on: W, unit: U) の引数が不足');
|
|
574
|
+
return { d: d, W: W, U: U };
|
|
575
|
+
}
|
|
576
|
+
expandShiftBoundary(base, d, W, U) {
|
|
577
|
+
const def = base.findDef(W) ?? this.err(`rephase: base に窓 ${W} がない`);
|
|
578
|
+
const rhs = def.rhs;
|
|
579
|
+
if (rhs.t !== 'gen' || rhs.word !== 'span' || rhs.operand.t !== 'name' || rhs.operand.name !== U) {
|
|
580
|
+
this.err(`rephase の射程外: base の ${W} は「${U} span (定数)」の形でない(§3.7)`);
|
|
581
|
+
}
|
|
582
|
+
const g = rhs;
|
|
583
|
+
const k = this.constLambdaValue(g.arg);
|
|
584
|
+
const phi0 = g.named.phase ? this.constNum(g.named.phase) : 0;
|
|
585
|
+
// 位相は k を法として合成(負の δ も正規化=F65)。base の label: は保存する(ADR-34 帰結・§3.7)
|
|
586
|
+
const named = { phase: { t: 'num', v: ((phi0 + d) % k + k) % k } };
|
|
587
|
+
if (g.named.label)
|
|
588
|
+
named.label = g.named.label;
|
|
589
|
+
return {
|
|
590
|
+
params: [],
|
|
591
|
+
rhs: {
|
|
592
|
+
t: 'gen', operand: { t: 'name', name: U }, word: 'span',
|
|
593
|
+
arg: { t: 'lambda', params: ['_'], body: { t: 'num', v: k } },
|
|
594
|
+
named,
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
constNum(e) {
|
|
599
|
+
if (e.t === 'num')
|
|
600
|
+
return e.v;
|
|
601
|
+
if (e.t === 'neg')
|
|
602
|
+
return -this.constNum(e.e);
|
|
603
|
+
this.err('数値定数を期待');
|
|
604
|
+
}
|
|
605
|
+
constLambdaValue(e) {
|
|
606
|
+
if (e.t === 'lambda' && e.body.t === 'num')
|
|
607
|
+
return e.body.v;
|
|
608
|
+
this.err('rephase の射程は k 定数の span のみ(§3.7)');
|
|
609
|
+
}
|
|
610
|
+
/** rhs が covering: つき、または日付要素を含むテーブルリテラルを含むか(tz: 必須の執行の判定)。
|
|
611
|
+
* external 束縛も対象(解決値 covering の端と kind: dates の錨は定義側 tz で解決——ADR-46 判断 4) */
|
|
612
|
+
hasDateTable(e) {
|
|
613
|
+
if (!e || typeof e !== 'object')
|
|
614
|
+
return false;
|
|
615
|
+
if (isExternalCall(e))
|
|
616
|
+
return true;
|
|
617
|
+
if (e.t === 'list') {
|
|
618
|
+
if (e.covering)
|
|
619
|
+
return true;
|
|
620
|
+
if (e.elems.some(el => ('t' in el) && (el.t === 'date' || el.t === 'range')))
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
for (const v of Object.values(e)) {
|
|
624
|
+
if (Array.isArray(v)) {
|
|
625
|
+
if (v.some(x => x && typeof x === 'object' && this.hasDateTable(x)))
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
else if (v && typeof v === 'object' && 't' in v && this.hasDateTable(v))
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
/** cycle のラベル列・labels: 列から列挙ラベル語彙を集める(名前解決のフォールバック用) */
|
|
634
|
+
scanVocab(e) {
|
|
635
|
+
const visit = (x) => {
|
|
636
|
+
if (!x || typeof x !== 'object')
|
|
637
|
+
return;
|
|
638
|
+
if (x.t === 'gen' && x.word === 'cycle' && x.arg.t === 'list') {
|
|
639
|
+
for (const el of x.arg.elems)
|
|
640
|
+
if ('t' in el && el.t === 'name')
|
|
641
|
+
this.rt.vocab.add(el.name);
|
|
642
|
+
}
|
|
643
|
+
if (x.t === 'list' && x.labels && x.labels.t === 'list') {
|
|
644
|
+
for (const el of x.labels.elems)
|
|
645
|
+
if ('t' in el && el.t === 'name')
|
|
646
|
+
this.rt.vocab.add(el.name);
|
|
647
|
+
}
|
|
648
|
+
// segmentBy の labels: 引数(ADR-39)の列挙名も語彙に収集(named-arg の形で現れる)
|
|
649
|
+
if (!('t' in x) && x.name === 'labels' && x.value?.t === 'list') {
|
|
650
|
+
for (const el of x.value.elems) {
|
|
651
|
+
if ('t' in el && el.t === 'name')
|
|
652
|
+
this.rt.vocab.add(el.name);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// labels: cycle 形(ADR-47)の列挙名も同様に収集
|
|
656
|
+
if (!('t' in x) && x.name === 'labels' && x.value?.t === 'cycleLabels'
|
|
657
|
+
&& x.value.list?.t === 'list') {
|
|
658
|
+
for (const el of x.value.list.elems) {
|
|
659
|
+
if ('t' in el && el.t === 'name')
|
|
660
|
+
this.rt.vocab.add(el.name);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
for (const v of Object.values(x)) {
|
|
664
|
+
if (Array.isArray(v))
|
|
665
|
+
v.forEach(visit);
|
|
666
|
+
else if (v && typeof v === 'object' && 't' in v)
|
|
667
|
+
visit(v);
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
visit(e);
|
|
671
|
+
}
|
|
672
|
+
// ---------- 名前解決(§3.4: premise 相対) ----------
|
|
673
|
+
resolve(name, env) {
|
|
674
|
+
// calendar: の在圏では bizDay は言語予約の導出名(手動束縛は静的エラー。ADR-35 判断 4)
|
|
675
|
+
if (name === 'bizDay' && env.members.has('calendar')) {
|
|
676
|
+
if (this.isBoundName('bizDay', env)) {
|
|
677
|
+
this.err('calendar: の在圏では bizDay は言語予約の導出名——手動束縛は静的エラー(独自軸は別名で。ADR-35)');
|
|
678
|
+
}
|
|
679
|
+
return this.deriveBizDay(env);
|
|
680
|
+
}
|
|
681
|
+
// calendar: の在圏では bizOpen/bizClose も言語予約の導出名(細粒度の標準導出。ADR-41 判断 3)
|
|
682
|
+
if ((name === 'bizOpen' || name === 'bizClose') && env.members.has('calendar')) {
|
|
683
|
+
if (this.isBoundName(name, env)) {
|
|
684
|
+
this.err(`calendar: の在圏では ${name} は言語予約の導出名——手動束縛は静的エラー(独自の列は別名で。ADR-41)`);
|
|
685
|
+
}
|
|
686
|
+
const fine = this.deriveBizFine(env);
|
|
687
|
+
return name === 'bizOpen' ? fine.open : fine.close;
|
|
688
|
+
}
|
|
689
|
+
for (let e = env; e; e = e.parent) {
|
|
690
|
+
if (e.locals?.has(name))
|
|
691
|
+
return e.locals.get(name);
|
|
692
|
+
}
|
|
693
|
+
// 在圏 premise の公開語(機構 A: ルートは env.premise のまま)
|
|
694
|
+
if (env.premise) {
|
|
695
|
+
const def = env.premise.findDef(name);
|
|
696
|
+
if (def)
|
|
697
|
+
return this.evalDef(env.premise, name, def, env);
|
|
698
|
+
const cs = this.calendarSystemOf(env);
|
|
699
|
+
if (cs) {
|
|
700
|
+
const def2 = cs.findDef(name);
|
|
701
|
+
if (def2)
|
|
702
|
+
return this.evalDef(cs, name, def2, env);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
const cs = this.calendarSystemOf(env);
|
|
707
|
+
if (cs) {
|
|
708
|
+
const def = cs.findDef(name);
|
|
709
|
+
if (def)
|
|
710
|
+
return this.evalDef(cs, name, def, env);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
// 前文メンバー(wkst 等の遅延解決。§3.6・§4.8)
|
|
714
|
+
if (env.members.has(name)) {
|
|
715
|
+
const m = env.members.get(name);
|
|
716
|
+
return typeof m === 'string' ? m : this.evalExpr(m, env);
|
|
717
|
+
}
|
|
718
|
+
if (this.rt.topBindings.has(name)) {
|
|
719
|
+
const b = this.rt.topBindings.get(name);
|
|
720
|
+
if (b.params.length > 0)
|
|
721
|
+
this.err(`${name} は引数付き束縛(呼び出しが必要)`);
|
|
722
|
+
// 評価文脈ごとのメモ化(premise 公開語の defCache と同じ面・同じ文脈キー): 述語内の
|
|
723
|
+
// 束縛名射影(sekkiMonth(d) 級)が実体化全点で右辺を再評価する O(N²) の封止
|
|
724
|
+
const key = `#top#${env.premise?.name ?? ''}#${name}#`
|
|
725
|
+
+ ['wkst', 'tz', 'calendar-system', 'calendar', 'axis', 'roll', 'asof', 'source']
|
|
726
|
+
.map(k => this.memberStr(env.members, k)).join('#');
|
|
727
|
+
const hit = this.defCache.get(key);
|
|
728
|
+
if (hit !== undefined) {
|
|
729
|
+
// 記録済みの tz 名検査を現在の predicateAlign で再実行(ADR-53 §2——順序独立の安全ゲート)
|
|
730
|
+
for (const c of hit.tzChecks)
|
|
731
|
+
this.checkTzMembership(this.predicateAlign, c.win, c.ctx, true);
|
|
732
|
+
// ↑ fromPredicate=true: 再実行された検査は、評価中の外側束縛のフレームへも再記録する
|
|
733
|
+
//(「ネスト束縛は全層へ記録——外側ヒット時は内側評価が走らない」の維持。第 17 便処置が
|
|
734
|
+
// ここを既定 false のまま落とし、間接参照 B = T 経由で順序独立が破れた=還流第 18 便)
|
|
735
|
+
return hit.v;
|
|
736
|
+
}
|
|
737
|
+
// 束縛の循環検出(§4.8 の依存解析・F110): premise 公開語(evalDef)と同じ網を本体層にも
|
|
738
|
+
const defId = name;
|
|
739
|
+
if (this.resolvingDefs.includes(defId)) {
|
|
740
|
+
this.err(`束縛の循環参照は静的エラー: ${[...this.resolvingDefs.slice(this.resolvingDefs.indexOf(defId)), defId].join(' → ')}(§4.8 の依存解析)`);
|
|
741
|
+
}
|
|
742
|
+
this.resolvingDefs.push(defId);
|
|
743
|
+
// top-level 束縛は external 不可(source: 統治が premise に要る。ADR-46)——premise 定義の
|
|
744
|
+
// 評価中に参照されても文脈を継がない(合法位置のすり抜け防止)
|
|
745
|
+
const prevCtx = this.externalCtx;
|
|
746
|
+
this.externalCtx = null;
|
|
747
|
+
// 右辺の名前解決は呼び出し側 locals 鎖を遮断する(ADR-53=還流第 16 便 §1: 定義の意味が
|
|
748
|
+
// 使用箇所のラムダ変数名に依存する捕獲と、メモ化の誤共有〈焼き付き〉を根で断つ)
|
|
749
|
+
const prevSevered = this.severedLocalsEnv;
|
|
750
|
+
this.severedLocalsEnv = this.severedLocalsEnv ?? env; // 最外の遮断点=ユーザーのラムダ変数が居る env を保持(ネストで上書きしない)
|
|
751
|
+
const rhsEnv = { ...env, locals: null, parent: null };
|
|
752
|
+
const tzFrame = { log: [], baseAlign: this.predicateAlign };
|
|
753
|
+
this.tzCheckLogs.push(tzFrame);
|
|
754
|
+
let v;
|
|
755
|
+
try {
|
|
756
|
+
v = this.evalExpr(b.rhs, rhsEnv);
|
|
757
|
+
}
|
|
758
|
+
finally {
|
|
759
|
+
this.externalCtx = prevCtx;
|
|
760
|
+
this.severedLocalsEnv = prevSevered;
|
|
761
|
+
this.resolvingDefs.pop();
|
|
762
|
+
this.tzCheckLogs.pop();
|
|
763
|
+
}
|
|
764
|
+
if (isObj(v) && v.k === 'table' && !v.src)
|
|
765
|
+
v = { ...v, src: name }; // 出自の焼印(ADR-37 判断 2)
|
|
766
|
+
if (isObj(v) && v.k === 'windows' && !v.name)
|
|
767
|
+
v = { ...v, name }; // 診断用の束縛名(ADR-42)
|
|
768
|
+
if (b.covering)
|
|
769
|
+
v = this.applyClaim(v, b.covering, name, env); // 明示の被覆主張(判断 5)
|
|
770
|
+
this.defCache.set(key, { v, tzChecks: tzFrame.log });
|
|
771
|
+
return v;
|
|
772
|
+
}
|
|
773
|
+
if (name === 'chronos')
|
|
774
|
+
return { k: 'chronos' };
|
|
775
|
+
if (name === 'everyDay')
|
|
776
|
+
return this.everyDay(env);
|
|
777
|
+
if (name === 'everyInstant')
|
|
778
|
+
return { k: 'instant' };
|
|
779
|
+
if (this.rt.vocab.has(name))
|
|
780
|
+
return name; // 列挙ラベル(意味論で区別。§5.6 注記)
|
|
781
|
+
// 遮断された呼び出し側ラムダ変数なら誘導つき静的エラー(ADR-53——「未解決」より原因が深い)
|
|
782
|
+
if (this.severedLocalsEnv && this.lookupLocal(name, this.severedLocalsEnv) !== undefined) {
|
|
783
|
+
this.err(`束縛の右辺から呼び出し側のラムダ変数は見えない: ${name}——束縛の意味は定義単体で閉じる`
|
|
784
|
+
+ `(点に依存する値は引数付き束縛のパラメータで受ける: T(x) = … の右辺では x を使い、`
|
|
785
|
+
+ `呼び出し側で T(${name}) と渡す。ADR-53)`);
|
|
786
|
+
}
|
|
787
|
+
this.err(`未解決の名前: ${name}(premise 相対解決 §3.4)`);
|
|
788
|
+
}
|
|
789
|
+
/** 名前が束縛(locals・premise 公開語・メンバー・top-level)として解決可能か(軸位置の曖昧性検出用) */
|
|
790
|
+
isBoundName(name, env) {
|
|
791
|
+
if (this.lookupLocal(name, env) !== undefined)
|
|
792
|
+
return true;
|
|
793
|
+
if (env.premise?.findDef(name))
|
|
794
|
+
return true;
|
|
795
|
+
const cs = this.calendarSystemOf(env);
|
|
796
|
+
if (cs?.findDef(name))
|
|
797
|
+
return true;
|
|
798
|
+
if (env.members.has(name))
|
|
799
|
+
return true;
|
|
800
|
+
if (this.rt.topBindings.has(name))
|
|
801
|
+
return true;
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
calendarSystemOf(env) {
|
|
805
|
+
const m = env.members.get('calendar-system');
|
|
806
|
+
if (!m)
|
|
807
|
+
return null;
|
|
808
|
+
const name = typeof m === 'string' ? m : m.t === 'name' ? m.name : null;
|
|
809
|
+
if (!name)
|
|
810
|
+
return null;
|
|
811
|
+
const inst = this.rt.premises.get(name) ?? null;
|
|
812
|
+
// 逆向き正体判定: nonWorking を持つ premise は calendar-system: に立てない(ADR-35 判断 2)
|
|
813
|
+
if (inst?.findDef('nonWorking')) {
|
|
814
|
+
this.err(`calendar-system: にカレンダー実体は立てられない: ${name}(nonWorking を持つ premise。ADR-35 正体判定)`);
|
|
815
|
+
}
|
|
816
|
+
return inst;
|
|
817
|
+
}
|
|
818
|
+
/** member 解決規則(ADR-35 判断 8): 定義側(root と base 連鎖)が宣言するメンバーは定義側の値で
|
|
819
|
+
* 固定し、宣言しないメンバーだけ利用側で解決する(tz: の内側固定と wkst: の遅延解決が同居する形) */
|
|
820
|
+
overlayMembers(base, root) {
|
|
821
|
+
const chain = [];
|
|
822
|
+
for (let p = root; p; p = p.base)
|
|
823
|
+
chain.unshift(p);
|
|
824
|
+
if (chain.every(p => p.members.size === 0))
|
|
825
|
+
return base;
|
|
826
|
+
const m = new Map(base);
|
|
827
|
+
for (const p of chain)
|
|
828
|
+
for (const [k, v] of p.members)
|
|
829
|
+
m.set(k, v);
|
|
830
|
+
return m;
|
|
831
|
+
}
|
|
832
|
+
memberStr(members, key) {
|
|
833
|
+
const m = members.get(key);
|
|
834
|
+
if (m === undefined)
|
|
835
|
+
return '';
|
|
836
|
+
if (typeof m === 'string')
|
|
837
|
+
return m;
|
|
838
|
+
if (m.t === 'name')
|
|
839
|
+
return m.name;
|
|
840
|
+
if (m.t === 'str')
|
|
841
|
+
return m.v;
|
|
842
|
+
return JSON.stringify(m); // その他の式も一意にキー化('?' への潰しは衝突源。I6 の文脈キー)
|
|
843
|
+
}
|
|
844
|
+
/** premise 公開語の評価。裸名は root(利用側の premise)で再解決される=機構 A。
|
|
845
|
+
* 前文メンバーは定義側優先で上書き重ね(ADR-35 判断 8) */
|
|
846
|
+
evalDef(root, name, def, env) {
|
|
847
|
+
const members = this.overlayMembers(env.members, root);
|
|
848
|
+
// 右辺の名前解決は呼び出し側 locals 鎖を遮断する(ADR-53=還流第 16 便 §1。引数付き束縛の
|
|
849
|
+
// 閉包 env も同じ——右辺は自分の引数・premise・前文メンバーで閉じる)
|
|
850
|
+
const defEnv = { ...env, premise: root, members, locals: null, parent: null };
|
|
851
|
+
this.checkExternalPositions(root, name, def); // external の合法位置(ADR-46 判断 1)
|
|
852
|
+
if (def.params.length > 0) {
|
|
853
|
+
// 引数付き束縛は lambda 値として返す
|
|
854
|
+
return { k: 'lambda', params: def.params.map(p => p.name), body: def.rhs, env: defEnv };
|
|
855
|
+
}
|
|
856
|
+
// asof/source をキーに含める(版差の誤共有防止——註釈が asof を運ぶ。ADR-37)
|
|
857
|
+
const key = `${root.name}#${name}#`
|
|
858
|
+
+ ['wkst', 'tz', 'calendar-system', 'calendar', 'axis', 'roll', 'asof', 'source']
|
|
859
|
+
.map(k => this.memberStr(members, k)).join('#');
|
|
860
|
+
const hit = this.defCache.get(key);
|
|
861
|
+
if (hit !== undefined) {
|
|
862
|
+
// 記録済みの tz 名検査を現在の predicateAlign で再実行(ADR-53 §2——順序独立の安全ゲート)
|
|
863
|
+
for (const c of hit.tzChecks)
|
|
864
|
+
this.checkTzMembership(this.predicateAlign, c.win, c.ctx, true);
|
|
865
|
+
// ↑ fromPredicate=true: 再実行された検査は、評価中の外側束縛のフレームへも再記録する
|
|
866
|
+
//(「ネスト束縛は全層へ記録——外側ヒット時は内側評価が走らない」の維持。第 17 便処置が
|
|
867
|
+
// ここを既定 false のまま落とし、間接参照 B = T 経由で順序独立が破れた=還流第 18 便)
|
|
868
|
+
return hit.v;
|
|
869
|
+
}
|
|
870
|
+
// 束縛の循環検出(§4.8 の依存解析・F110): 右辺評価中に自分(または相互参照先)へ戻ったら静的エラー
|
|
871
|
+
const defId = `${root.name}.${name}`;
|
|
872
|
+
if (this.resolvingDefs.includes(defId)) {
|
|
873
|
+
this.err(`束縛の循環参照は静的エラー: ${[...this.resolvingDefs.slice(this.resolvingDefs.indexOf(defId)), defId].join(' → ')}(§4.8 の依存解析)`);
|
|
874
|
+
}
|
|
875
|
+
this.resolvingDefs.push(defId);
|
|
876
|
+
const prevCtx = this.externalCtx;
|
|
877
|
+
this.externalCtx = { root, name };
|
|
878
|
+
const prevSevered = this.severedLocalsEnv;
|
|
879
|
+
this.severedLocalsEnv = this.severedLocalsEnv ?? env; // 最外の遮断点を保持(ネストで上書きしない)
|
|
880
|
+
const tzFrame = { log: [], baseAlign: this.predicateAlign };
|
|
881
|
+
this.tzCheckLogs.push(tzFrame);
|
|
882
|
+
let v;
|
|
883
|
+
try {
|
|
884
|
+
v = this.evalExpr(def.rhs, defEnv);
|
|
885
|
+
}
|
|
886
|
+
finally {
|
|
887
|
+
this.externalCtx = prevCtx;
|
|
888
|
+
this.severedLocalsEnv = prevSevered;
|
|
889
|
+
this.resolvingDefs.pop();
|
|
890
|
+
this.tzCheckLogs.pop();
|
|
891
|
+
}
|
|
892
|
+
if (isObj(v) && v.k === 'table' && !v.src)
|
|
893
|
+
v = { ...v, src: `${root.name}.${name}` }; // 出自の焼印
|
|
894
|
+
if (isObj(v) && v.k === 'windows' && !v.name)
|
|
895
|
+
v = { ...v, name: `${root.name}.${name}` }; // 診断用の束縛名(ADR-42)
|
|
896
|
+
if (def.covering)
|
|
897
|
+
v = this.applyClaim(v, def.covering, `${root.name}.${name}`, defEnv); // 被覆主張
|
|
898
|
+
this.defCache.set(key, { v, tzChecks: tzFrame.log });
|
|
899
|
+
return v;
|
|
900
|
+
}
|
|
901
|
+
/** 明示の被覆主張(ADR-37 判断 5): 成分由来の註釈をこの束縛の主張の補集合で置き換える
|
|
902
|
+
* (相殺の唯一の口)。必要条件検査: 主張範囲 ⊆(成分の covering の和 ∪ 註釈区間の補集合)
|
|
903
|
+
* ——規則ベースの成分(註釈なし)は補集合側が全時間を語る */
|
|
904
|
+
applyClaim(v, claim, source, env) {
|
|
905
|
+
if (isObj(v) && v.k === 'lambda')
|
|
906
|
+
this.err(`被覆主張は引数なしの束縛にのみ付く: ${source}(ADR-37)`);
|
|
907
|
+
const { iv, desc, concluded } = this.resolveCovering(claim, env);
|
|
908
|
+
const s = this.toStream(v);
|
|
909
|
+
const spoken = mergeIv([
|
|
910
|
+
...complementIv(s.ann.map(a => ({ start: a.from, end: a.to }))),
|
|
911
|
+
...s.ann.flatMap(a => a.covIv),
|
|
912
|
+
]);
|
|
913
|
+
for (const c of iv) {
|
|
914
|
+
if (!ivCovers(spoken, c)) {
|
|
915
|
+
this.err(`被覆主張が成分覆域を越える: ${source} covering ${desc}`
|
|
916
|
+
+ `——どの成分も語れない区間の完全性は主張できない(必要条件・ADR-37 判断 5)`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
const asof = this.asofOf(env.members);
|
|
920
|
+
const ann = complementIv(iv).map(x => ({
|
|
921
|
+
from: x.start, to: x.end, source, covering: desc, covIv: iv, ...(asof ? { asof } : {}),
|
|
922
|
+
}));
|
|
923
|
+
this.registerCoverage(source, desc, iv, concluded, asof); // 主張は被覆サマリに常時表示
|
|
924
|
+
return { ...s, ann };
|
|
925
|
+
}
|
|
926
|
+
/** external の合法位置の静的検査(ADR-46 判断 1): premise 束縛の rhs 自身または pipe の先頭のみ。
|
|
927
|
+
* 引数つき束縛・深い位置(ラムダ内・引数内・結合子の枝)は静的エラー */
|
|
928
|
+
checkExternalPositions(root, name, def) {
|
|
929
|
+
const legal = new Set();
|
|
930
|
+
if (isExternalCall(def.rhs))
|
|
931
|
+
legal.add(def.rhs);
|
|
932
|
+
if (def.rhs.t === 'pipe' && isExternalCall(def.rhs.head))
|
|
933
|
+
legal.add(def.rhs.head);
|
|
934
|
+
let found = false;
|
|
935
|
+
const walk = (x) => {
|
|
936
|
+
if (!x || typeof x !== 'object')
|
|
937
|
+
return;
|
|
938
|
+
if (isExternalCall(x)) {
|
|
939
|
+
found = true;
|
|
940
|
+
if (!legal.has(x)) {
|
|
941
|
+
this.err(`external は premise 束縛の右辺の先頭でのみ書ける: ${root.name}.${name}`
|
|
942
|
+
+ '(合成は external(…) |> … の形・本体層/ラムダ内/引数位置は不可。ADR-46)');
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
for (const v of Object.values(x)) {
|
|
946
|
+
if (Array.isArray(v))
|
|
947
|
+
v.forEach(walk);
|
|
948
|
+
else
|
|
949
|
+
walk(v);
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
walk(def.rhs);
|
|
953
|
+
if (found && def.params.length > 0) {
|
|
954
|
+
this.err(`external は引数なしの束縛にのみ書ける: ${root.name}.${name}(ADR-46)`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
/** 外部供給宣言 external(ADR-46): 実行時に解決されるテーブルリテラル。宣言(kind・labels 値域・
|
|
958
|
+
* source)が字面の代役=静的知識を先取りし、解決値にリテラルと同一の統治検査を課す */
|
|
959
|
+
evalExternal(e, env) {
|
|
960
|
+
const ctx = this.externalCtx;
|
|
961
|
+
if (!ctx) {
|
|
962
|
+
this.err('external は premise 束縛の右辺(先頭)でのみ書ける'
|
|
963
|
+
+ '(source: 統治が premise に要る——本体層・top-level 束縛は不可。ADR-46)');
|
|
964
|
+
}
|
|
965
|
+
// 宣言の構造読み(kind 値・labels 値域は評価しない——位置依存のキーワード解釈=ADR-42 の統一原理)
|
|
966
|
+
let kind;
|
|
967
|
+
let declLabels;
|
|
968
|
+
let srcOverride;
|
|
969
|
+
for (const a of e.args) {
|
|
970
|
+
if (!a.name)
|
|
971
|
+
this.err('external は named-arg のみを取る(kind: が必須。ADR-46)');
|
|
972
|
+
if (a.name === 'kind') {
|
|
973
|
+
if (a.value.t !== 'name' || (a.value.name !== 'dates' && a.value.name !== 'instants')) {
|
|
974
|
+
this.err('external の kind: は dates | instants(整列の主張=字面クラスの宣言。ADR-46)');
|
|
975
|
+
}
|
|
976
|
+
kind = a.value.name;
|
|
977
|
+
}
|
|
978
|
+
else if (a.name === 'labels') {
|
|
979
|
+
if (a.value.t !== 'list')
|
|
980
|
+
this.err('external の labels: はラベル値域の列挙リスト(ADR-46)');
|
|
981
|
+
declLabels = a.value.elems.map(el => ('t' in el && el.t === 'name') ? el.name : this.err('external の labels: の要素はラベル名'));
|
|
982
|
+
if (declLabels.length === 0)
|
|
983
|
+
this.err('external の labels: は空にできない(無宣言=ラベルなし。ADR-46)');
|
|
984
|
+
}
|
|
985
|
+
else if (a.name === 'source') {
|
|
986
|
+
const v = this.evalExpr(a.value, env);
|
|
987
|
+
if (typeof v !== 'string')
|
|
988
|
+
this.err(`external の source: は文字列リテラル: ${kindOf(v)}`);
|
|
989
|
+
srcOverride = v;
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
this.err(`external の未知の引数: ${a.name}:(黙って捨てない——ADR-39/46)`);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
if (!kind)
|
|
996
|
+
this.err('external は kind: が必須(dates | instants=整列の主張。ADR-46)');
|
|
997
|
+
const owner = ctx.root.findDefOwner(ctx.name)?.owner ?? ctx.root;
|
|
998
|
+
const source = srcOverride ?? this.memberStr(env.members, 'source');
|
|
999
|
+
if (!source) {
|
|
1000
|
+
this.err(`external は source: が必須(premise メンバーまたは named-arg。ADR-46): ${owner.name}.${ctx.name}`);
|
|
1001
|
+
}
|
|
1002
|
+
// スナップショット(判断 5): 一評価一解決——キー=定義側 premise#束縛#解決後 source
|
|
1003
|
+
const snapKey = `${owner.name}#${ctx.name}#${source}`;
|
|
1004
|
+
let data = this.socketCache.get(snapKey);
|
|
1005
|
+
if (data === undefined) {
|
|
1006
|
+
const decl = { kind, ...(declLabels ? { labels: declLabels } : {}), source };
|
|
1007
|
+
if (!this.rt.resolver) {
|
|
1008
|
+
throw new SupplyError(`供給エラー: 解決子がない——external ${owner.name}.${ctx.name}`
|
|
1009
|
+
+ `(source: "${source}")は解決できない(ADR-46 判断 7 (a))`);
|
|
1010
|
+
}
|
|
1011
|
+
try {
|
|
1012
|
+
data = this.rt.resolver(owner.name, ctx.name, decl);
|
|
1013
|
+
}
|
|
1014
|
+
catch (err) {
|
|
1015
|
+
if (err instanceof KairosError)
|
|
1016
|
+
throw err;
|
|
1017
|
+
throw new SupplyError(`供給エラー: 解決に失敗——external ${owner.name}.${ctx.name}`
|
|
1018
|
+
+ `(source: "${source}"): ${err.message}`);
|
|
1019
|
+
}
|
|
1020
|
+
if (!data) {
|
|
1021
|
+
throw new SupplyError(`供給エラー: 解決値が無い——external ${owner.name}.${ctx.name}(source: "${source}")`);
|
|
1022
|
+
}
|
|
1023
|
+
this.socketCache.set(snapKey, data);
|
|
1024
|
+
}
|
|
1025
|
+
return this.externalToTable(owner, ctx.name, kind, declLabels, data, env);
|
|
1026
|
+
}
|
|
1027
|
+
/** 解決値→テーブル値の変換と契約検査(ADR-46 判断 6。リテラルと同一の統治・文言体系) */
|
|
1028
|
+
externalToTable(owner, name, kind, declLabels, data, env) {
|
|
1029
|
+
const src = `${owner.name}.${name}`;
|
|
1030
|
+
if (typeof data.covering !== 'string' || data.covering.trim() === '') {
|
|
1031
|
+
this.err(`契約違反: covering がない——external ${src}(解決値は覆域の主張を必ず運ぶ。ADR-46)`);
|
|
1032
|
+
}
|
|
1033
|
+
let claim;
|
|
1034
|
+
try {
|
|
1035
|
+
claim = parseCoveringText(data.covering);
|
|
1036
|
+
}
|
|
1037
|
+
catch (err) {
|
|
1038
|
+
this.err(`契約違反: covering が読めない——external ${src}: ${err.message}`);
|
|
1039
|
+
}
|
|
1040
|
+
const r = this.resolveCovering(claim, env);
|
|
1041
|
+
if (typeof data.asof !== 'string' || data.asof.trim() === '') {
|
|
1042
|
+
this.err(`契約違反: asof がない——external ${src}(データの観測日はデータと一緒に来る。ADR-46)`);
|
|
1043
|
+
}
|
|
1044
|
+
let asof = data.asof;
|
|
1045
|
+
const staticAsof = this.asofOf(env.members);
|
|
1046
|
+
if (staticAsof && staticAsof !== 'latest' && staticAsof !== asof) {
|
|
1047
|
+
asof = `${asof}(宣言 asof ${staticAsof} と不一致)`; // 黙って上書きしない——被覆サマリ・註釈に常時表示
|
|
1048
|
+
}
|
|
1049
|
+
let pts;
|
|
1050
|
+
if (kind === 'dates') {
|
|
1051
|
+
if (!Array.isArray(data.dates)) {
|
|
1052
|
+
this.err(`契約違反: kind: dates の解決値は dates("YYYY-MM-DD" の列)を運ぶ——external ${src}`);
|
|
1053
|
+
}
|
|
1054
|
+
if (data.instants)
|
|
1055
|
+
this.err(`契約違反: kind: dates に instants が来た——external ${src}`);
|
|
1056
|
+
const tz = this.tzObjOf(env); // 定義側 tz(覆域端と同じ解決=判断 4)で錨打ち——派生の tz 上書きで再錨可能
|
|
1057
|
+
pts = data.dates.map(s => {
|
|
1058
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(s))
|
|
1059
|
+
?? this.err(`契約違反: 日付の形式が不正——external ${src}: ${s}("YYYY-MM-DD")`);
|
|
1060
|
+
const y = +m[1], mo = +m[2], d = +m[3];
|
|
1061
|
+
if (!isRealDate(y, mo, d)) {
|
|
1062
|
+
this.err(`契約違反: 実在しない日付——external ${src}: ${s}`
|
|
1063
|
+
+ '(黙ったロールオーバーの封止=ADR-43 の字句検査の解決値向け再執行。ADR-46)');
|
|
1064
|
+
}
|
|
1065
|
+
return tz.civilDayStart(y, mo, d);
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
if (!Array.isArray(data.instants)) {
|
|
1070
|
+
this.err(`契約違反: kind: instants の解決値は instants(epoch ms の列)を運ぶ——external ${src}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (data.dates)
|
|
1073
|
+
this.err(`契約違反: kind: instants に dates が来た——external ${src}`);
|
|
1074
|
+
for (const v of data.instants) {
|
|
1075
|
+
if (typeof v !== 'number' || !Number.isInteger(v)) {
|
|
1076
|
+
this.err(`契約違反: instants は有限整数の epoch ms——external ${src}: ${v}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
pts = data.instants.slice();
|
|
1080
|
+
}
|
|
1081
|
+
for (let i = 1; i < pts.length; i++) {
|
|
1082
|
+
if (pts[i] <= pts[i - 1])
|
|
1083
|
+
this.err(`契約違反: 解決値は昇順・重複なし——external ${src}(§3.8 と同じ整列則)`);
|
|
1084
|
+
}
|
|
1085
|
+
for (const p of pts) {
|
|
1086
|
+
if (!ivContainsPoint(r.iv, p)) {
|
|
1087
|
+
this.err(`契約違反: 解決値の点が covering の外——external ${src}: ${this.rt.fmt(p)}`
|
|
1088
|
+
+ '(列の全要素は covering に包含——ADR-37 判断 1)');
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
let labels;
|
|
1092
|
+
if (declLabels) {
|
|
1093
|
+
if (!Array.isArray(data.labels))
|
|
1094
|
+
this.err(`契約違反: labels 宣言つきの external にラベルが来ない——${src}`);
|
|
1095
|
+
if (data.labels.length !== pts.length)
|
|
1096
|
+
this.err(`契約違反: labels は時点列と同長——external ${src}(ADR-30)`);
|
|
1097
|
+
for (const l of data.labels) {
|
|
1098
|
+
if (!declLabels.includes(l)) {
|
|
1099
|
+
this.err(`契約違反: 宣言値域の外のラベル——external ${src}: ${l}(域外の封止=ADR-42 判断 7 の契約版)`);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
labels = data.labels.slice();
|
|
1103
|
+
}
|
|
1104
|
+
else if (data.labels) {
|
|
1105
|
+
this.err(`契約違反: labels 無宣言の external にラベルが来た——${src}(黙って捨てない=ADR-39 の統治)`);
|
|
1106
|
+
}
|
|
1107
|
+
// 整列は宣言から静的に決まる(空でも宣言どおり=ADR-46 判断 2。dates の目盛り所属は錨打ちの構成で保証)
|
|
1108
|
+
const align = kind === 'dates' ? this.dayGrain(env) : null;
|
|
1109
|
+
return { k: 'table', pts, labels, covIv: r.iv, covDesc: r.desc, concluded: r.concluded, asof, src, align };
|
|
1110
|
+
}
|
|
1111
|
+
/** asof: メンバーの表示形(註釈・被覆サマリの属性) */
|
|
1112
|
+
asofOf(members) {
|
|
1113
|
+
const m = members.get('asof');
|
|
1114
|
+
if (m === undefined)
|
|
1115
|
+
return undefined;
|
|
1116
|
+
if (typeof m === 'string')
|
|
1117
|
+
return m;
|
|
1118
|
+
if (m.t === 'date')
|
|
1119
|
+
return this.rt.fmt(this.rt.dateToMs(m.v));
|
|
1120
|
+
if (m.t === 'str')
|
|
1121
|
+
return m.v;
|
|
1122
|
+
if (m.t === 'name')
|
|
1123
|
+
return m.name;
|
|
1124
|
+
return JSON.stringify(m);
|
|
1125
|
+
}
|
|
1126
|
+
/** 被覆サマリへの登録(ADR-37 判断 7 (b)): 評価が参照した各データ源・被覆主張 */
|
|
1127
|
+
registerCoverage(source, covDesc, covIv, concluded, asof) {
|
|
1128
|
+
const key = `${source}#${covDesc}#${asof ?? ''}`;
|
|
1129
|
+
if (this.rt.coverage.has(key))
|
|
1130
|
+
return;
|
|
1131
|
+
const covEnd = covIv.length ? Math.max(...covIv.map(x => x.end)) : Infinity;
|
|
1132
|
+
this.rt.coverage.set(key, { source, covering: covDesc, ...(asof ? { asof } : {}), concluded, covEnd });
|
|
1133
|
+
}
|
|
1134
|
+
/** テーブルの範囲外註釈=覆域の補集合(ADR-37 判断 1: 値は列が決め、註釈は covering が決める) */
|
|
1135
|
+
tableAnn(t) {
|
|
1136
|
+
const src = t.src ?? '(無名テーブル)';
|
|
1137
|
+
return complementIv(t.covIv).map(x => ({
|
|
1138
|
+
from: x.start, to: x.end, source: src, covering: t.covDesc, covIv: t.covIv,
|
|
1139
|
+
...(t.asof ? { asof: t.asof } : {}),
|
|
1140
|
+
}));
|
|
1141
|
+
}
|
|
1142
|
+
/** 窓相当の値の註釈(窓列の実効被覆域の補集合)。生成語の windows(暦法純粋)は無註釈 */
|
|
1143
|
+
winAnnOfV(v) {
|
|
1144
|
+
if (isObj(v) && v.k === 'stream' && v.wins.length > 0)
|
|
1145
|
+
return v.wins[v.wins.length - 1].ann;
|
|
1146
|
+
return [];
|
|
1147
|
+
}
|
|
1148
|
+
/** 窓リーダーの覆域検査(ADR-37 判断 4/6・F82): 点が窓に解決できても、その窓が窓列自身の
|
|
1149
|
+
* 註釈区間(マーカー覆域の補集合)に交差するなら窓境界そのものが未知——証人・序数の判定
|
|
1150
|
+
* 以前に範囲外へ分類する(過小近似は不可。合成マーカー(openTick | closeTick 級)の帯が
|
|
1151
|
+
* 覆域の尽きた側で黙って 24 時間帯化して読まれる欠陥の封止) */
|
|
1152
|
+
winCovOrOut(win, ann, what) {
|
|
1153
|
+
const overlap = ann.filter(a => a.from < win.end && win.start < a.to);
|
|
1154
|
+
if (overlap.length === 0)
|
|
1155
|
+
return;
|
|
1156
|
+
const a = overlap[0];
|
|
1157
|
+
throw new OutOfCoverageSignal(`範囲外(out-of-coverage): ${what}の窓 [${this.rt.fmt(Math.max(win.start, -8.64e15))}, `
|
|
1158
|
+
+ `${this.rt.fmt(Math.min(win.end, 8.64e15))}) はマーカー覆域の外に掛かり境界が未知`
|
|
1159
|
+
+ `(${a.source} covering ${a.covering}${a.asof ? `, asof ${a.asof}` : ''}——ADR-37 判断 4/6・F82)`, overlap, win);
|
|
1160
|
+
}
|
|
1161
|
+
/** 分類器(ADR-37 判断 6): 失敗点が依存の実効被覆域の外=註釈区間の内なら範囲外シグナル・
|
|
1162
|
+
* 内(覆域内の失敗)なら従来どおり硬エラー(取り違え=ADR-16 の統治のまま) */
|
|
1163
|
+
outOrErr(ms, depAnn, hardMsg) {
|
|
1164
|
+
const hit = annAt(depAnn, ms);
|
|
1165
|
+
if (hit.length > 0) {
|
|
1166
|
+
const a = hit[0];
|
|
1167
|
+
throw new OutOfCoverageSignal(`範囲外(out-of-coverage): 参照点 ${this.rt.fmt(ms)} は ${a.source} の実効被覆域の外`
|
|
1168
|
+
+ `(covering ${a.covering}${a.asof ? `, asof ${a.asof}` : ''}——判定は外部・ADR-37)`, hit);
|
|
1169
|
+
}
|
|
1170
|
+
this.err(hardMsg);
|
|
1171
|
+
}
|
|
1172
|
+
everyDay(env) {
|
|
1173
|
+
const day = this.resolve('day', env);
|
|
1174
|
+
if (kindOf(day) !== 'windows')
|
|
1175
|
+
this.err('everyDay: 在圏暦法に day がない');
|
|
1176
|
+
const dw = day;
|
|
1177
|
+
return { k: 'stream', pts: dw.units.slice(), wins: [], align: dw.grain, ann: [], endless: true };
|
|
1178
|
+
}
|
|
1179
|
+
/** 在圏 tz 名(ADR-36 判断 6: リテラル文字列の等値)。未宣言は実行既定 tz へフォールバック */
|
|
1180
|
+
tzNameOf(env) {
|
|
1181
|
+
return this.memberStr(env.members, 'tz') || this.rt.tzName;
|
|
1182
|
+
}
|
|
1183
|
+
/** 在圏 tz の幾何(ADR-33 射影パラメータモデル)——タグの tz 名と実グリッドはここで一致する */
|
|
1184
|
+
tzObjOf(env) {
|
|
1185
|
+
return getTz(this.tzNameOf(env));
|
|
1186
|
+
}
|
|
1187
|
+
/** 日付・時刻リテラルの錨打ち(在圏 tz。ADR-33): 日付のみ=市民日の最初の瞬間・
|
|
1188
|
+
* 時刻付きで DST の隙間(存在しない時刻)・重複(二意の時刻)は明示エラー */
|
|
1189
|
+
anchorDate(d, env) {
|
|
1190
|
+
const tz = this.tzObjOf(env);
|
|
1191
|
+
if (!d.hasTime)
|
|
1192
|
+
return tz.civilDayStart(d.y, d.mo, d.d);
|
|
1193
|
+
const local = Date.UTC(d.y, d.mo - 1, d.d, d.h, d.mi, Math.floor(d.s), Math.round((d.s % 1) * 1000));
|
|
1194
|
+
const a = tz.anchor(local);
|
|
1195
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
1196
|
+
const lit = `${d.y}-${p(d.mo)}-${p(d.d)}T${p(d.h)}:${p(d.mi)}`;
|
|
1197
|
+
if (a.kind === 'gap') {
|
|
1198
|
+
this.err(`存在しない時刻: ${lit}(tz "${tz.name}" の DST の隙間に落ちる——実在の壁時計で書く。ADR-33)`);
|
|
1199
|
+
}
|
|
1200
|
+
if (a.kind === 'overlap') {
|
|
1201
|
+
this.err(`二意の時刻: ${lit}(tz "${tz.name}" の DST の重複に落ちる——解決規約は将来の opt-in。ADR-33)`);
|
|
1202
|
+
}
|
|
1203
|
+
return a.ms;
|
|
1204
|
+
}
|
|
1205
|
+
/** 単独時刻リテラルの錨打ち(ADR-51): epoch 錨日 1970-01-01(在圏 tz)+日内時刻。
|
|
1206
|
+
* 現行 tzdb 全ゾーンで 1969-12-30〜1970-01-04 に DST 遷移は無い(悉皆確認済み)が、
|
|
1207
|
+
* 錨打ちは anchorDate と同じ厳格規約を通す(万一の非一意は黙らせない) */
|
|
1208
|
+
anchorTimeOfDay(todMs, env) {
|
|
1209
|
+
const tz = this.tzObjOf(env);
|
|
1210
|
+
const a = tz.anchor(Date.UTC(1970, 0, 1) + todMs);
|
|
1211
|
+
if (a.kind !== 'unique') {
|
|
1212
|
+
this.err(`単独時刻リテラルの錨打ちが一意でない(tz "${tz.name}" の 1970-01-01 に DST 遷移)——`
|
|
1213
|
+
+ '日付つきの日時リテラルで錨を明示する(ADR-51)');
|
|
1214
|
+
}
|
|
1215
|
+
return a.ms;
|
|
1216
|
+
}
|
|
1217
|
+
/** 市民日グリッド(1d・既定整列・在圏 tz)——テーブル・day 原子の整列の既定形 */
|
|
1218
|
+
dayGrain(env) {
|
|
1219
|
+
return { kind: 'civil', step: 1, phase: 0, off: 0, tz: this.tzNameOf(env) };
|
|
1220
|
+
}
|
|
1221
|
+
/** 整列の検査(ADR-36 判断 3): 等値所属の両辺は同一 G を要求 */
|
|
1222
|
+
checkAlign(input, axis, ctx) {
|
|
1223
|
+
if (!gridEq(input, axis)) {
|
|
1224
|
+
this.err(`${ctx}: 両辺の整列が同一でない——点の等値所属が黙って空振りする形(ADR-36)。`
|
|
1225
|
+
+ `入力=${gridDesc(input)}・軸/右辺=${gridDesc(axis)}。同じ点が意図なら snapTo で明示的に整合する`
|
|
1226
|
+
+ `(同じ所属〈日〉が意図なら coincides。ADR-38)`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
/** 免除系の tz 名検査(ADR-36 改訂 2・ADR-40 判断 6): 区間所属は幅・位相を問わない(免除は維持)が、
|
|
1230
|
+
* tz 名だけは日付座標系そのもの——市民グリッド入力 × 市民の窓要素グリッドの不一致は
|
|
1231
|
+
* 「ラベル 1 日ずれの束ね・曜日読み」が黙って通る形なので静的エラー。snapTo は除外(chronos 所属) */
|
|
1232
|
+
checkTzMembership(input, win, ctx, fromPredicate = false) {
|
|
1233
|
+
// predicateAlign 文脈の検査要求を、評価中の束縛のキャッシュエントリへ記録する(ADR-53=還流
|
|
1234
|
+
// 第 16 便 §2): 検査は値でなく評価器状態(predicateAlign)に依るため、キャッシュヒット時にも
|
|
1235
|
+
// 記録済みの検査だけ再実行する——「最初に評価された文脈の検査 1 回で以降の文脈が素通る」順序
|
|
1236
|
+
// 依存の黙殺を防ぐ。値は align 非依存なのでキャッシュ再利用自体は正しい(キーは変えない——
|
|
1237
|
+
// 解決値のクロージャ同一性が labelStack ガードの前提。ADR-34/42 判断 7 (f))。
|
|
1238
|
+
// 記録条件の精密化(還流第 17 便 §2): ①記録するのは predicateAlign を入力に据える呼び出し元
|
|
1239
|
+
// だけ——fromPredicate の明示渡し(参照等値の推測は、値由来の検査〈within・単位窓×枠窓〉が
|
|
1240
|
+
// 偶然同一オブジェクトになる経路で誤記録し、診断の誤帰属と偽陽性を生んだ)②かつ predicateAlign
|
|
1241
|
+
// が束縛評価開始時(frame.baseAlign)のままの検査だけ——右辺内の filter が自分で確定した
|
|
1242
|
+
// align での検査は右辺内で完結し、呼び出し文脈に依存しない(再実行は過剰停止になる)
|
|
1243
|
+
if (fromPredicate) {
|
|
1244
|
+
// 重複排除(同じ検査が点ごとに積まれると、ヒット時再実行が O(N²) を戻す)——ユニークな
|
|
1245
|
+
// (win, ctx) は式中の射影箇所数程度なので線形走査で足りる
|
|
1246
|
+
for (const f of this.tzCheckLogs) {
|
|
1247
|
+
if (this.predicateAlign === f.baseAlign
|
|
1248
|
+
&& !f.log.some(c => c.ctx === ctx && gridEq(c.win, win)))
|
|
1249
|
+
f.log.push({ win, ctx });
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
if (input && input.kind === 'civil' && input.tz && win && win.kind === 'civil' && win.tz
|
|
1253
|
+
&& input.tz !== win.tz) {
|
|
1254
|
+
this.err(`${ctx}: 入力と窓の tz 名が不一致(入力="${input.tz}"・窓="${win.tz}")——ラベル 1 日ずれの`
|
|
1255
|
+
+ `所属が黙って通る形(ADR-36 改訂 2/ADR-40)。同じ日付の所属が意図なら rebase(to: "${win.tz}") で再錨する`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
/** 軸位置の名前の評価(ADR-35 判断 4): 束縛は通常解決・premise 名だけならカレンダー実体の
|
|
1259
|
+
* 読み替え(標準導出 bizDay)・両方に解決できる名前は曖昧=静的エラー */
|
|
1260
|
+
evalAxis(e, env) {
|
|
1261
|
+
if (e.t === 'name' && this.rt.premises.has(e.name)) {
|
|
1262
|
+
if (this.isBoundName(e.name, env)) {
|
|
1263
|
+
this.err(`軸位置の名前 ${e.name} は束縛にも premise 名にも解決できて曖昧(ADR-35/17)`);
|
|
1264
|
+
}
|
|
1265
|
+
return this.deriveBizDay(env, this.rt.premises.get(e.name));
|
|
1266
|
+
}
|
|
1267
|
+
return this.evalExpr(e, env);
|
|
1268
|
+
}
|
|
1269
|
+
/** 在圏 calendar: の実体の解決(deriveBizDay / deriveBizFine の共通部) */
|
|
1270
|
+
calendarEntity(env, what) {
|
|
1271
|
+
const m = env.members.get('calendar');
|
|
1272
|
+
if (m === undefined)
|
|
1273
|
+
this.err(`${what}: 在圏に calendar: がない(前文で宣言する。§3.3/§3.9)`);
|
|
1274
|
+
const name = typeof m === 'string' ? m : m.t === 'name' ? m.name : null;
|
|
1275
|
+
if (!name)
|
|
1276
|
+
this.err('calendar: はカレンダー実体(premise 名)を取る(ADR-35)');
|
|
1277
|
+
return this.rt.premises.get(name) ?? this.err(`calendar: が指す premise が未定義: ${name}`);
|
|
1278
|
+
}
|
|
1279
|
+
/** bizDay 標準導出(ADR-35 判断 3/4): everyDay \ C.nonWorking——everyDay は在圏解決・
|
|
1280
|
+
* C.nonWorking は C にピン(member 解決規則で内側固定)。正体判定込み */
|
|
1281
|
+
deriveBizDay(env, entity) {
|
|
1282
|
+
const ent = entity ?? this.calendarEntity(env, 'bizDay');
|
|
1283
|
+
// 正体判定(ADR-35 判断 2。実体としての初回使用時に走る)
|
|
1284
|
+
const nw = ent.findDef('nonWorking')
|
|
1285
|
+
?? this.err(`カレンダー実体ではない: ${ent.name}(公開語 nonWorking を持たない。ADR-35 正体判定)`);
|
|
1286
|
+
if (nw.params.length > 0)
|
|
1287
|
+
this.err(`正体判定: nonWorking は引数なしの束縛(ADR-35): ${ent.name}`);
|
|
1288
|
+
const entMembers = this.overlayMembers(new Map(), ent);
|
|
1289
|
+
if (!entMembers.has('tz')) {
|
|
1290
|
+
this.err(`正体判定: カレンダー実体は tz: を宣言する(内側固定の執行点。ADR-33/35): ${ent.name}`);
|
|
1291
|
+
}
|
|
1292
|
+
if (this.derivingEntities.has(ent.name)) {
|
|
1293
|
+
this.err(`カレンダー実体の循環(自己・相互参照): ${ent.name}(ADR-35 判断 8)`);
|
|
1294
|
+
}
|
|
1295
|
+
// 導出のメモ化(公開語の defCache と同じ文脈キー): 述語内の bizDay 参照(coincides 等)が
|
|
1296
|
+
// 評価点ごとに再導出して二次コストになるのを防ぐ
|
|
1297
|
+
const cacheKey = `bizDay⌗${ent.name}⌗`
|
|
1298
|
+
+ ['wkst', 'tz', 'calendar-system', 'calendar', 'axis', 'roll', 'asof', 'source']
|
|
1299
|
+
.map(k => this.memberStr(env.members, k)).join('#');
|
|
1300
|
+
const hit = this.defCache.get(cacheKey);
|
|
1301
|
+
if (hit !== undefined) {
|
|
1302
|
+
// 記録済みの tz 名検査を現在の predicateAlign で再実行(ADR-53 §2——順序独立の安全ゲート)
|
|
1303
|
+
for (const c of hit.tzChecks)
|
|
1304
|
+
this.checkTzMembership(this.predicateAlign, c.win, c.ctx, true);
|
|
1305
|
+
// ↑ fromPredicate=true: 再実行された検査は、評価中の外側束縛のフレームへも再記録する
|
|
1306
|
+
//(「ネスト束縛は全層へ記録——外側ヒット時は内側評価が走らない」の維持。第 17 便処置が
|
|
1307
|
+
// ここを既定 false のまま落とし、間接参照 B = T 経由で順序独立が破れた=還流第 18 便)
|
|
1308
|
+
return hit.v;
|
|
1309
|
+
}
|
|
1310
|
+
this.derivingEntities.add(ent.name);
|
|
1311
|
+
const tzFrame = { log: [], baseAlign: this.predicateAlign };
|
|
1312
|
+
this.tzCheckLogs.push(tzFrame);
|
|
1313
|
+
try {
|
|
1314
|
+
const nwV = this.toStream(this.evalDef(ent, 'nonWorking', nw, env));
|
|
1315
|
+
// 正体判定: nonWorking は実体 tz の市民日グリッドに整列(「日粒度で読む」の操作的定義)
|
|
1316
|
+
const entDay = { kind: 'civil', step: 1, phase: 0, off: 0,
|
|
1317
|
+
tz: this.memberStr(this.overlayMembers(env.members, ent), 'tz') || this.rt.tzName };
|
|
1318
|
+
if (!gridEq(nwV.align, entDay)) {
|
|
1319
|
+
this.err(`正体判定: nonWorking は実体 tz の市民日グリッドに整列する(ADR-35。細粒度は別の束縛に): `
|
|
1320
|
+
+ `${ent.name} の nonWorking の整列=${gridDesc(nwV.align)}`);
|
|
1321
|
+
}
|
|
1322
|
+
const every = this.everyDay(env);
|
|
1323
|
+
this.checkAlign(every.align, nwV.align, `bizDay 標準導出(everyDay \\ ${ent.name}.nonWorking)`);
|
|
1324
|
+
// 差 \ は両辺の註釈の和(ADR-35 判断 3 / ADR-37): 祝日データの尽きた先の退化は、するが観測可能
|
|
1325
|
+
const v = { k: 'stream', pts: ptDiff(every.pts, nwV.pts), wins: [], align: every.align,
|
|
1326
|
+
ann: annUnion(every.ann, nwV.ann), endless: true };
|
|
1327
|
+
this.defCache.set(cacheKey, { v, tzChecks: tzFrame.log });
|
|
1328
|
+
return v;
|
|
1329
|
+
}
|
|
1330
|
+
finally {
|
|
1331
|
+
this.derivingEntities.delete(ent.name);
|
|
1332
|
+
this.tzCheckLogs.pop();
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
/** 細粒度標準導出(ADR-41): bizOpen・bizClose・isOpen の共通実体化。評価は実体相対
|
|
1336
|
+
* (判断 3)——C の env(member 解決=定義側優先・ADR-35 判断 8)で C.sessionOpens・C.sessionCloses・
|
|
1337
|
+
* C.nonWorking・C.everyDay を読む。対宣言と整合性の検査は細粒度導出の初回使用時 */
|
|
1338
|
+
deriveBizFine(env) {
|
|
1339
|
+
const ent = this.calendarEntity(env, '細粒度導出(bizOpen/bizClose/isOpen)');
|
|
1340
|
+
// 正体判定(ADR-35 判断 2)
|
|
1341
|
+
if (!ent.findDef('nonWorking')) {
|
|
1342
|
+
this.err(`カレンダー実体ではない: ${ent.name}(公開語 nonWorking を持たない。ADR-35 正体判定)`);
|
|
1343
|
+
}
|
|
1344
|
+
if (!this.overlayMembers(new Map(), ent).has('tz')) {
|
|
1345
|
+
this.err(`正体判定: カレンダー実体は tz: を宣言する(内側固定の執行点。ADR-33/35): ${ent.name}`);
|
|
1346
|
+
}
|
|
1347
|
+
// 供給規約=対宣言(ADR-41 判断 1): with 派生は継承込みで判定(findDef が base 連鎖を辿る)
|
|
1348
|
+
const opensDef = ent.findDef('sessionOpens');
|
|
1349
|
+
const closesDef = ent.findDef('sessionCloses');
|
|
1350
|
+
if (!opensDef && !closesDef) {
|
|
1351
|
+
this.err(`実体 ${ent.name} は sessionOpens/sessionCloses を宣言していない`
|
|
1352
|
+
+ `——細粒度導出(bizOpen/bizClose/isOpen)は静的エラー(ADR-41)`);
|
|
1353
|
+
}
|
|
1354
|
+
if (!opensDef || !closesDef) {
|
|
1355
|
+
this.err(`sessionOpens/sessionCloses は対で宣言する——実体 ${ent.name} は ${opensDef ? 'sessionOpens' : 'sessionCloses'} だけ`
|
|
1356
|
+
+ `(片方だけは静的エラー・with 派生は継承込みで判定。ADR-41)`);
|
|
1357
|
+
}
|
|
1358
|
+
if (opensDef.params.length > 0 || closesDef.params.length > 0) {
|
|
1359
|
+
this.err(`sessionOpens/sessionCloses は引数なしの時間ストリーム束縛(ADR-41): ${ent.name}`);
|
|
1360
|
+
}
|
|
1361
|
+
// メモ化は実体名+文脈キー(定義側優先で重ねたメンバー。defCache と同じ面——ADR-41 帰結)
|
|
1362
|
+
const entMembers = this.overlayMembers(env.members, ent);
|
|
1363
|
+
const key = `bizFine⌗${ent.name}⌗`
|
|
1364
|
+
+ ['wkst', 'tz', 'calendar-system', 'calendar', 'axis', 'roll', 'asof', 'source']
|
|
1365
|
+
.map(k => this.memberStr(entMembers, k)).join('#');
|
|
1366
|
+
const hit = this.fineCache.get(key);
|
|
1367
|
+
if (hit)
|
|
1368
|
+
return hit;
|
|
1369
|
+
if (this.derivingEntities.has(ent.name)) {
|
|
1370
|
+
this.err(`カレンダー実体の循環(自己・相互参照): ${ent.name}`
|
|
1371
|
+
+ `(ADR-35 判断 8——導出鎖 isOpen → bizOpen → nonWorking 越しも検査)`);
|
|
1372
|
+
}
|
|
1373
|
+
this.derivingEntities.add(ent.name);
|
|
1374
|
+
try {
|
|
1375
|
+
const entEnv = { ...env, premise: ent, members: entMembers };
|
|
1376
|
+
const entTz = this.tzObjOf(entEnv);
|
|
1377
|
+
// 実体相対の評価(判断 3): 点は実体 tz の市民座標の事実(ADR-33 判断 10 の内側固定)
|
|
1378
|
+
const opensS = this.toStream(this.evalDef(ent, 'sessionOpens', opensDef, env));
|
|
1379
|
+
const closesS = this.toStream(this.evalDef(ent, 'sessionCloses', closesDef, env));
|
|
1380
|
+
const nwS = this.toStream(this.evalDef(ent, 'nonWorking', ent.findDef('nonWorking'), env));
|
|
1381
|
+
const entDay = { kind: 'civil', step: 1, phase: 0, off: 0, tz: entTz.name };
|
|
1382
|
+
if (!gridEq(nwS.align, entDay)) {
|
|
1383
|
+
this.err(`正体判定: nonWorking は実体 tz の市民日グリッドに整列する(ADR-35。細粒度は sessionOpens/sessionCloses に): `
|
|
1384
|
+
+ `${ent.name} の nonWorking の整列=${gridDesc(nwS.align)}`);
|
|
1385
|
+
}
|
|
1386
|
+
const every = this.everyDay(entEnv);
|
|
1387
|
+
const bizDays = new Set(ptDiff(every.pts, nwS.pts)); // C の営業日=C.everyDay \ C.nonWorking
|
|
1388
|
+
const dayAnn = annUnion(every.ann, nwS.ann);
|
|
1389
|
+
const markerAnn = annUnion(opensS.ann, closesS.ann);
|
|
1390
|
+
// 整合性検査(判断 2): 初回使用時・データ相対の層
|
|
1391
|
+
this.checkAlternation(ent, opensS.pts, closesS.pts, markerAnn);
|
|
1392
|
+
// セッション断片の構築: 開場マーカーごとの半開区間 [open, close)。同時刻の open/close は
|
|
1393
|
+
// 両点保持——順序は文脈で一意(直前が open 状態なら close→open=連続営業・closed 状態なら
|
|
1394
|
+
// open→close=幅 0 セッション=空)。落ちた断片(開場日が非営業日)も isOpen の偽の材料に保持
|
|
1395
|
+
const segIv = [];
|
|
1396
|
+
const segDayStart = [];
|
|
1397
|
+
const segKept = [];
|
|
1398
|
+
const closeSet = new Set(); // 断片を閉じた実 close(bizClose の候補)
|
|
1399
|
+
let since = -1; // 現セッションの開場点(-1=閉場中)
|
|
1400
|
+
let sinceDay = 0;
|
|
1401
|
+
const push = (end) => {
|
|
1402
|
+
if (end > since) { // 幅 0 のセッションは空(区間の和に現れない。判断 2)
|
|
1403
|
+
segIv.push({ start: since, end });
|
|
1404
|
+
segDayStart.push(sinceDay);
|
|
1405
|
+
segKept.push(bizDays.has(sinceDay));
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
let i = 0, j = 0;
|
|
1409
|
+
while (i < opensS.pts.length || j < closesS.pts.length) {
|
|
1410
|
+
const to = i < opensS.pts.length ? opensS.pts[i] : Infinity;
|
|
1411
|
+
const tc = j < closesS.pts.length ? closesS.pts[j] : Infinity;
|
|
1412
|
+
if (to === tc) {
|
|
1413
|
+
// 同時刻対: 開場中なら close→open(断片を切って即開く=連続営業)・閉場中なら幅 0
|
|
1414
|
+
if (since >= 0) {
|
|
1415
|
+
push(to);
|
|
1416
|
+
closeSet.add(to);
|
|
1417
|
+
since = to;
|
|
1418
|
+
sinceDay = entTz.floorToDay(to);
|
|
1419
|
+
}
|
|
1420
|
+
i++;
|
|
1421
|
+
j++;
|
|
1422
|
+
}
|
|
1423
|
+
else if (to < tc) {
|
|
1424
|
+
if (since < 0) {
|
|
1425
|
+
since = to;
|
|
1426
|
+
sinceDay = entTz.floorToDay(to);
|
|
1427
|
+
} // 交互破れは検査済み
|
|
1428
|
+
i++;
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
if (since >= 0) {
|
|
1432
|
+
push(tc);
|
|
1433
|
+
closeSet.add(tc);
|
|
1434
|
+
since = -1;
|
|
1435
|
+
}
|
|
1436
|
+
j++;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (since >= 0)
|
|
1440
|
+
push(this.rt.computeEnd); // 尾の孤立 open(切り欠き)=実体化端まで開く
|
|
1441
|
+
// bizOpen=営業日の開場点(判断 3。帰属は開場日固定——実体 tz の市民日で読む)
|
|
1442
|
+
const bizOpens = opensS.pts.filter(o => bizDays.has(entTz.floorToDay(o)));
|
|
1443
|
+
// bizClose=bizOpen セッションの併合区間の和の右端(接する断片は融合・実体化端の仮端は除く)
|
|
1444
|
+
const bizCloses = mergeIv(segIv.filter((_, k) => segKept[k]))
|
|
1445
|
+
.map(x => x.end).filter(e2 => closeSet.has(e2));
|
|
1446
|
+
const ann = annUnion(markerAnn, dayAnn); // 導出は sessionOpens/sessionCloses/nonWorking の註釈に依存(判断 3)
|
|
1447
|
+
const fine = {
|
|
1448
|
+
open: { k: 'stream', pts: bizOpens, wins: [], align: null, ann, endless: opensS.endless },
|
|
1449
|
+
close: { k: 'stream', pts: bizCloses, wins: [], align: null, ann, endless: closesS.endless },
|
|
1450
|
+
segIv, segDayStart, segKept,
|
|
1451
|
+
markers: ptUnion(opensS.pts, closesS.pts), markerAnn, dayAnn,
|
|
1452
|
+
};
|
|
1453
|
+
this.fineCache.set(key, fine);
|
|
1454
|
+
return fine;
|
|
1455
|
+
}
|
|
1456
|
+
finally {
|
|
1457
|
+
this.derivingEntities.delete(ent.name);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/** 整合性検査(ADR-41 判断 2): マーカー列(sessionOpens/sessionCloses の成分別の素性つきマージ)の局所交互。
|
|
1461
|
+
* 定義域=結合実効被覆域(両ストリームの註釈区間の補集合)∩ 実体化範囲——註釈区間は未知で
|
|
1462
|
+
* あって違反ではない。定義域の頭の孤立 close・尾の孤立 open は切り欠きとして合法。
|
|
1463
|
+
* 同時刻の open/close は両点保持——順序は文脈で一意なので状態を変えない。層はデータ相対 */
|
|
1464
|
+
checkAlternation(ent, sessionOpens, sessionCloses, markerAnn) {
|
|
1465
|
+
const domain = complementIv(markerAnn.map(a => ({ start: a.from, end: a.to })))
|
|
1466
|
+
.map(x => ({ start: Math.max(x.start, this.rt.epoch), end: Math.min(x.end, this.rt.computeEnd) }))
|
|
1467
|
+
.filter(x => x.start < x.end);
|
|
1468
|
+
for (const D of domain) {
|
|
1469
|
+
let state = null; // 定義域の頭は不定(切り欠き許容)
|
|
1470
|
+
let i = ptUpperBound(sessionOpens, D.start - 1);
|
|
1471
|
+
let j = ptUpperBound(sessionCloses, D.start - 1);
|
|
1472
|
+
for (;;) {
|
|
1473
|
+
const to = i < sessionOpens.length && sessionOpens[i] < D.end ? sessionOpens[i] : Infinity;
|
|
1474
|
+
const tc = j < sessionCloses.length && sessionCloses[j] < D.end ? sessionCloses[j] : Infinity;
|
|
1475
|
+
if (to === Infinity && tc === Infinity)
|
|
1476
|
+
break;
|
|
1477
|
+
if (to === tc) {
|
|
1478
|
+
i++;
|
|
1479
|
+
j++;
|
|
1480
|
+
continue;
|
|
1481
|
+
} // 同時刻対=close→open か open→close(状態不変)
|
|
1482
|
+
if (to < tc) {
|
|
1483
|
+
if (state === 'open') {
|
|
1484
|
+
this.err(`カレンダー実体 ${ent.name} の開場列/閉場列の交互が破れている: `
|
|
1485
|
+
+ `${this.rt.fmt(to)} の open の直前のマーカーも open`
|
|
1486
|
+
+ `(sessionOpens/sessionCloses は半開区間の列を定める——データ相対エラー・ADR-41 判断 2)`);
|
|
1487
|
+
}
|
|
1488
|
+
state = 'open';
|
|
1489
|
+
i++;
|
|
1490
|
+
}
|
|
1491
|
+
else {
|
|
1492
|
+
if (state === 'closed') {
|
|
1493
|
+
this.err(`カレンダー実体 ${ent.name} の開場列/閉場列の交互が破れている: `
|
|
1494
|
+
+ `${this.rt.fmt(tc)} の close の直前のマーカーも close`
|
|
1495
|
+
+ `(sessionOpens/sessionCloses は半開区間の列を定める——データ相対エラー・ADR-41 判断 2)`);
|
|
1496
|
+
}
|
|
1497
|
+
state = 'closed';
|
|
1498
|
+
j++;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
/** isOpen(t) の判定(ADR-41 判断 3): t が bizOpen セッションの半開区間の和に入るか。
|
|
1504
|
+
* 覆域は証人規則の三分岐——真偽が sessionOpens/sessionCloses/nonWorking の註釈区間に依存するなら範囲外
|
|
1505
|
+
* (filter 文脈は落として註釈・純値文脈は明示エラー。ADR-37 判断 6 の分類器と同じ層) */
|
|
1506
|
+
isOpenAt(ms, env) {
|
|
1507
|
+
const f = this.deriveBizFine(env);
|
|
1508
|
+
const k = ivIndexOf(f.segIv, ms);
|
|
1509
|
+
if (k >= 0) {
|
|
1510
|
+
// セッション断片内: 判定材料=[開場点, t] のマーカー既知性・開場日の営業日性
|
|
1511
|
+
const o = f.segIv[k].start;
|
|
1512
|
+
const hit = [
|
|
1513
|
+
...f.markerAnn.filter(a => a.from <= ms && a.to > o),
|
|
1514
|
+
...annAt(f.dayAnn, f.segDayStart[k]),
|
|
1515
|
+
];
|
|
1516
|
+
if (hit.length > 0)
|
|
1517
|
+
this.openOut(ms, hit);
|
|
1518
|
+
return f.segKept[k];
|
|
1519
|
+
}
|
|
1520
|
+
// セッション外: 直前の既知マーカー〜t に未知のマーカー(註釈区間)が挟まらなければ閉場で確定
|
|
1521
|
+
const j = ptUpperBound(f.markers, ms) - 1;
|
|
1522
|
+
const lo = j >= 0 ? f.markers[j] : this.rt.epoch; // 実体化の頭は閉扱い(頭の切り欠き=保守側)
|
|
1523
|
+
const hit = f.markerAnn.filter(a => a.from <= ms && a.to > lo);
|
|
1524
|
+
if (hit.length > 0)
|
|
1525
|
+
this.openOut(ms, hit);
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
/** isOpen の範囲外シグナル(ADR-41 判断 3 の範囲外側・ADR-37 の分類器の流儀) */
|
|
1529
|
+
openOut(ms, hit) {
|
|
1530
|
+
const a = hit[0];
|
|
1531
|
+
throw new OutOfCoverageSignal(`範囲外(out-of-coverage): isOpen の判定点 ${this.rt.fmt(ms)} は ${a.source} の実効被覆域に依存する`
|
|
1532
|
+
+ `(covering ${a.covering}${a.asof ? `, asof ${a.asof}` : ''}——判定は外部・ADR-41/ADR-37)`, hit);
|
|
1533
|
+
}
|
|
1534
|
+
// ---------- 式 ----------
|
|
1535
|
+
evalExpr(e, env) {
|
|
1536
|
+
switch (e.t) {
|
|
1537
|
+
case 'num': return e.v;
|
|
1538
|
+
case 'str': return e.v;
|
|
1539
|
+
case 'name': return this.resolve(e.name, env);
|
|
1540
|
+
case 'qualified': {
|
|
1541
|
+
const inst = this.rt.premises.get(e.ns) ?? this.err(`未定義の premise: ${e.ns}`);
|
|
1542
|
+
const def = inst.findDef(e.name) ?? this.err(`${e.ns}.${e.name} は未定義`);
|
|
1543
|
+
return this.evalDef(inst, e.name, def, env); // 修飾参照は base 値にピン(機構 A)
|
|
1544
|
+
}
|
|
1545
|
+
case 'date': return { k: 'point', ms: this.anchorDate(e.v, env) }; // 錨打ちは在圏 tz(ADR-33)
|
|
1546
|
+
case 'time': return { k: 'time', todMs: e.tod }; // 単独時刻リテラル(ADR-51)
|
|
1547
|
+
case 'width': return { k: 'width', w: e.v };
|
|
1548
|
+
case 'lambda': return { k: 'lambda', params: e.params, body: e.body, env };
|
|
1549
|
+
case 'list': return this.evalList(e, env);
|
|
1550
|
+
case 'neg': return -this.num(this.evalExpr(e.e, env));
|
|
1551
|
+
case 'not': return !this.bool(this.evalExpr(e.e, env));
|
|
1552
|
+
case 'ternary':
|
|
1553
|
+
return this.bool(this.evalExpr(e.c, env)) ? this.evalExpr(e.a, env) : this.evalExpr(e.b, env);
|
|
1554
|
+
case 'bin': return this.evalBin(e, env);
|
|
1555
|
+
case 'index': {
|
|
1556
|
+
const l = this.evalExpr(e.target, env);
|
|
1557
|
+
if (!Array.isArray(l))
|
|
1558
|
+
this.err('添字はリストにのみ適用できる');
|
|
1559
|
+
const i = this.num(this.evalExpr(e.index, env));
|
|
1560
|
+
if (i < 0 || i >= l.length)
|
|
1561
|
+
this.err(`添字が範囲外: ${i}`);
|
|
1562
|
+
return l[i];
|
|
1563
|
+
}
|
|
1564
|
+
case 'call': return this.evalCall(e, env);
|
|
1565
|
+
case 'pipe': {
|
|
1566
|
+
const v = this.evalExpr(e.head, env);
|
|
1567
|
+
// everyInstant は連続基底の全点=実体化できないので strideBy が直接受ける(§4.2)
|
|
1568
|
+
if (isObj(v) && v.k === 'instant') {
|
|
1569
|
+
if (e.stages[0]?.name !== 'strideBy') {
|
|
1570
|
+
this.err('everyInstant は strideBy を直後に要する(プロトタイプ)');
|
|
1571
|
+
}
|
|
1572
|
+
let s = this.applyStage({ k: 'stream', pts: [], wins: [], align: null, ann: [], endless: true }, e.stages[0], env);
|
|
1573
|
+
for (const st of e.stages.slice(1))
|
|
1574
|
+
s = this.applyStage(s, st, env);
|
|
1575
|
+
return s;
|
|
1576
|
+
}
|
|
1577
|
+
let s = this.toStream(v, e.head.t === 'name' ? e.head.name : undefined);
|
|
1578
|
+
for (const st of e.stages)
|
|
1579
|
+
s = this.applyStage(s, st, env);
|
|
1580
|
+
return s;
|
|
1581
|
+
}
|
|
1582
|
+
case 'combine': {
|
|
1583
|
+
const ls = this.toStream(this.evalExpr(e.l, env));
|
|
1584
|
+
const rs = this.toStream(this.evalExpr(e.r, env));
|
|
1585
|
+
// ADR-36: & と \ は両辺同一 G を要求。| は不問(混合の出力は整列なし)。
|
|
1586
|
+
// 空虚適合(空テーブル・ADR-45)は検査に通り、出力整列は相手側を継承する
|
|
1587
|
+
let align;
|
|
1588
|
+
if (e.op === '|') {
|
|
1589
|
+
align = isVacuous(ls.align) ? rs.align : isVacuous(rs.align) ? ls.align
|
|
1590
|
+
: gridEq(ls.align, rs.align) ? ls.align : null;
|
|
1591
|
+
}
|
|
1592
|
+
else {
|
|
1593
|
+
this.checkAlign(ls.align, rs.align, `結合子 ${e.op}`);
|
|
1594
|
+
align = isVacuous(ls.align) ? rs.align : ls.align;
|
|
1595
|
+
}
|
|
1596
|
+
const a = ls.pts, b = rs.pts;
|
|
1597
|
+
const pts = e.op === '|' ? ptUnion(a, b) : e.op === '&' ? ptInter(a, b) : ptDiff(a, b);
|
|
1598
|
+
// 註釈は両辺の和・自動相殺なし(`|` も例外にしない。ADR-37 判断 4)。
|
|
1599
|
+
// endless: 和は片方でも無限なら無限・積は両方・差は左辺(右辺は削るだけ)
|
|
1600
|
+
const endless = e.op === '|' ? ls.endless || rs.endless
|
|
1601
|
+
: e.op === '&' ? ls.endless && rs.endless : ls.endless;
|
|
1602
|
+
return { k: 'stream', pts, wins: [], align, ann: annUnion(ls.ann, rs.ann), endless };
|
|
1603
|
+
}
|
|
1604
|
+
case 'gen': return this.evalGen(e, env);
|
|
1605
|
+
default: this.err(`未対応の式: ${e.t}`);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
evalList(e, env) {
|
|
1609
|
+
const vals = [];
|
|
1610
|
+
for (const el of e.elems) {
|
|
1611
|
+
if ('t' in el && el.t === 'range') {
|
|
1612
|
+
const a = this.point(this.evalExpr(el.a, env));
|
|
1613
|
+
const b = this.point(this.evalExpr(el.b, env));
|
|
1614
|
+
const tz = this.tzObjOf(env);
|
|
1615
|
+
for (let p = a; p <= b; p = tz.addCivilDays(p, 1)) { // 連続日への展開糖衣(§3.8・市民日歩進)
|
|
1616
|
+
vals.push({ k: 'point', ms: p });
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
else {
|
|
1620
|
+
vals.push(this.evalExpr(el, env));
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
// 時点要素のリストは時間ストリーム定数に昇格(ADR-26)
|
|
1624
|
+
if (vals.length > 0 && vals.every(v => isObj(v) && v.k === 'point')) {
|
|
1625
|
+
const pts = vals.map(p => p.ms);
|
|
1626
|
+
for (let i = 1; i < pts.length; i++) {
|
|
1627
|
+
if (pts[i] <= pts[i - 1])
|
|
1628
|
+
this.err('テーブルリテラルは昇順・重複なし(§3.8 静的エラー)');
|
|
1629
|
+
}
|
|
1630
|
+
let labels;
|
|
1631
|
+
if (e.labels) {
|
|
1632
|
+
if (e.labels.t !== 'list')
|
|
1633
|
+
this.err('labels: はリストを取る');
|
|
1634
|
+
const ls = e.labels.elems.map(el => ('t' in el && el.t === 'name') ? el.name : this.err('labels: の要素はラベル名'));
|
|
1635
|
+
if (ls.length !== pts.length)
|
|
1636
|
+
this.err('labels: は時点列と同長(ADR-30)');
|
|
1637
|
+
labels = ls;
|
|
1638
|
+
}
|
|
1639
|
+
let covIv;
|
|
1640
|
+
let covDesc;
|
|
1641
|
+
let concluded = false;
|
|
1642
|
+
if (e.covering) {
|
|
1643
|
+
const r = this.resolveCovering(e.covering, env);
|
|
1644
|
+
covIv = r.iv;
|
|
1645
|
+
covDesc = r.desc;
|
|
1646
|
+
concluded = r.concluded;
|
|
1647
|
+
// 静的検査(ADR-37 判断 1): 主張と値の整合——列の全要素は covering に包含
|
|
1648
|
+
for (const p of pts) {
|
|
1649
|
+
if (!ivContainsPoint(covIv, p)) {
|
|
1650
|
+
this.err(`テーブルの要素が covering の外: ${this.rt.fmt(p)}(列の全要素は covering に包含——ADR-37)`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
// 省略時は列の端(閉区間 [先頭要素, 末尾要素])が覆域=最狭の主張(ADR-26/37 既定)
|
|
1656
|
+
covIv = [{ start: pts[0], end: pts[pts.length - 1] + 1 }];
|
|
1657
|
+
covDesc = `${this.rt.fmt(pts[0])}..${this.rt.fmt(pts[pts.length - 1])}`;
|
|
1658
|
+
}
|
|
1659
|
+
const asof = this.asofOf(env.members);
|
|
1660
|
+
// ADR-36: 全要素が「日付のみの字句」→ 錨打ちに使われた tz の市民日グリッド。
|
|
1661
|
+
// 時刻付き・字句でない要素(計算値・名前参照)を含む → なし
|
|
1662
|
+
const dateOnly = e.elems.every(el => ('t' in el && el.t === 'range')
|
|
1663
|
+
? el.a.t === 'date' && !el.a.v.hasTime && el.b.t === 'date' && !el.b.v.hasTime
|
|
1664
|
+
: el.t === 'date' && !el.v.hasTime);
|
|
1665
|
+
return { k: 'table', pts, labels, covIv, covDesc, concluded, ...(asof ? { asof } : {}),
|
|
1666
|
+
align: dateOnly ? this.dayGrain(env) : null };
|
|
1667
|
+
}
|
|
1668
|
+
// 空テーブル(ADR-45・F98): 空リストは covering: 後置に限り時間ストリーム定数に昇格する。
|
|
1669
|
+
// 「点ゼロだが覆域は主張したい」の一次形——包含・昇順・同長の各検査は空虚に成立し、
|
|
1670
|
+
// 整列は空虚適合(全整列に適合する第三状態)。生成器は行数 0..N で同一の出力形になる
|
|
1671
|
+
if (vals.length === 0 && e.covering) {
|
|
1672
|
+
let labels;
|
|
1673
|
+
if (e.labels) {
|
|
1674
|
+
if (e.labels.t !== 'list')
|
|
1675
|
+
this.err('labels: はリストを取る');
|
|
1676
|
+
if (e.labels.elems.length !== 0)
|
|
1677
|
+
this.err('labels: は時点列と同長(ADR-30)——空テーブルに付く labels: は [] のみ');
|
|
1678
|
+
labels = [];
|
|
1679
|
+
}
|
|
1680
|
+
const r = this.resolveCovering(e.covering, env);
|
|
1681
|
+
const asof = this.asofOf(env.members);
|
|
1682
|
+
return { k: 'table', pts: [], labels, covIv: r.iv, covDesc: r.desc, concluded: r.concluded,
|
|
1683
|
+
...(asof ? { asof } : {}), align: VACUOUS_GRAIN };
|
|
1684
|
+
}
|
|
1685
|
+
if (vals.length === 0 && e.labels) {
|
|
1686
|
+
this.err('空テーブルは covering: を明示する(省略既定「列の端」が空列で定義できない。ADR-45)');
|
|
1687
|
+
}
|
|
1688
|
+
if (e.labels || e.covering)
|
|
1689
|
+
this.err('labels:/covering: は時点列(テーブルリテラル)にのみ付く');
|
|
1690
|
+
return vals;
|
|
1691
|
+
}
|
|
1692
|
+
/** covering-list の解決(ADR-37 判断 9): 端は premise tz の市民日・年は暦年・
|
|
1693
|
+
* 端の省略=開端(完結主張=±∞)・区間リスト=中抜けの申告 */
|
|
1694
|
+
resolveCovering(list, env) {
|
|
1695
|
+
const iv = [];
|
|
1696
|
+
const parts = [];
|
|
1697
|
+
const tz = this.tzObjOf(env);
|
|
1698
|
+
const dateStr = (ms) => {
|
|
1699
|
+
const f = tz.localFields(ms);
|
|
1700
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
1701
|
+
return `${f.y}-${p(f.mo)}-${p(f.d)}`;
|
|
1702
|
+
};
|
|
1703
|
+
for (const r of list) {
|
|
1704
|
+
const start = r.a ? this.coveringEdge(r.a, env, false) : -Infinity;
|
|
1705
|
+
const end = r.b ? this.coveringEdge(r.b, env, true) : Infinity;
|
|
1706
|
+
if (end <= start)
|
|
1707
|
+
this.err('covering: 区間の端が逆順');
|
|
1708
|
+
iv.push({ start, end });
|
|
1709
|
+
parts.push(`${r.a ? dateStr(start) : ''}..${r.b ? dateStr(end - 1) : ''}`);
|
|
1710
|
+
}
|
|
1711
|
+
for (let i = 1; i < iv.length; i++) {
|
|
1712
|
+
if (iv[i].start < iv[i - 1].end)
|
|
1713
|
+
this.err('covering: 区間リストは昇順・重複なし(ADR-37 判断 9)');
|
|
1714
|
+
}
|
|
1715
|
+
return { iv, desc: parts.join(', '), concluded: iv.some(x => x.start === -Infinity || x.end === Infinity) };
|
|
1716
|
+
}
|
|
1717
|
+
/** covering の端は premise の tz の市民日で解決する(ADR-37 判断 1) */
|
|
1718
|
+
coveringEdge(e, env, isEnd) {
|
|
1719
|
+
const tz = this.tzObjOf(env);
|
|
1720
|
+
if (e.t === 'date' && !e.v.hasTime) { // 日付略記はフィールドで市民日解決(+1 日も市民日歩進)
|
|
1721
|
+
return tz.civilDayStart(e.v.y, e.v.mo, isEnd ? e.v.d + 1 : e.v.d);
|
|
1722
|
+
}
|
|
1723
|
+
const v = this.evalExpr(e, env);
|
|
1724
|
+
if (typeof v === 'number') { // 年だけの略記(§5.6 covering-range)
|
|
1725
|
+
return tz.civilDayStart(isEnd ? v + 1 : v, 1, 1);
|
|
1726
|
+
}
|
|
1727
|
+
if (isObj(v) && v.k === 'point')
|
|
1728
|
+
return isEnd ? tz.addCivilDays(tz.floorToDay(v.ms), 1) : v.ms;
|
|
1729
|
+
this.err('covering: の端は年または日付');
|
|
1730
|
+
}
|
|
1731
|
+
evalBin(e, env) {
|
|
1732
|
+
if (e.op === 'and')
|
|
1733
|
+
return this.bool(this.evalExpr(e.l, env)) && this.bool(this.evalExpr(e.r, env));
|
|
1734
|
+
if (e.op === 'or')
|
|
1735
|
+
return this.bool(this.evalExpr(e.l, env)) || this.bool(this.evalExpr(e.r, env));
|
|
1736
|
+
const l = this.evalExpr(e.l, env);
|
|
1737
|
+
const r = this.evalExpr(e.r, env);
|
|
1738
|
+
switch (e.op) {
|
|
1739
|
+
case '+': return this.num(l) + this.num(r);
|
|
1740
|
+
case '-': return this.num(l) - this.num(r);
|
|
1741
|
+
case '*': return this.num(l) * this.num(r);
|
|
1742
|
+
case '/': return this.num(l) / this.num(r);
|
|
1743
|
+
case 'mod': {
|
|
1744
|
+
const a = this.num(l), b = this.num(r);
|
|
1745
|
+
return ((a % b) + b) % b;
|
|
1746
|
+
}
|
|
1747
|
+
case 'div': return Math.floor(this.num(l) / this.num(r));
|
|
1748
|
+
case '<': return this.num(l) < this.num(r);
|
|
1749
|
+
case '<=': return this.num(l) <= this.num(r);
|
|
1750
|
+
case '>': return this.num(l) > this.num(r);
|
|
1751
|
+
case '>=': return this.num(l) >= this.num(r);
|
|
1752
|
+
case '==': return this.eq(l, r);
|
|
1753
|
+
case '!=': return !this.eq(l, r);
|
|
1754
|
+
case 'in': {
|
|
1755
|
+
if (!Array.isArray(r))
|
|
1756
|
+
this.err('in の右辺はリスト');
|
|
1757
|
+
return r.some(x => this.eq(l, x));
|
|
1758
|
+
}
|
|
1759
|
+
default: this.err(`未対応の演算子: ${e.op}`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
eq(l, r) {
|
|
1763
|
+
if (isObj(l) && l.k === 'point' && isObj(r) && r.k === 'point')
|
|
1764
|
+
return l.ms === r.ms;
|
|
1765
|
+
if (isObj(l) && l.k === 'time' && isObj(r) && r.k === 'time')
|
|
1766
|
+
return l.todMs === r.todMs; // ADR-51(忘れると恒偽)
|
|
1767
|
+
return l === r;
|
|
1768
|
+
}
|
|
1769
|
+
evalCall(e, env) {
|
|
1770
|
+
if (e.callee.t === 'name') {
|
|
1771
|
+
const name = e.callee.name;
|
|
1772
|
+
if (name === 'external')
|
|
1773
|
+
return this.evalExternal(e, env); // 外部供給宣言(ADR-46)
|
|
1774
|
+
// 射影(§4.9・ADR-30)。窓所属の失敗は実効被覆域で分類(ADR-37 判断 6)
|
|
1775
|
+
if (name === 'ordinalIn') {
|
|
1776
|
+
const [uV, wV, dV] = e.args.map(a => this.evalExpr(a.value, env));
|
|
1777
|
+
const d = this.point(dV);
|
|
1778
|
+
// 免除系の tz 名検査(ADR-40): 単位窓×枠窓・述語入力×枠窓
|
|
1779
|
+
this.checkTzMembership(this.winGrainOf(uV), this.winGrainOf(wV), 'ordinalIn(単位窓×枠窓)');
|
|
1780
|
+
this.checkTzMembership(this.predicateAlign, this.winGrainOf(wV), 'ordinalIn', true);
|
|
1781
|
+
const u = this.intervalsOf(uV, env);
|
|
1782
|
+
const w = this.intervalsOf(wV, env);
|
|
1783
|
+
const wi = ivIndexOf(w, d);
|
|
1784
|
+
if (wi < 0)
|
|
1785
|
+
this.outOrErr(d, this.winAnnOfV(wV), 'ordinalIn: 点が枠窓の外');
|
|
1786
|
+
const ui = ivIndexOf(u, d);
|
|
1787
|
+
if (ui < 0)
|
|
1788
|
+
this.outOrErr(d, this.winAnnOfV(uV), 'ordinalIn: 点が単位窓の外');
|
|
1789
|
+
this.winCovOrOut(w[wi], this.winAnnOfV(wV), 'ordinalIn(枠窓)');
|
|
1790
|
+
this.winCovOrOut(u[ui], this.winAnnOfV(uV), 'ordinalIn(単位窓)');
|
|
1791
|
+
// 整合検査(ADR-50・ADR-36 判断 7 の免除の精密化): 経過幅グリッドの単位窓 × 枠窓では
|
|
1792
|
+
// 枠窓の開始が単位の目盛り上にあること(per-instance・tz 履歴依存=データ相対の層)。
|
|
1793
|
+
// 外れると「半端な序数」が黙って出る(紀元差が単位幅の整数倍でない tz——Kathmandu 1986・
|
|
1794
|
+
// Singapore 1981・Lord_Howe の半時 DST)——黙らせず明示エラーにする
|
|
1795
|
+
const ug = this.winGrainOf(uV);
|
|
1796
|
+
if (ug && ug.kind === 'elapsed' && ug.step > 0) {
|
|
1797
|
+
const rem = ((w[wi].start - ug.phase) % ug.step + ug.step) % ug.step;
|
|
1798
|
+
if (rem !== 0) {
|
|
1799
|
+
this.err(`ordinalIn: 枠窓の開始 ${this.rt.fmt(w[wi].start)} が単位グリッドの目盛り上に`
|
|
1800
|
+
+ 'ない(経過幅タイルと市民窓の不整合——紀元差が単位幅の整数倍でない tz。壁時計の帯は'
|
|
1801
|
+
+ ' isOpen・壁時計の時点は at(Thh:mm) が正準。ADR-50/51)');
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
let first = ui;
|
|
1805
|
+
while (first > 0 && u[first - 1].start >= w[wi].start)
|
|
1806
|
+
first--;
|
|
1807
|
+
return ui - first + 1; // 1 起点(ADR-30)
|
|
1808
|
+
}
|
|
1809
|
+
if (name === 'epochOrdinal') {
|
|
1810
|
+
const [uV, dV] = e.args.map(a => this.evalExpr(a.value, env));
|
|
1811
|
+
const d = this.point(dV);
|
|
1812
|
+
this.checkTzMembership(this.predicateAlign, this.winGrainOf(uV), 'epochOrdinal', true); // ADR-40
|
|
1813
|
+
const uIv = this.intervalsOf(uV, env);
|
|
1814
|
+
const idx = ivIndexOf(uIv, d);
|
|
1815
|
+
if (idx < 0)
|
|
1816
|
+
this.outOrErr(d, this.winAnnOfV(uV), 'epochOrdinal: 点が窓の外');
|
|
1817
|
+
this.winCovOrOut(uIv[idx], this.winAnnOfV(uV), 'epochOrdinal');
|
|
1818
|
+
return idx; // 存在する窓列の通し序数(先頭 0。データ由来窓の規則は F60=ADR-31 改訂で確定)
|
|
1819
|
+
}
|
|
1820
|
+
// 窓所属の述語(仮称 coincides。ADR-38): 点 d の属する w 窓の中に S の点が在るか——
|
|
1821
|
+
// 値式の有界存在量化。整列要求(同一 G)は課さない(区間所属=ADR-36 判断 7 の免除系)
|
|
1822
|
+
if (name === 'coincides') {
|
|
1823
|
+
if (e.args.length !== 3)
|
|
1824
|
+
this.err('coincides(S, w, d) は 3 引数(ADR-38)');
|
|
1825
|
+
// S: 軸位置と同一規則の準用(束縛名・修飾名・インライン式→有効点ストリーム)。
|
|
1826
|
+
// premise 名の読み替えは適用しない(標準導出は bizDay の名で書ける)
|
|
1827
|
+
const sV = this.evalExpr(e.args[0].value, env);
|
|
1828
|
+
if (isObj(sV) && sV.k === 'windows') {
|
|
1829
|
+
this.err('coincides: S に窓語は置けない——原子点列への暗黙降格は恒真化の罠'
|
|
1830
|
+
+ '(点列を意図するなら month |> first を書く。ADR-38 判断 3)');
|
|
1831
|
+
}
|
|
1832
|
+
const S = this.toStream(sV);
|
|
1833
|
+
const wV = this.evalExpr(e.args[1].value, env);
|
|
1834
|
+
if (isObj(wV) && wV.k === 'cycle') {
|
|
1835
|
+
this.err('coincides: w に cycle 名は立てられない——cycle は窓でなくラベル(ADR-21/38)');
|
|
1836
|
+
}
|
|
1837
|
+
const w = this.intervalsOf(wV, env);
|
|
1838
|
+
const d = this.point(this.evalExpr(e.args[2].value, env));
|
|
1839
|
+
// tz の静的検査(ADR-38 判断 5): 幅・位相は不問(所属だから細分は許す)が、tz 名だけは
|
|
1840
|
+
// 日付座標系そのもの——クロス tz の黙った 1 日ずれを免除系の裏口から再開させない。
|
|
1841
|
+
// S の整列「なし」は検査不能(合法)——coincides は chronos 所属であり「同じ日付ラベル」ではない(F69)
|
|
1842
|
+
const wg = this.winGrainOf(wV);
|
|
1843
|
+
if (S.align && S.align.kind === 'civil' && S.align.tz
|
|
1844
|
+
&& wg && wg.kind === 'civil' && wg.tz && S.align.tz !== wg.tz) {
|
|
1845
|
+
this.err(`coincides: S と w の tz 名が不一致(S="${S.align.tz}"・w="${wg.tz}")`
|
|
1846
|
+
+ '——chronos 所属であり「同じ日付ラベル」の所属ではない(F69 の再錨の領分。ADR-38 判断 5)');
|
|
1847
|
+
}
|
|
1848
|
+
const wi = ivIndexOf(w, d);
|
|
1849
|
+
if (wi < 0)
|
|
1850
|
+
this.outOrErr(d, this.winAnnOfV(wV), 'coincides: 点が窓の外');
|
|
1851
|
+
const win = w[wi];
|
|
1852
|
+
// (0) 窓列自身の覆域検査(F82): 窓境界が未知なら証人の有無以前に範囲外
|
|
1853
|
+
this.winCovOrOut(win, this.winAnnOfV(wV), 'coincides');
|
|
1854
|
+
// 証人規則の三分岐(ADR-38 判断 4。基準は実効被覆域=輸送済み註釈区間の補集合):
|
|
1855
|
+
// (i) 窓内に非註釈区間の S の点(証人)→ 真(∃ は単調・註釈不要)。
|
|
1856
|
+
// 註釈区間内の点は証人にならない(everyDay \ holidays の退化尾部を確信に使わない)
|
|
1857
|
+
for (let i = ptUpperBound(S.pts, win.start - 1); i < S.pts.length && S.pts[i] < win.end; i++) {
|
|
1858
|
+
if (annAt(S.ann, S.pts[i]).length === 0)
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
// (ii) 証人なし ∧ 窓 ∩ S の註釈区間 ≠ ∅ → 範囲外(読んだ窓を運び filter が逆像拡幅する=F75)
|
|
1862
|
+
const overlap = S.ann.filter(a => a.from < win.end && win.start < a.to);
|
|
1863
|
+
if (overlap.length > 0) {
|
|
1864
|
+
const a = overlap[0];
|
|
1865
|
+
throw new OutOfCoverageSignal(`範囲外(out-of-coverage): coincides の窓 [${this.rt.fmt(Math.max(win.start, -8.64e15))}, `
|
|
1866
|
+
+ `${this.rt.fmt(Math.min(win.end, 8.64e15))}) は ${a.source} の実効被覆域に掛かり、証人(非註釈区間の点)が無い`
|
|
1867
|
+
+ `(covering ${a.covering}${a.asof ? `, asof ${a.asof}` : ''}——判定は外部・ADR-38 判断 4)`, overlap, win);
|
|
1868
|
+
}
|
|
1869
|
+
return false; // (iii) 窓が完全に実効被覆域内 → 偽(覆域完全性に依存する側)
|
|
1870
|
+
}
|
|
1871
|
+
// 営業中の導出値述語(仮称 isOpen。ADR-41 判断 3): t が bizOpen セッションの半開区間の
|
|
1872
|
+
// 和に入るか——実体相対(C の文化で解決する chronos 述語。読み手の tz に依存しないので
|
|
1873
|
+
// coincides と違い tz の静的検査は掛からない=F89)。calendar: 非在圏では自由な束縛名
|
|
1874
|
+
if (name === 'isOpen' && env.members.has('calendar')) {
|
|
1875
|
+
if (this.isBoundName('isOpen', env)) {
|
|
1876
|
+
this.err('calendar: の在圏では isOpen は言語予約の導出名——手動束縛は静的エラー(独自の述語は別名で。ADR-41)');
|
|
1877
|
+
}
|
|
1878
|
+
if (e.args.length !== 1 || e.args[0].name)
|
|
1879
|
+
this.err('isOpen(t) は位置引数 1 個(ADR-41)');
|
|
1880
|
+
return this.isOpenAt(this.point(this.evalExpr(e.args[0].value, env)), env);
|
|
1881
|
+
}
|
|
1882
|
+
if (name === 'not')
|
|
1883
|
+
return !this.bool(this.evalExpr(e.args[0].value, env));
|
|
1884
|
+
// 引数付き束縛(糖衣・値関数): 呼び出し時の在圏 premise で遅延解決(§4.8)
|
|
1885
|
+
const local = this.lookupLocal(name, env);
|
|
1886
|
+
if (local === undefined) {
|
|
1887
|
+
const binding = this.lookupBinding(name, env);
|
|
1888
|
+
if (binding && binding.decl.params.length > 0) {
|
|
1889
|
+
const locals = this.bindParams(binding.decl.params, e.args, env, name);
|
|
1890
|
+
// 引数付き束縛の右辺も呼び出し側 locals 鎖を遮断(ADR-53 判断 3 の完遂=還流第 17 便 §1:
|
|
1891
|
+
// この経路だけ漏れて「T(x) = dayNo(d)」が使用箇所の変数名に依存していた。実引数は遮断前の
|
|
1892
|
+
// env で評価済み——値渡しの意味は不変)
|
|
1893
|
+
const base = { ...(binding.premise ? { ...env, premise: binding.premise } : env),
|
|
1894
|
+
locals: null, parent: null };
|
|
1895
|
+
const prevSevered = this.severedLocalsEnv;
|
|
1896
|
+
this.severedLocalsEnv = this.severedLocalsEnv ?? env; // 最外の遮断点を保持
|
|
1897
|
+
try {
|
|
1898
|
+
return this.evalExpr(binding.decl.rhs, childEnv(base, locals));
|
|
1899
|
+
}
|
|
1900
|
+
finally {
|
|
1901
|
+
this.severedLocalsEnv = prevSevered;
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const callee = this.evalExpr(e.callee, env);
|
|
1907
|
+
// 束縛名を診断用に随伴(窓束縛のインスタンス参照・射影のエラーメッセージ。ADR-42)
|
|
1908
|
+
const calleeName = e.callee.t === 'name' ? e.callee.name
|
|
1909
|
+
: e.callee.t === 'qualified' ? `${e.callee.ns}.${e.callee.name}` : undefined;
|
|
1910
|
+
return this.applyValue(callee, e.args.map(a => this.evalExpr(a.value, env)), env, calleeName);
|
|
1911
|
+
}
|
|
1912
|
+
lookupLocal(name, env) {
|
|
1913
|
+
for (let e = env; e; e = e.parent) {
|
|
1914
|
+
if (e.locals?.has(name))
|
|
1915
|
+
return e.locals.get(name);
|
|
1916
|
+
}
|
|
1917
|
+
return undefined;
|
|
1918
|
+
}
|
|
1919
|
+
lookupBinding(name, env) {
|
|
1920
|
+
if (env.premise) {
|
|
1921
|
+
const d = env.premise.findDef(name);
|
|
1922
|
+
if (d)
|
|
1923
|
+
return { decl: d, premise: env.premise };
|
|
1924
|
+
}
|
|
1925
|
+
const cs = this.calendarSystemOf(env);
|
|
1926
|
+
if (cs) {
|
|
1927
|
+
const d = cs.findDef(name);
|
|
1928
|
+
if (d)
|
|
1929
|
+
return { decl: d, premise: cs };
|
|
1930
|
+
}
|
|
1931
|
+
const t = this.rt.topBindings.get(name);
|
|
1932
|
+
if (t)
|
|
1933
|
+
return { decl: t, premise: null };
|
|
1934
|
+
return null;
|
|
1935
|
+
}
|
|
1936
|
+
bindParams(params, args, env, name) {
|
|
1937
|
+
const locals = new Map();
|
|
1938
|
+
const positional = args.filter(a => !a.name);
|
|
1939
|
+
let pi = 0;
|
|
1940
|
+
for (const p of params) {
|
|
1941
|
+
const arg = p.key !== undefined
|
|
1942
|
+
? args.find(a => a.name === p.key) ?? this.err(`${name}: 名前付き引数 ${p.key}: が必要`)
|
|
1943
|
+
: positional[pi++] ?? this.err(`${name}: 引数が不足`);
|
|
1944
|
+
locals.set(p.name, this.evalExpr(arg.value, env));
|
|
1945
|
+
}
|
|
1946
|
+
// 未知の名前付き引数・余剰の位置引数を黙って捨てない(ADR-39——タイポの検査面)
|
|
1947
|
+
const keys = new Set(params.filter(p => p.key !== undefined).map(p => p.key));
|
|
1948
|
+
for (const a of args) {
|
|
1949
|
+
if (a.name && !keys.has(a.name))
|
|
1950
|
+
this.err(`${name}: 未知の名前付き引数 ${a.name}:(黙って捨てない——ADR-39)`);
|
|
1951
|
+
}
|
|
1952
|
+
if (pi < positional.length)
|
|
1953
|
+
this.err(`${name}: 引数が多すぎる(位置引数 ${positional.length} 個・仮引数 ${pi} 個)`);
|
|
1954
|
+
return locals;
|
|
1955
|
+
}
|
|
1956
|
+
/** 値の適用: ラムダ・cycle(点→ラベル)・ラベル付きテーブル(点→ラベル。ADR-30 束縛名射影)・
|
|
1957
|
+
* label: 付与式つきの窓(点→ラベル。§4.9・ADR-34 束縛名射影)。
|
|
1958
|
+
* 窓束縛は引数の型で dispatch(ADR-42 判断 4: 点→射影・値→窓インスタンス参照=逆像。
|
|
1959
|
+
* 判定時点は糖衣展開後・実引数束縛後=実引数の評価値の型) */
|
|
1960
|
+
applyValue(callee, args, env, calleeName) {
|
|
1961
|
+
if (isObj(callee)) {
|
|
1962
|
+
if (callee.k === 'lambda') {
|
|
1963
|
+
// 糖衣定義の再帰適用の検出(§4.8「展開=有限の機械的差し込み」・F110): 本体評価中に
|
|
1964
|
+
// 同じ本体の適用が再入したら静的エラー(自己再帰も相互再帰もここで止まる)
|
|
1965
|
+
if (this.applyingBodies.has(callee.body)) {
|
|
1966
|
+
const path = [...this.applyingBodies.values(), calleeName ?? '(無名ラムダ)'].join(' → ');
|
|
1967
|
+
this.err(`糖衣定義の循環参照は静的エラー: ${path}——糖衣の展開は有限(再帰は書けない。§4.8 の依存解析)`);
|
|
1968
|
+
}
|
|
1969
|
+
this.applyingBodies.set(callee.body, calleeName ?? '(無名ラムダ)');
|
|
1970
|
+
try {
|
|
1971
|
+
const locals = new Map();
|
|
1972
|
+
callee.params.forEach((p, i) => locals.set(p, args[i]));
|
|
1973
|
+
return this.evalExpr(callee.body, childEnv(callee.env, locals));
|
|
1974
|
+
}
|
|
1975
|
+
finally {
|
|
1976
|
+
this.applyingBodies.delete(callee.body);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
if (callee.k === 'cycle') {
|
|
1980
|
+
if (isValueV(args[0])) { // ADR-42 判断 5/7 (b): 値引数は窓束縛のみ
|
|
1981
|
+
this.err('インスタンス参照(値引数適用)は窓束縛のみ——cycle は台の点列を filter で書く'
|
|
1982
|
+
+ '(everyDay |> filter(d => weekday(d) == Mon)。ADR-42 判断 5/7 (b))');
|
|
1983
|
+
}
|
|
1984
|
+
this.checkTzMembership(this.predicateAlign, callee.grain, 'cycle 射影', true); // ADR-40(weekday 等)
|
|
1985
|
+
const ms = this.point(args[0]);
|
|
1986
|
+
const idx = ivIndexOf(callee.iv, ms);
|
|
1987
|
+
if (idx < 0)
|
|
1988
|
+
this.err('cycle: 点が対象窓の外');
|
|
1989
|
+
const L = callee.labels.length;
|
|
1990
|
+
return callee.labels[(((idx - callee.anchor) % L) + L) % L];
|
|
1991
|
+
}
|
|
1992
|
+
if (callee.k === 'table') {
|
|
1993
|
+
if (isValueV(args[0])) { // ADR-42 判断 5/7 (b): 値引数は窓束縛のみ
|
|
1994
|
+
this.err('インスタンス参照(値引数適用)は窓束縛のみ——テーブルは台の点列を filter で書く'
|
|
1995
|
+
+ '(sekki |> filter(s => sekki(s) == 立春)。ADR-42 判断 5/7 (b))');
|
|
1996
|
+
}
|
|
1997
|
+
if (!callee.labels)
|
|
1998
|
+
this.err('ラベル列(labels:)のないテーブルは射影できない');
|
|
1999
|
+
this.registerCoverage(callee.src ?? '(無名テーブル)', callee.covDesc, callee.covIv, callee.concluded, callee.asof);
|
|
2000
|
+
const ms = this.point(args[0]);
|
|
2001
|
+
const i = ptIndexOf(callee.pts, ms);
|
|
2002
|
+
if (i < 0)
|
|
2003
|
+
this.outOrErr(ms, this.tableAnn(callee), 'テーブル射影: 点が列にない');
|
|
2004
|
+
return callee.labels[i];
|
|
2005
|
+
}
|
|
2006
|
+
if (callee.k === 'windows') {
|
|
2007
|
+
const who = `窓束縛 ${calleeName ?? callee.name ?? '(無名)'}`;
|
|
2008
|
+
if (isValueV(args[0])) { // 値引数=窓インスタンス参照(ADR-42 判断 4)
|
|
2009
|
+
if (!callee.labelFn) {
|
|
2010
|
+
this.err(`${who} にラベル源(label:/labels:)がない——インスタンス参照には label: を`
|
|
2011
|
+
+ '付けるか、値関数の filter(everyDay |> filter(d => isoYearNo(d) == 2026) 級)で'
|
|
2012
|
+
+ '書く(ADR-42 判断 7 (a))');
|
|
2013
|
+
}
|
|
2014
|
+
return this.windowInstance(who, { fn: callee.labelFn }, callee.iv, callee.units, callee.grain ?? null, [], [], true, args[0], env);
|
|
2015
|
+
}
|
|
2016
|
+
if (callee.labelFn) {
|
|
2017
|
+
this.checkArgIsPoint(args[0], who); // ADR-42 判断 7 (e)
|
|
2018
|
+
this.checkTzMembership(this.predicateAlign, callee.grain, 'label 射影', true); // ADR-40
|
|
2019
|
+
return this.projectLabel(callee.labelFn, callee.iv, [], args[0], env);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
if (callee.k === 'stream' && callee.wins.length > 0) {
|
|
2023
|
+
const top = callee.wins[callee.wins.length - 1];
|
|
2024
|
+
const who = `窓束縛 ${calleeName ?? top.name ?? '(無名)'}`;
|
|
2025
|
+
if (isValueV(args[0])) { // 値引数=窓インスタンス参照(ADR-42 判断 4。segmentBy 由来の表現)
|
|
2026
|
+
if (!top.labelFn && !top.labels && !top.labelsCycle) {
|
|
2027
|
+
this.err(`${who} にラベル源(label:/labels:)がない——インスタンス参照には label: を`
|
|
2028
|
+
+ '付けるか、値関数の filter(everyDay |> filter(d => isoYearNo(d) == 2026) 級)で'
|
|
2029
|
+
+ '書く(ADR-42 判断 7 (a))');
|
|
2030
|
+
}
|
|
2031
|
+
return this.windowInstance(who, { fn: top.labelFn, labels: top.labels, labelsCycle: top.labelsCycle }, top.iv, callee.pts, callee.align, top.ann, callee.ann, callee.endless, args[0], env);
|
|
2032
|
+
}
|
|
2033
|
+
if (top.labelFn || top.labels || top.labelsCycle) {
|
|
2034
|
+
this.checkArgIsPoint(args[0], who); // ADR-42 判断 7 (e)
|
|
2035
|
+
this.checkTzMembership(this.predicateAlign, top.grain, 'ラベル射影', true); // ADR-40
|
|
2036
|
+
}
|
|
2037
|
+
if (top.labelFn)
|
|
2038
|
+
return this.projectLabel(top.labelFn, top.iv, top.ann, args[0], env);
|
|
2039
|
+
// 窓列への並行ラベル列の束縛名射影(ADR-39): 名前(d) ≡ labels[窓列序数(d の属する窓)]。
|
|
2040
|
+
// 読みは区間所属——空窓のラベルもマーカー点等の窓区間内の任意の点から読める
|
|
2041
|
+
if (top.labels || top.labelsCycle) {
|
|
2042
|
+
const ms = this.point(args[0]);
|
|
2043
|
+
const idx = ivIndexOf(top.iv, ms);
|
|
2044
|
+
if (idx < 0)
|
|
2045
|
+
this.outOrErr(ms, top.ann, 'ラベル射影: 点が窓の外');
|
|
2046
|
+
this.winCovOrOut(top.iv[idx], top.ann, 'ラベル射影');
|
|
2047
|
+
if (top.labelsCycle) { // 周期ラベル(ADR-47): list[(i − i0) mod N]・負も法で正規化(F65 規約)
|
|
2048
|
+
const { list, i0 } = top.labelsCycle;
|
|
2049
|
+
const N = list.length;
|
|
2050
|
+
return list[(((idx - i0) % N) + N) % N];
|
|
2051
|
+
}
|
|
2052
|
+
if (idx >= top.labels.length)
|
|
2053
|
+
this.err('ラベル射影: 窓列序数がラベル列を越えた(内部整合)');
|
|
2054
|
+
return top.labels[idx];
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
if (callee.k === 'stream' && isValueV(args[0])) { // ADR-42 判断 7 (b): 点列束縛
|
|
2058
|
+
this.err('インスタンス参照(値引数適用)は窓束縛のみ——点列束縛は filter で書く'
|
|
2059
|
+
+ '(S |> filter(d => …)。ADR-42 判断 7 (b))');
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
this.err(`適用できない値: ${kindOf(callee)}`);
|
|
2063
|
+
}
|
|
2064
|
+
/** 窓束縛への引数の型域(ADR-42 判断 7 (e)): 点(射影)か値(インスタンス参照)のみ */
|
|
2065
|
+
checkArgIsPoint(arg, who) {
|
|
2066
|
+
if (!isObj(arg) || arg.k !== 'point') {
|
|
2067
|
+
this.err(`${who}: 引数は点(射影)か値(インスタンス参照)——ストリーム等は置けない`
|
|
2068
|
+
+ `(窓所属は within/coincides で。ADR-42 判断 7 (e)): ${kindOf(arg)}`);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
/** 窓インスタンス参照(ADR-42 判断 2): W(v) ≡ W の要素点列 |> filter(d => W(d) == v)。
|
|
2072
|
+
* 要素点列=W の定義が窓に束ねた入力点列のうち W の窓に属する点(grid/span/split 連鎖では
|
|
2073
|
+
* 原子グリッドの目盛りと同値・segmentBy では入力ストリームの点=F93 の修正)。
|
|
2074
|
+
* 出力の註釈区間=窓列の実効被覆域の補集合(判断 3 の輸送行=F94・segmentBy 行の継承)。
|
|
2075
|
+
* ラベルは窓ごとに一定(付与式は窓先頭点で評価=ADR-34 の構造保証)なので窓単位で評価する
|
|
2076
|
+
* (外延等価の最適化——ADR-42 帰結)。ラベル一意でなければ全マッチの和・ゼロマッチは空(ADR-15) */
|
|
2077
|
+
windowInstance(who, src, iv, inputPts, align, winAnn, baseAnn, endless, v, env) {
|
|
2078
|
+
// (f) 自束縛への値引数適用の締め(ADR-34 改訂): 逆像は定義上ラベル射影を内包する——
|
|
2079
|
+
// 「射影を呼ぶ形」だけの検査は W(v) 経由の無限再帰を見逃す(相互参照も labelStack が捕まえる)
|
|
2080
|
+
if (src.fn && this.labelStack.has(src.fn)) {
|
|
2081
|
+
this.err('label: 付与式は定義中の束縛名(自己または相互)への値引数適用(インスタンス参照)を'
|
|
2082
|
+
+ '呼べない——逆像は射影を内包する(ADR-34 改訂・ADR-42 判断 7 (f))');
|
|
2083
|
+
}
|
|
2084
|
+
// labels: リテラル=値域が静的に列挙できる束縛: 型域 (c) と域外 (g) を先に検査(ADR-42 判断 7。
|
|
2085
|
+
// cycle 形〈ADR-47〉のリストも静的な全値域として同じ検査に乗る)
|
|
2086
|
+
const domain = src.labels ?? src.labelsCycle?.list;
|
|
2087
|
+
if (domain) {
|
|
2088
|
+
if (typeof domain[0] !== typeof v) {
|
|
2089
|
+
this.err(`${who}: 値引数の型(${typeof v})がラベル値の型域(${typeof domain[0]})と`
|
|
2090
|
+
+ '不一致(ADR-42 判断 7 (c))');
|
|
2091
|
+
}
|
|
2092
|
+
if (!domain.includes(v)) {
|
|
2093
|
+
this.err(`${who}(${typeof v === 'string' ? `"${v}"` : v}): ラベル値域(labels: リテラル)の外`
|
|
2094
|
+
+ '——域外の値引数は静的エラー(タイポが「註釈なしの空」で流れるのを封じる。'
|
|
2095
|
+
+ 'ADR-42 判断 7 (g)。計算ラベル〈label: 式〉は列挙不能なので該当なし=空)');
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
// 要素点列(判断 2): 窓列・点列ともソート済み=スイープで窓所属だけ拾う
|
|
2099
|
+
const elemPts = [];
|
|
2100
|
+
let wi = 0;
|
|
2101
|
+
for (const p of inputPts) {
|
|
2102
|
+
while (wi < iv.length && p >= iv[wi].end)
|
|
2103
|
+
wi++;
|
|
2104
|
+
if (wi >= iv.length)
|
|
2105
|
+
break;
|
|
2106
|
+
if (p >= iv[wi].start)
|
|
2107
|
+
elemPts.push(p);
|
|
2108
|
+
}
|
|
2109
|
+
// 輸送行(判断 3・F94): マーカー覆域の外では要素点そのものが無く filter が一度も走らない——
|
|
2110
|
+
// 窓列の実効被覆域の補集合をここで湧かせないと F61 と同型の黙った退化になる
|
|
2111
|
+
const elem = { k: 'stream', pts: elemPts, wins: [], align,
|
|
2112
|
+
ann: annUnion(baseAnn, winAnn), endless };
|
|
2113
|
+
// 覆域外に張られた窓(境界未知=F82 の第 5 の窓リーダーサイト)はラベル評価が範囲外シグナルを
|
|
2114
|
+
// 投げ、filter 共通経路が「落として註釈」+窓の逆像への拡幅(F75)で輸送する。
|
|
2115
|
+
// 窓単位のメモはシグナルも保存(点ごとの再投げで輸送は filter と共有・評価は窓 1 回)
|
|
2116
|
+
const memo = new Map();
|
|
2117
|
+
const labelAt = (i) => {
|
|
2118
|
+
const m = memo.get(i);
|
|
2119
|
+
if (m) {
|
|
2120
|
+
if (m.sig)
|
|
2121
|
+
throw m.sig;
|
|
2122
|
+
return m.label;
|
|
2123
|
+
}
|
|
2124
|
+
try {
|
|
2125
|
+
let label;
|
|
2126
|
+
if (src.fn) {
|
|
2127
|
+
label = this.projectLabel(src.fn, iv, winAnn, { k: 'point', ms: iv[i].start }, env);
|
|
2128
|
+
}
|
|
2129
|
+
else if (src.labelsCycle) { // 周期ラベル(ADR-47)
|
|
2130
|
+
this.winCovOrOut(iv[i], winAnn, 'インスタンス参照');
|
|
2131
|
+
const { list, i0 } = src.labelsCycle;
|
|
2132
|
+
const N = list.length;
|
|
2133
|
+
label = list[(((i - i0) % N) + N) % N];
|
|
2134
|
+
}
|
|
2135
|
+
else {
|
|
2136
|
+
this.winCovOrOut(iv[i], winAnn, 'インスタンス参照');
|
|
2137
|
+
if (i >= src.labels.length)
|
|
2138
|
+
this.err('インスタンス参照: 窓列序数がラベル列を越えた(内部整合)');
|
|
2139
|
+
label = src.labels[i];
|
|
2140
|
+
}
|
|
2141
|
+
memo.set(i, { label });
|
|
2142
|
+
return label;
|
|
2143
|
+
}
|
|
2144
|
+
catch (e) {
|
|
2145
|
+
if (e instanceof OutOfCoverageSignal)
|
|
2146
|
+
memo.set(i, { sig: e });
|
|
2147
|
+
throw e;
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
let typeChecked = !!domain;
|
|
2151
|
+
return this.filterByPredicate(elem, ms => {
|
|
2152
|
+
const i = ivIndexOf(iv, ms);
|
|
2153
|
+
if (i < 0)
|
|
2154
|
+
return false; // 要素点列は窓所属で作るので来ない(防御)
|
|
2155
|
+
const label = labelAt(i);
|
|
2156
|
+
if (!typeChecked) { // (c) 計算ラベルの型域は最初の窓の値で検査(ADR-34 判断 3 の等質前提)
|
|
2157
|
+
if (typeof label !== typeof v) {
|
|
2158
|
+
this.err(`${who}: 値引数の型(${typeof v})がラベル値の型域(${typeof label})と`
|
|
2159
|
+
+ '不一致(ADR-42 判断 7 (c))');
|
|
2160
|
+
}
|
|
2161
|
+
typeChecked = true;
|
|
2162
|
+
}
|
|
2163
|
+
return label === v;
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
/** 述語 filter の共通経路(ADR-37 判断 4/6・F75): 述語が範囲外参照を要求した点は落とし、
|
|
2167
|
+
* 依存の註釈区間(窓越し参照は読んだ窓の逆像へ拡幅)を出力へ輸送する。filter 段と
|
|
2168
|
+
* 窓インスタンス参照(ADR-42 判断 3)が共有する。
|
|
2169
|
+
* predicateAlign=述語に流れる点の整列(免除系 tz 検査の評価時近似の文脈。ADR-40) */
|
|
2170
|
+
filterByPredicate(stream, test) {
|
|
2171
|
+
const kept = [];
|
|
2172
|
+
let ann = stream.ann;
|
|
2173
|
+
const prevAlign = this.predicateAlign;
|
|
2174
|
+
this.predicateAlign = stream.align;
|
|
2175
|
+
try {
|
|
2176
|
+
for (const p of stream.pts) {
|
|
2177
|
+
try {
|
|
2178
|
+
if (test(p))
|
|
2179
|
+
kept.push(p);
|
|
2180
|
+
}
|
|
2181
|
+
catch (err) {
|
|
2182
|
+
if (err instanceof OutOfCoverageSignal) {
|
|
2183
|
+
let extra = err.entries;
|
|
2184
|
+
if (err.window) {
|
|
2185
|
+
const w = err.window;
|
|
2186
|
+
extra = [...extra, ...err.entries.map(a => ({ ...a, from: w.start, to: w.end }))];
|
|
2187
|
+
}
|
|
2188
|
+
ann = annUnion(ann, extra);
|
|
2189
|
+
}
|
|
2190
|
+
else
|
|
2191
|
+
throw err;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
finally {
|
|
2196
|
+
this.predicateAlign = prevAlign;
|
|
2197
|
+
}
|
|
2198
|
+
return { ...stream, pts: kept, ann };
|
|
2199
|
+
}
|
|
2200
|
+
/** label: 射影(§4.9・ADR-34): 名前(d) ≡ 付与式(d の属する窓の先頭点)。評価は射影時・遅延(I7) */
|
|
2201
|
+
projectLabel(fn, iv, depAnn, arg, env) {
|
|
2202
|
+
if (this.labelStack.has(fn)) {
|
|
2203
|
+
this.err('label: 付与式は定義中の束縛名(自己または相互)のラベル射影を呼べない(隣接窓参照の裏面・I7)');
|
|
2204
|
+
}
|
|
2205
|
+
const idx = ivIndexOf(iv, this.point(arg));
|
|
2206
|
+
if (idx < 0)
|
|
2207
|
+
this.outOrErr(this.point(arg), depAnn, 'label 射影: 点が窓の外');
|
|
2208
|
+
this.winCovOrOut(iv[idx], depAnn, 'label 射影');
|
|
2209
|
+
this.labelStack.add(fn);
|
|
2210
|
+
try {
|
|
2211
|
+
const v = this.applyValue(fn, [{ k: 'point', ms: iv[idx].start }], env);
|
|
2212
|
+
// ラベル値の型域は値型(数値・論理・列挙名・文字列。ADR-34 判断 3)——点・窓・ストリーム等は不可
|
|
2213
|
+
if (typeof v !== 'number' && typeof v !== 'string' && typeof v !== 'boolean') {
|
|
2214
|
+
this.err(`label: 付与式の値は値型(ADR-34): ${kindOf(v)}`);
|
|
2215
|
+
}
|
|
2216
|
+
return v;
|
|
2217
|
+
}
|
|
2218
|
+
finally {
|
|
2219
|
+
this.labelStack.delete(fn);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
// ---------- premise 層: 窓生成語(§3.6) ----------
|
|
2223
|
+
/** gen 語ごとの既知 named-arg(ADR-39: 未知の名前付き引数を黙って捨てない)。
|
|
2224
|
+
* cycle の label: は既知に含め、genLabelFn の専用エラー(ADR-34)に委ねる */
|
|
2225
|
+
static GEN_NAMED = {
|
|
2226
|
+
grid: new Set(['anchor', 'label']),
|
|
2227
|
+
span: new Set(['phase', 'label']),
|
|
2228
|
+
split: new Set(['by', 'label']),
|
|
2229
|
+
cycle: new Set(['anchor', 'label']),
|
|
2230
|
+
};
|
|
2231
|
+
evalGen(e, env) {
|
|
2232
|
+
for (const key of Object.keys(e.named)) {
|
|
2233
|
+
if (!Evaluator.GEN_NAMED[e.word].has(key)) {
|
|
2234
|
+
this.err(`${e.word}: 未知の名前付き引数 ${key}:(黙って捨てない——ADR-39。`
|
|
2235
|
+
+ `窓のデータラベルは segmentBy の labels:・周期ラベルは窓束縛 cycle か labels: cycle〈ADR-47〉)`);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
const labelFn = this.genLabelFn(e, env); // label: 付与式(§4.9・ADR-34)。cycle は対象外
|
|
2239
|
+
const operand = this.evalExpr(e.operand, env);
|
|
2240
|
+
switch (e.word) {
|
|
2241
|
+
case 'grid': {
|
|
2242
|
+
if (kindOf(operand) !== 'chronos')
|
|
2243
|
+
this.err('grid は chronos だけが受け取る(ADR-29)');
|
|
2244
|
+
const w = this.evalExpr(e.arg, env);
|
|
2245
|
+
if (!isObj(w) || w.k !== 'width')
|
|
2246
|
+
this.err('grid は幅リテラルを取る');
|
|
2247
|
+
const mod = (a, b) => ((a % b) + b) % b;
|
|
2248
|
+
if (w.w.kind === 'civil') {
|
|
2249
|
+
// 市民時幅(ADR-31/33): 位相は在圏 tz の市民日の開始瞬間——DST 切替日は 23/25 時間
|
|
2250
|
+
//(1d は市民日であって 86400s ではない=ADR-11/12 の幅規約)。anchor: で位相・日内オフセット上書き
|
|
2251
|
+
const tz = this.tzObjOf(env);
|
|
2252
|
+
const days = tz.dayStarts(this.rt.computeEnd + (w.w.days + 2) * DAY_MS);
|
|
2253
|
+
const stepDays = w.w.days;
|
|
2254
|
+
let phase = 0; // 紀元(在圏 tz の 1970-01-01 の市民日)からの日序数 mod 幅
|
|
2255
|
+
let off = 0; // 日内オフセット(壁時計)
|
|
2256
|
+
if (e.named.anchor) {
|
|
2257
|
+
const anchor = this.point(this.evalExpr(e.named.anchor, env));
|
|
2258
|
+
const aDay = tz.floorToDay(anchor);
|
|
2259
|
+
// 日内オフセットは壁時計ラベル読み(経過 ms ではない・ADR-31 改訂 2=F87 の修正):
|
|
2260
|
+
// anchor が DST 切替日でも全目盛りが anchor の壁時計時刻を通り、同じ壁時計 anchor の
|
|
2261
|
+
// 2 本が同一 G になる。市民日の開始点は日整列(真夜中遷移 tz の 01:00 開始も off 0)
|
|
2262
|
+
off = anchor === aDay ? 0 : tz.timeOfDayLabel(anchor);
|
|
2263
|
+
phase = mod(tz.dayIndex(aDay), stepDays);
|
|
2264
|
+
}
|
|
2265
|
+
const iv = [];
|
|
2266
|
+
for (let i = phase; i + stepDays < days.length; i += stepDays) {
|
|
2267
|
+
const s = off === 0 ? days[i] : tz.atTimeOfDay(days[i], off);
|
|
2268
|
+
if (s >= this.rt.computeEnd)
|
|
2269
|
+
break;
|
|
2270
|
+
iv.push({ start: s, end: off === 0 ? days[i + stepDays] : tz.atTimeOfDay(days[i + stepDays], off) });
|
|
2271
|
+
}
|
|
2272
|
+
const grain = { kind: 'civil', step: stepDays, phase, off, tz: this.tzNameOf(env) };
|
|
2273
|
+
return { k: 'windows', iv, units: iv.map(x => x.start), labelFn, grain };
|
|
2274
|
+
}
|
|
2275
|
+
// 経過時間幅: 既定位相は在圏 tz の紀元(epoch: 上書きは未対応=ADR-31)・anchor: で上書き
|
|
2276
|
+
const step = w.w.ms;
|
|
2277
|
+
const tzEpoch = this.tzObjOf(env).civilDayStart(1970, 1, 1);
|
|
2278
|
+
let t0 = tzEpoch;
|
|
2279
|
+
if (e.named.anchor) {
|
|
2280
|
+
const anchor = this.point(this.evalExpr(e.named.anchor, env));
|
|
2281
|
+
const k = Math.ceil((tzEpoch - anchor) / step);
|
|
2282
|
+
t0 = anchor + k * step; // 紀元以後で最初の位相点(それ以前はプロトタイプの評価範囲外)
|
|
2283
|
+
}
|
|
2284
|
+
const iv = [];
|
|
2285
|
+
for (let t = t0; t < this.rt.computeEnd; t += step)
|
|
2286
|
+
iv.push({ start: t, end: t + step });
|
|
2287
|
+
const grain = { kind: 'elapsed', step, phase: mod(t0, step), off: 0, tz: '' };
|
|
2288
|
+
return { k: 'windows', iv, units: iv.map(x => x.start), labelFn, grain };
|
|
2289
|
+
}
|
|
2290
|
+
case 'span': {
|
|
2291
|
+
const units = this.windowsOf(operand);
|
|
2292
|
+
const f = this.evalExpr(e.arg, env);
|
|
2293
|
+
const phase = e.named.phase ? this.num(this.evalExpr(e.named.phase, env)) : 0;
|
|
2294
|
+
if (phase < 0)
|
|
2295
|
+
this.err('span: phase は 0 以上(負位相は周期を法として正規化して書く。F65)');
|
|
2296
|
+
const iv = [];
|
|
2297
|
+
// phase > 0 のとき、紀元前に始まる窓の切れ端を先頭に張る。I5 では窓は全域を覆うが、
|
|
2298
|
+
// 有界実体化(紀元起点)は頭の単位 phase 個を覆えず、ordinalIn が「枠窓の外」を誤報していた
|
|
2299
|
+
if (phase > 0 && units.iv.length > 0) {
|
|
2300
|
+
const cut = Math.min(phase, units.iv.length);
|
|
2301
|
+
iv.push({ start: units.iv[0].start, end: units.iv[cut - 1].end });
|
|
2302
|
+
}
|
|
2303
|
+
let idx = phase, n = 0;
|
|
2304
|
+
while (idx < units.iv.length) {
|
|
2305
|
+
const k = this.num(this.applyValue(f, [n], env));
|
|
2306
|
+
if (k < 1)
|
|
2307
|
+
this.err('span: 個数は 1 以上');
|
|
2308
|
+
const last = Math.min(idx + k, units.iv.length) - 1;
|
|
2309
|
+
iv.push({ start: units.iv[idx].start, end: units.iv[last].end });
|
|
2310
|
+
idx += k;
|
|
2311
|
+
n++;
|
|
2312
|
+
}
|
|
2313
|
+
return { k: 'windows', iv, units: units.units, labelFn, grain: units.grain };
|
|
2314
|
+
}
|
|
2315
|
+
case 'split': {
|
|
2316
|
+
const parent = this.splitWindowsOf(operand, '親');
|
|
2317
|
+
const g = this.evalExpr(e.arg, env);
|
|
2318
|
+
const byName = e.named.by;
|
|
2319
|
+
if (!byName)
|
|
2320
|
+
this.err('split は by: で幅の単位を明示する(§3.6)');
|
|
2321
|
+
const u = this.splitWindowsOf(this.evalExpr(byName, env), 'by:');
|
|
2322
|
+
// 境界整合検査(ADR-48 新設): 親窓の両端が u の窓境界に一致すること(per-instance・
|
|
2323
|
+
// 実体化の端は免除)。ADR-36 の G 同一検査は本件を覆わない(暦年×isoWeek はどちらも
|
|
2324
|
+
// day グリッドで通ってしまう)——非整列(暦年×週など)はここで弾く
|
|
2325
|
+
const uBounds = new Set();
|
|
2326
|
+
for (const x of u.iv) {
|
|
2327
|
+
uBounds.add(x.start);
|
|
2328
|
+
uBounds.add(x.end);
|
|
2329
|
+
}
|
|
2330
|
+
const iv = [];
|
|
2331
|
+
parent.iv.forEach((p, y) => {
|
|
2332
|
+
if (p.start > this.rt.epoch && p.end < this.rt.computeEnd
|
|
2333
|
+
&& (!uBounds.has(p.start) || !uBounds.has(p.end))) {
|
|
2334
|
+
this.err(`split: 親窓の境界(${this.rt.fmt(p.start)}..${this.rt.fmt(p.end)})が by: 単位の`
|
|
2335
|
+
+ '窓境界に一致しない——非整列の親×単位(暦年×週など)は分割できない(ADR-48)');
|
|
2336
|
+
}
|
|
2337
|
+
const widths = this.applyValue(g, [y], env);
|
|
2338
|
+
if (!Array.isArray(widths))
|
|
2339
|
+
this.err('split の g は幅リストを返す');
|
|
2340
|
+
// 親窓内の単位窓を幅リストで割る。幅総和=親は I5 で検査
|
|
2341
|
+
let ui = u.iv.findIndex(x => x.start >= p.start);
|
|
2342
|
+
const inParent = u.iv.filter(x => x.start >= p.start && x.end <= p.end).length;
|
|
2343
|
+
const endIdx = ui + inParent - 1; // 親窓内の最後の単位
|
|
2344
|
+
const total = widths.reduce((s, w) => s + this.num(w), 0);
|
|
2345
|
+
// 実体化の両端に接する親窓は切れ端でありうる(span の頭の切れ端・末尾の打ち切り)ため検査しない。
|
|
2346
|
+
// 強度はエラー(ADR-48 裁定=「黙って 53 週目を落とさない」を警告でなくエラーで保証)
|
|
2347
|
+
if (total !== inParent && p.start > this.rt.epoch && p.end < this.rt.computeEnd) {
|
|
2348
|
+
this.err(`I5: split の幅総和 ${total} ≠ 親窓内の単位数 ${inParent}`
|
|
2349
|
+
+ `(親窓 ${this.rt.fmt(p.start)}..${this.rt.fmt(p.end)}。可変長の親は g が親序数で`
|
|
2350
|
+
+ '分岐するか segmentBy 正準形へ。ADR-48)');
|
|
2351
|
+
}
|
|
2352
|
+
for (const w of widths) {
|
|
2353
|
+
const k = this.num(w);
|
|
2354
|
+
if (ui + k - 1 > endIdx)
|
|
2355
|
+
break; // 幅ぶんの単位が親窓内にない(実体化の端)
|
|
2356
|
+
iv.push({ start: u.iv[ui].start, end: u.iv[ui + k - 1].end });
|
|
2357
|
+
ui += k;
|
|
2358
|
+
}
|
|
2359
|
+
// 実体化の端(切れ端の親窓)では残りの単位にも切れ端窓を張る(I5: 全域を覆う)
|
|
2360
|
+
if (ui <= endIdx)
|
|
2361
|
+
iv.push({ start: u.iv[ui].start, end: u.iv[endIdx].end });
|
|
2362
|
+
});
|
|
2363
|
+
return { k: 'windows', iv, units: parent.units, labelFn, grain: parent.grain };
|
|
2364
|
+
}
|
|
2365
|
+
case 'cycle': {
|
|
2366
|
+
const target = this.windowsOf(operand);
|
|
2367
|
+
const labelsV = this.evalExpr(e.arg, env);
|
|
2368
|
+
if (!Array.isArray(labelsV))
|
|
2369
|
+
this.err('cycle はラベルのリストを取る');
|
|
2370
|
+
const labels = labelsV.map(l => typeof l === 'string' ? l : this.err('cycle のラベルは名前'));
|
|
2371
|
+
const anchorE = e.named.anchor ?? this.err('cycle は anchor: が必要(§3.6)');
|
|
2372
|
+
const anchor = ivIndexOf(target.iv, this.point(this.evalExpr(anchorE, env)));
|
|
2373
|
+
if (anchor < 0)
|
|
2374
|
+
this.err('cycle: anchor が対象窓の外');
|
|
2375
|
+
return { k: 'cycle', iv: target.iv, labels, anchor, grain: target.grain };
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
/** segmentBy の labels: リストの評価(ADR-39): リテラルまたはリスト束縛名。
|
|
2380
|
+
* ラベル値の型域は ADR-34 判断 3 と同一(スカラー値・等質・空リスト不可) */
|
|
2381
|
+
windowLabelList(e, env) {
|
|
2382
|
+
const v = this.evalExpr(e, env);
|
|
2383
|
+
if (!Array.isArray(v)) {
|
|
2384
|
+
this.err(`segmentBy(labels:): リスト(リテラルまたはリスト束縛名)を取る: ${kindOf(v)}(ADR-39)`);
|
|
2385
|
+
}
|
|
2386
|
+
if (v.length === 0)
|
|
2387
|
+
this.err('segmentBy(labels:): 空リストは不可(ADR-39)');
|
|
2388
|
+
const out = [];
|
|
2389
|
+
for (const x of v) {
|
|
2390
|
+
if (typeof x !== 'number' && typeof x !== 'string' && typeof x !== 'boolean') {
|
|
2391
|
+
this.err(`segmentBy(labels:): ラベル値は値型(数値・論理・列挙名・文字列。ADR-34 判断 3): ${kindOf(x)}`);
|
|
2392
|
+
}
|
|
2393
|
+
out.push(x);
|
|
2394
|
+
}
|
|
2395
|
+
if (!out.every(x => typeof x === typeof out[0])) {
|
|
2396
|
+
this.err('segmentBy(labels:): ラベル列は等質(同一の値型。ADR-34 判断 3)');
|
|
2397
|
+
}
|
|
2398
|
+
return out;
|
|
2399
|
+
}
|
|
2400
|
+
/** gen-expr の label: 付与式(§4.9・ADR-34)。ラムダ値を評価時の環境ごと閉包として保持し、
|
|
2401
|
+
* 評価は射影時まで遅延する(I7。premise 相対の遅延解決=ADR-17 はラムダの env が担う) */
|
|
2402
|
+
genLabelFn(e, env) {
|
|
2403
|
+
const labelE = e.named.label;
|
|
2404
|
+
if (labelE === undefined)
|
|
2405
|
+
return undefined;
|
|
2406
|
+
if (e.word === 'cycle')
|
|
2407
|
+
this.err('cycle は label: を取らない(ラベル列は cycle 自身が持つ。ADR-34)');
|
|
2408
|
+
return this.labelLambda(labelE, env);
|
|
2409
|
+
}
|
|
2410
|
+
labelLambda(labelE, env) {
|
|
2411
|
+
const v = this.evalExpr(labelE, env);
|
|
2412
|
+
if (!isObj(v) || v.k !== 'lambda')
|
|
2413
|
+
this.err(`label: はラムダ(付与式)を取る: ${kindOf(v)}(§4.9)`);
|
|
2414
|
+
return v;
|
|
2415
|
+
}
|
|
2416
|
+
// ---------- 本体層: core 段(§4) ----------
|
|
2417
|
+
/** core 段ごとの既知 named-arg(ADR-39: 未知の名前付き引数を黙って捨てない——タイポの検査面) */
|
|
2418
|
+
static STAGE_NAMED = {
|
|
2419
|
+
within: new Set([]),
|
|
2420
|
+
segmentBy: new Set(['edges', 'empties', 'label', 'labels']),
|
|
2421
|
+
first: new Set(['of']), nth: new Set(['of']), last: new Set(['of']),
|
|
2422
|
+
filter: new Set(['on']),
|
|
2423
|
+
roll: new Set(['on']),
|
|
2424
|
+
shift: new Set(['unit']),
|
|
2425
|
+
snapTo: new Set([]),
|
|
2426
|
+
rebase: new Set(['to']),
|
|
2427
|
+
stride: new Set(['from']),
|
|
2428
|
+
strideBy: new Set(['from']),
|
|
2429
|
+
take: new Set(['from']),
|
|
2430
|
+
takeLast: new Set(['until']),
|
|
2431
|
+
};
|
|
2432
|
+
applyStage(stream, stage, env) {
|
|
2433
|
+
const named = (key) => stage.args.find(a => a.name === key)?.value;
|
|
2434
|
+
const positional = stage.args.filter(a => !a.name).map(a => a.value);
|
|
2435
|
+
if (CORE_STAGES.has(stage.name)) {
|
|
2436
|
+
const known = Evaluator.STAGE_NAMED[stage.name];
|
|
2437
|
+
for (const a of stage.args) {
|
|
2438
|
+
if (a.name && !known.has(a.name)) {
|
|
2439
|
+
// 錨の取り違えは専用診断(ADR-52——一族の from:/until: 双対の教示)
|
|
2440
|
+
if (stage.name === 'takeLast' && a.name === 'from') {
|
|
2441
|
+
this.err('takeLast の錨は until:(末尾は終端から数える)——先頭からは take(n, from:)(ADR-52)');
|
|
2442
|
+
}
|
|
2443
|
+
if (stage.name === 'take' && a.name === 'until') {
|
|
2444
|
+
this.err('take の錨は from:(先頭は起点から数える)——末尾からは takeLast(n, until:)(ADR-52)');
|
|
2445
|
+
}
|
|
2446
|
+
this.err(`${stage.name}: 未知の名前付き引数 ${a.name}:(黙って捨てない——ADR-39)`);
|
|
2447
|
+
}
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
switch (stage.name) {
|
|
2451
|
+
case 'within': {
|
|
2452
|
+
const wname = positional[0]?.t === 'name' ? positional[0].name : undefined;
|
|
2453
|
+
const w = this.evalExpr(positional[0] ?? this.err('within(w) の窓名が必要'), env);
|
|
2454
|
+
const iv = this.intervalsOf(w, env);
|
|
2455
|
+
this.checkPartition(iv, wname ?? 'within');
|
|
2456
|
+
// ラベルは随伴させない——射影はラベル付き束縛名に限る(ADR-30 の範囲を広げない。ADR-34 帰結)
|
|
2457
|
+
const wgrain = isObj(w) && w.k === 'windows' ? w.grain : this.winGrainOf(w);
|
|
2458
|
+
this.checkTzMembership(stream.align, wgrain, 'within'); // 免除系の tz 名検査(ADR-40)
|
|
2459
|
+
return { k: 'stream', pts: stream.pts,
|
|
2460
|
+
wins: [...stream.wins, { name: wname, iv, grain: wgrain, ann: this.winAnnOfV(w) }],
|
|
2461
|
+
align: stream.align, ann: stream.ann, endless: stream.endless };
|
|
2462
|
+
}
|
|
2463
|
+
case 'segmentBy': {
|
|
2464
|
+
const labelE = named('label');
|
|
2465
|
+
const labelFn = labelE ? this.labelLambda(labelE, env) : undefined; // label: 付与式(§4.9・ADR-34)
|
|
2466
|
+
const edges = named('edges') ?? this.err('segmentBy は edges: が必須(I5・§4.2)');
|
|
2467
|
+
const empties = named('empties') ?? this.err('segmentBy は empties: が必須(I5・§4.2)');
|
|
2468
|
+
const edgesV = this.sym(this.evalExpr(edges, env));
|
|
2469
|
+
const emptiesV = this.sym(this.evalExpr(empties, env));
|
|
2470
|
+
const markerS = this.toStream(this.evalExpr(positional[0] ?? this.err('segmentBy にマーカーが必要'), env));
|
|
2471
|
+
const markers = markerS.pts;
|
|
2472
|
+
if (markers.length < 1)
|
|
2473
|
+
this.err('segmentBy: マーカーが空');
|
|
2474
|
+
// マーカーの実効被覆域(ADR-37 判断 4): 覆域は「範囲内に他のマーカーは無い」を保証する——
|
|
2475
|
+
// edges:/empties: の発火判定は覆域の端であって列の端ではない
|
|
2476
|
+
const cov = complementIv(markerS.ann.map(a => ({ start: a.from, end: a.to })));
|
|
2477
|
+
const covOf = (ms) => cov.find(x => x.start <= ms && ms < x.end);
|
|
2478
|
+
// 窓列への並行ラベル列(ADR-39): 前提条件と締め・覆域基準の同長性検査(F62 の器)。
|
|
2479
|
+
// 検査は窓束縛の評価と同時=射影の有無に依らず走る(長さ検査はラベル射影を遅延させない)
|
|
2480
|
+
const labelsE = named('labels');
|
|
2481
|
+
let labels;
|
|
2482
|
+
let labelsCycle;
|
|
2483
|
+
if (labelsE) {
|
|
2484
|
+
const form = labelsE.t === 'cycleLabels' ? 'labels: cycle' : 'labels:';
|
|
2485
|
+
if (labelFn) {
|
|
2486
|
+
this.err('segmentBy: labels: と label: は同居できない——ラベル源の二重化'
|
|
2487
|
+
+ '(束縛名射影の一意性。ADR-39 判断 4)');
|
|
2488
|
+
}
|
|
2489
|
+
if (edgesV === 'clip') {
|
|
2490
|
+
this.err(`segmentBy(${form}): edges: clip とは組めない——擬似窓(マーカー起点でない窓)が`
|
|
2491
|
+
+ '窓列序数とリスト添字の対応を黙ってずらす(edges: drop へ。ADR-39 判断 4)');
|
|
2492
|
+
}
|
|
2493
|
+
if (emptiesV === 'drop') {
|
|
2494
|
+
this.err(`segmentBy(${form}): empties: drop とは組めない——空窓の除去が序数を詰め、`
|
|
2495
|
+
+ 'どのラベルが落ちたかリストから判別できない(empties: keep/error へ。ADR-39 判断 4)');
|
|
2496
|
+
}
|
|
2497
|
+
if (markerS.endless) {
|
|
2498
|
+
this.err(`segmentBy(${form}): 規則マーカー(無限の点列)の窓列はデータラベルの対象外——`
|
|
2499
|
+
+ '周期ラベルは窓束縛 cycle・計算番号は ordinalIn か label: ラムダで(ADR-39 判断 4・ADR-47)');
|
|
2500
|
+
}
|
|
2501
|
+
// マーカー流の実効被覆域は全マーカー点を包む単一の無註釈区間であること(合成マーカーの締め。
|
|
2502
|
+
// 静的形は窓数確定・cycle 形は位相確定の根拠——註釈域内の未知マーカーは位相を黙ってずらす)
|
|
2503
|
+
const home = cov.find(x => x.start <= markers[0] && markers[markers.length - 1] < x.end);
|
|
2504
|
+
if (!home) {
|
|
2505
|
+
this.err(`segmentBy(${form}): マーカーの実効被覆域が全マーカーを包む単一の無註釈区間でない`
|
|
2506
|
+
+ '——窓数・位相が確定しない(合成マーカーは束縛後置の covering: で覆域を確定してから。'
|
|
2507
|
+
+ 'ADR-39 判断 4 / ADR-37 判断 5)');
|
|
2508
|
+
}
|
|
2509
|
+
if (labelsE.t === 'cycleLabels') {
|
|
2510
|
+
// 周期ラベル(ADR-47): 同長性検査は課さない(周期は任意の窓数を覆う)。
|
|
2511
|
+
// i0 はマーカー列基準で計算=実体化・評価範囲に依存しない(ADR-39 判断 2 の原則)
|
|
2512
|
+
const list = this.windowLabelList(labelsE.list, env);
|
|
2513
|
+
const anchorMs = this.point(this.evalExpr(labelsE.anchor, env));
|
|
2514
|
+
if (anchorMs < markers[0] || anchorMs >= home.end) {
|
|
2515
|
+
this.err('segmentBy(labels: cycle): anchor が窓列のいずれの窓にも属さない(頭側・範囲外'
|
|
2516
|
+
+ '——位相が定まらない)。覆域の更新で anchor の窓が消えた場合は、anchor をリスト周期'
|
|
2517
|
+
+ '(N 窓)分先の対応する実日へ進めれば全ラベル不変(ADR-47)');
|
|
2518
|
+
}
|
|
2519
|
+
let i0 = markers.length - 1;
|
|
2520
|
+
for (let j = 0; j < markers.length - 1; j++) {
|
|
2521
|
+
if (anchorMs < markers[j + 1]) {
|
|
2522
|
+
i0 = j;
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
labelsCycle = { list, i0 };
|
|
2527
|
+
}
|
|
2528
|
+
else {
|
|
2529
|
+
labels = this.windowLabelList(labelsE, env);
|
|
2530
|
+
// 同長性検査(覆域基準): 期待窓数=マーカー数(ADR-37 の覆域端確定の最終窓を含む。
|
|
2531
|
+
// 評価範囲・実体化範囲に依存しない正確な数え方)
|
|
2532
|
+
if (labels.length !== markers.length) {
|
|
2533
|
+
this.err(`segmentBy(labels:): ラベル列の長さ ${labels.length} ≠ 窓数 ${markers.length}`
|
|
2534
|
+
+ '(窓数は覆域基準=マーカー数。マーカーとラベルは対で更新する——ADR-39/F62)');
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
const iv = [];
|
|
2539
|
+
for (let i = 0; i < markers.length - 1; i++)
|
|
2540
|
+
iv.push({ start: markers[i], end: markers[i + 1] });
|
|
2541
|
+
// 覆域内では最終マーカー起点の窓も覆域端まで確定して張る(=窓列の実効被覆域)
|
|
2542
|
+
const mLast = markers[markers.length - 1];
|
|
2543
|
+
const tailCov = covOf(mLast);
|
|
2544
|
+
const fEnd = Math.min(tailCov ? tailCov.end : mLast, this.rt.computeEnd);
|
|
2545
|
+
if (fEnd > mLast)
|
|
2546
|
+
iv.push({ start: mLast, end: fEnd });
|
|
2547
|
+
const defEnd = iv.length ? iv[iv.length - 1].end : mLast; // 確定窓の末端
|
|
2548
|
+
// 頭側(覆域始端〜最初のマーカー)だけが edges: の領分。覆域外の点は範囲外=註釈が引き受ける
|
|
2549
|
+
if (edgesV === 'error') {
|
|
2550
|
+
const headCov = covOf(markers[0]);
|
|
2551
|
+
const headStart = Math.max(headCov ? headCov.start : markers[0], this.rt.epoch);
|
|
2552
|
+
const outside = stream.pts.some(p => p >= headStart && p < markers[0]);
|
|
2553
|
+
if (outside)
|
|
2554
|
+
this.err('segmentBy(edges: error): 窓の外に点がある');
|
|
2555
|
+
}
|
|
2556
|
+
let windows = iv;
|
|
2557
|
+
if (edgesV === 'clip') {
|
|
2558
|
+
windows = [{ start: this.rt.epoch, end: markers[0] }, ...iv];
|
|
2559
|
+
if (defEnd < this.rt.computeEnd)
|
|
2560
|
+
windows = [...windows, { start: defEnd, end: this.rt.computeEnd }];
|
|
2561
|
+
}
|
|
2562
|
+
if (emptiesV !== 'keep') {
|
|
2563
|
+
// スイープで各窓の要素数を数える(点・窓ともソート済み)
|
|
2564
|
+
const nonEmpty = new Set();
|
|
2565
|
+
let wi = 0;
|
|
2566
|
+
for (const p of stream.pts) {
|
|
2567
|
+
while (wi < windows.length && p >= windows[wi].end)
|
|
2568
|
+
wi++;
|
|
2569
|
+
if (wi >= windows.length)
|
|
2570
|
+
break;
|
|
2571
|
+
if (p >= windows[wi].start)
|
|
2572
|
+
nonEmpty.add(wi);
|
|
2573
|
+
}
|
|
2574
|
+
const empties_ = windows.map((_, i) => i).filter(i => !nonEmpty.has(i));
|
|
2575
|
+
if (emptiesV === 'error' && empties_.length > 0)
|
|
2576
|
+
this.err('segmentBy(empties: error): 空窓がある');
|
|
2577
|
+
if (emptiesV === 'drop')
|
|
2578
|
+
windows = windows.filter((_, i) => nonEmpty.has(i));
|
|
2579
|
+
}
|
|
2580
|
+
const pts = edgesV === 'drop'
|
|
2581
|
+
? stream.pts.filter(p => p >= markers[0] && p < defEnd)
|
|
2582
|
+
: stream.pts;
|
|
2583
|
+
// 窓の切れ目=マーカー点なので、この窓列の grain はマーカーの整列(snapTo の主張に使う)。
|
|
2584
|
+
// 窓列の註釈=マーカー覆域の補集合(判断 4)——分類器(判断 6)の基準になる実効被覆域。
|
|
2585
|
+
// 精密化(ADR-37 改訂 3・F105): マーカー覆域内でも窓の張られていない区間(edges: drop/error の
|
|
2586
|
+
// 頭側・empties: drop の中抜け)は窓列としては語れない——窓列由来の註釈にする(射影・coincides の
|
|
2587
|
+
// 分類器が「落として註釈」を選べる。末尾の覆域端確定=F72 の頭側対称)。覆域註釈のない
|
|
2588
|
+
// 規則マーカーは従来どおり対象外
|
|
2589
|
+
let winAnn = markerS.ann;
|
|
2590
|
+
if (markerS.ann.length > 0 && windows.length > 0) {
|
|
2591
|
+
const like = markerS.ann.find(a => a.to <= windows[0].start) ?? markerS.ann[0];
|
|
2592
|
+
const lo = Math.max(covOf(markers[0])?.start ?? markers[0], this.rt.epoch);
|
|
2593
|
+
const gaps = [];
|
|
2594
|
+
let prev = lo;
|
|
2595
|
+
for (const w of windows) {
|
|
2596
|
+
if (prev < w.start)
|
|
2597
|
+
gaps.push({ ...like, from: prev, to: w.start });
|
|
2598
|
+
prev = Math.max(prev, w.end);
|
|
2599
|
+
}
|
|
2600
|
+
if (gaps.length > 0)
|
|
2601
|
+
winAnn = annUnion(markerS.ann, gaps);
|
|
2602
|
+
}
|
|
2603
|
+
return { k: 'stream', pts,
|
|
2604
|
+
wins: [...stream.wins, { iv: windows, labelFn, grain: markerS.align, ann: winAnn, labels, labelsCycle }],
|
|
2605
|
+
align: stream.align, ann: annUnion(stream.ann, markerS.ann), endless: stream.endless };
|
|
2606
|
+
}
|
|
2607
|
+
case 'first':
|
|
2608
|
+
case 'last':
|
|
2609
|
+
case 'nth': {
|
|
2610
|
+
if (stream.wins.length === 0)
|
|
2611
|
+
this.err(`選択子 ${stage.name} は窓なしでは型エラー(I4)`);
|
|
2612
|
+
let n = 1;
|
|
2613
|
+
if (stage.name === 'nth')
|
|
2614
|
+
n = this.num(this.evalExpr(positional[0] ?? this.err('nth(n) の n が必要'), env));
|
|
2615
|
+
const ofE = named('of');
|
|
2616
|
+
let level = stream.wins.length - 1; // 既定は最内窓(§4.3)
|
|
2617
|
+
if (ofE) {
|
|
2618
|
+
const ofName = ofE.t === 'name' ? ofE.name : this.err('of: は窓名');
|
|
2619
|
+
level = stream.wins.findIndex(w => w.name === ofName);
|
|
2620
|
+
if (level < 0)
|
|
2621
|
+
this.err(`of: ${ofName} に該当する窓がない`);
|
|
2622
|
+
}
|
|
2623
|
+
const iv = stream.wins[level].iv;
|
|
2624
|
+
this.checkTzMembership(stream.align, stream.wins[level].grain, `選択子 ${stage.name}`); // ADR-40
|
|
2625
|
+
// 判断 4(選択子): 対象窓が註釈区間に交差したら窓全域へ拡幅(窓内の序数は覆域外を覗き得る)
|
|
2626
|
+
const combined = annUnion(stream.ann, stream.wins[level].ann);
|
|
2627
|
+
const widened = [];
|
|
2628
|
+
const out = [];
|
|
2629
|
+
let pi = 0;
|
|
2630
|
+
for (const w of iv) {
|
|
2631
|
+
for (const a of combined) {
|
|
2632
|
+
if (a.from < w.end && w.start < a.to)
|
|
2633
|
+
widened.push({ ...a, from: w.start, to: w.end });
|
|
2634
|
+
}
|
|
2635
|
+
const inWin = [];
|
|
2636
|
+
while (pi < stream.pts.length && stream.pts[pi] < w.start)
|
|
2637
|
+
pi++;
|
|
2638
|
+
let pj = pi;
|
|
2639
|
+
while (pj < stream.pts.length && stream.pts[pj] < w.end)
|
|
2640
|
+
inWin.push(stream.pts[pj++]);
|
|
2641
|
+
pi = pj;
|
|
2642
|
+
if (inWin.length === 0)
|
|
2643
|
+
continue; // 空は正当な値(I15/ADR-15)
|
|
2644
|
+
if (stage.name === 'first')
|
|
2645
|
+
out.push(inWin[0]);
|
|
2646
|
+
else if (stage.name === 'last')
|
|
2647
|
+
out.push(inWin[inWin.length - 1]);
|
|
2648
|
+
else if (n >= 1 && n <= inWin.length)
|
|
2649
|
+
out.push(inWin[n - 1]);
|
|
2650
|
+
}
|
|
2651
|
+
return { k: 'stream', pts: out, wins: stream.wins.slice(0, level), align: stream.align,
|
|
2652
|
+
ann: annUnion(combined, widened), endless: stream.endless }; // 対象窓と内側を消費
|
|
2653
|
+
}
|
|
2654
|
+
case 'filter': {
|
|
2655
|
+
const onE = named('on');
|
|
2656
|
+
if (onE || !positional[0]) {
|
|
2657
|
+
// on: 明示、または前文 axis: の畳み込み(どちらも軸=等値所属。ADR-35 の読み替えも通す)
|
|
2658
|
+
const axisS = this.toStream(this.evalAxis(onE ?? this.axisMember(env, 'filter'), env));
|
|
2659
|
+
this.checkAlign(stream.align, axisS.align, 'filter(on:)');
|
|
2660
|
+
const set = new Set(axisS.pts);
|
|
2661
|
+
return { ...stream, pts: stream.pts.filter(p => set.has(p)),
|
|
2662
|
+
ann: annUnion(stream.ann, axisS.ann), // 等値所属は軸の覆域に依存(判断 4)
|
|
2663
|
+
endless: stream.endless && axisS.endless }; // 積と同型(軸が有限なら出力も有限)
|
|
2664
|
+
}
|
|
2665
|
+
const pred = this.evalExpr(positional[0], env);
|
|
2666
|
+
// 判断 4/6: 述語が範囲外参照を要求した点は落とし、依存の註釈区間を出力に註釈(共通経路)
|
|
2667
|
+
return this.filterByPredicate(stream, p => this.bool(this.applyValue(pred, [{ k: 'point', ms: p }], env)));
|
|
2668
|
+
}
|
|
2669
|
+
case 'roll': {
|
|
2670
|
+
const conv = this.sym(this.evalExpr(positional[0] ?? this.err('roll は規約が必要(I3)'), env));
|
|
2671
|
+
const axisS = this.toStream(this.evalAxis(named('on') ?? this.axisMember(env, 'roll'), env));
|
|
2672
|
+
this.checkAlign(stream.align, axisS.align, 'roll(on:)');
|
|
2673
|
+
const axis = axisS.pts;
|
|
2674
|
+
if (conv === 'Modified')
|
|
2675
|
+
this.err('Modified は未実装(プロトタイプ)');
|
|
2676
|
+
if (conv !== 'Following' && conv !== 'Preceding')
|
|
2677
|
+
this.err(`未知のロール規約: ${conv}`);
|
|
2678
|
+
const pts = [];
|
|
2679
|
+
for (const p of stream.pts) {
|
|
2680
|
+
if (ptIndexOf(axis, p) >= 0) {
|
|
2681
|
+
pts.push(p);
|
|
2682
|
+
continue;
|
|
2683
|
+
} // 有効点は動かない
|
|
2684
|
+
if (conv === 'Following') {
|
|
2685
|
+
const i = ptUpperBound(axis, p);
|
|
2686
|
+
if (i < axis.length) {
|
|
2687
|
+
pts.push(axis[i]);
|
|
2688
|
+
continue;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
else {
|
|
2692
|
+
const i = ptUpperBound(axis, p) - 1;
|
|
2693
|
+
if (i >= 0) {
|
|
2694
|
+
pts.push(axis[i]);
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
// 着地先が無い=軸の尽き。三分岐(ADR-37 判断 6/8):
|
|
2699
|
+
if (annAt(axisS.ann, p).length > 0)
|
|
2700
|
+
continue; // ②実効被覆域外→空+註釈(依存像が張る)
|
|
2701
|
+
if (axis.length > 0
|
|
2702
|
+
&& (conv === 'Following' ? axis[axis.length - 1] >= this.rt.toMs : axis[0] <= this.rt.fromMs)) {
|
|
2703
|
+
// ①実体化地平線(軸は評価需要域の外まで実体化されている=データ端ではない)
|
|
2704
|
+
this.rt.warnings.push(`horizon-clip: roll(${conv}) ${this.rt.fmt(p)}`
|
|
2705
|
+
+ '(計算範囲 to+400日 の実体化地平線——言語の地平線ではない。ADR-37 判断 8)');
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
continue; // ③完結データ(開端 covering)の正当な尽き→註釈なしの空(ADR-15)
|
|
2709
|
+
}
|
|
2710
|
+
// 出力註釈=入力註釈の像 ∪ 軸の註釈区間の依存像(判断 4 の roll 行)
|
|
2711
|
+
const ann = annUnion(rollImageAnn(stream.ann, axis, conv), dependImageAnn(axisS.ann, axis, conv));
|
|
2712
|
+
return { ...stream, pts: [...new Set(pts)].sort((a, b) => a - b), ann };
|
|
2713
|
+
}
|
|
2714
|
+
case 'shift': {
|
|
2715
|
+
const n = this.num(this.evalExpr(positional[0] ?? this.err('shift(n) の n が必要'), env));
|
|
2716
|
+
const unitV = this.evalAxis(named('unit') ?? this.axisMember(env, 'shift'), env);
|
|
2717
|
+
const pts = [];
|
|
2718
|
+
let align;
|
|
2719
|
+
let ann;
|
|
2720
|
+
if (isObj(unitV) && unitV.k === 'windows') {
|
|
2721
|
+
// 窓単位: 属する窓の添字を n 動かし、窓内オフセットを保存(区間所属=整列検査なし。ADR-36)。
|
|
2722
|
+
// 出力整列は「入力整列=単位窓の要素グリッド」なら保存、さもなくば なし
|
|
2723
|
+
align = gridEq(stream.align, unitV.grain) ? stream.align : null;
|
|
2724
|
+
for (const p of stream.pts) {
|
|
2725
|
+
const i = ivIndexOf(unitV.iv, p);
|
|
2726
|
+
if (i < 0) {
|
|
2727
|
+
if (p >= this.rt.computeEnd || p < this.rt.epoch) { // ①実体化地平線(判断 8)
|
|
2728
|
+
this.rt.warnings.push(`horizon-clip: shift ${this.rt.fmt(p)}`
|
|
2729
|
+
+ '(計算範囲 to+400日 の実体化地平線——言語の地平線ではない。ADR-37 判断 8)');
|
|
2730
|
+
continue;
|
|
2731
|
+
}
|
|
2732
|
+
if (p < this.windowFloorOf(unitV)) { // ①' 単位窓列の実体化下限(紀元は tz 相対=ADR-33。F107)
|
|
2733
|
+
this.rt.warnings.push(`horizon-clip: shift ${this.rt.fmt(p)}`
|
|
2734
|
+
+ '(単位窓列の実体化下限より前——紀元は tz 相対・言語の地平線ではない。ADR-37 判断 8・F107)');
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
this.err('shift: 点が単位窓の外');
|
|
2738
|
+
}
|
|
2739
|
+
const j = i + n;
|
|
2740
|
+
if (j < 0 || j >= unitV.iv.length) { // ①窓語 unit は暦法純粋=地平線
|
|
2741
|
+
this.rt.warnings.push(`horizon-clip: shift ${this.rt.fmt(p)}`
|
|
2742
|
+
+ '(計算範囲 to+400日 の実体化地平線——言語の地平線ではない。ADR-37 判断 8)');
|
|
2743
|
+
continue;
|
|
2744
|
+
}
|
|
2745
|
+
pts.push(unitV.iv[j].start + (p - unitV.iv[i].start));
|
|
2746
|
+
}
|
|
2747
|
+
// 判断 4: 入力註釈 ∪ n·U 平行移動した像(窓添字ずらしの像)
|
|
2748
|
+
ann = annUnion(stream.ann, shiftAnnByWindows(stream.ann, unitV.iv, n));
|
|
2749
|
+
}
|
|
2750
|
+
else {
|
|
2751
|
+
// 点列軸(bizDay 等): 軸上の位置を n 動かす。等値所属なので整列検査(ADR-36)
|
|
2752
|
+
const axisS = this.toStream(unitV);
|
|
2753
|
+
this.checkAlign(stream.align, axisS.align, 'shift(unit: 点列軸)');
|
|
2754
|
+
align = axisS.align;
|
|
2755
|
+
const axis = axisS.pts;
|
|
2756
|
+
let extra = [];
|
|
2757
|
+
for (const p of stream.pts) {
|
|
2758
|
+
const i = ptIndexOf(axis, p);
|
|
2759
|
+
if (i < 0) {
|
|
2760
|
+
const hit = annAt(axisS.ann, p);
|
|
2761
|
+
if (hit.length > 0) {
|
|
2762
|
+
extra = annUnion(extra, hit);
|
|
2763
|
+
continue;
|
|
2764
|
+
} // ②範囲外→落として註釈
|
|
2765
|
+
this.err('shift: 点が軸上にない(先に roll で有効点へ寄せる)');
|
|
2766
|
+
}
|
|
2767
|
+
const j = i + n;
|
|
2768
|
+
if (j >= 0 && j < axis.length) {
|
|
2769
|
+
pts.push(axis[j]);
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
// 着地が軸の外=軸の尽き。三分岐(判断 6/8)
|
|
2773
|
+
const beyond = j >= axis.length;
|
|
2774
|
+
const edgeProbe = beyond ? axis[axis.length - 1] + 1 : axis[0] - 1;
|
|
2775
|
+
const hit = annAt(axisS.ann, edgeProbe);
|
|
2776
|
+
if (hit.length > 0) {
|
|
2777
|
+
extra = annUnion(extra, hit);
|
|
2778
|
+
continue;
|
|
2779
|
+
} // ②
|
|
2780
|
+
if (beyond ? axis[axis.length - 1] >= this.rt.toMs : axis[0] <= this.rt.fromMs) {
|
|
2781
|
+
this.rt.warnings.push(`horizon-clip: shift ${this.rt.fmt(p)}`
|
|
2782
|
+
+ '(計算範囲 to+400日 の実体化地平線——言語の地平線ではない。ADR-37 判断 8)');
|
|
2783
|
+
continue; // ①
|
|
2784
|
+
}
|
|
2785
|
+
continue; // ③完結の尽き
|
|
2786
|
+
}
|
|
2787
|
+
// 判断 4: 入力註釈 ∪ 軸上 n 歩の平行移動像(満了 90 日前通知の漏れを塞ぐ側)
|
|
2788
|
+
ann = annUnion(stream.ann, shiftAnnByAxis(stream.ann, axis, n), extra);
|
|
2789
|
+
}
|
|
2790
|
+
return { ...stream, pts: pts.sort((a, b) => a - b), align, ann };
|
|
2791
|
+
}
|
|
2792
|
+
case 'snapTo': {
|
|
2793
|
+
const wV = this.evalExpr(positional[0] ?? this.err('snapTo(w) の窓が必要'), env);
|
|
2794
|
+
const w = this.intervalsOf(wV, env);
|
|
2795
|
+
const wAnn = this.winAnnOfV(wV);
|
|
2796
|
+
const pts = [];
|
|
2797
|
+
let extra = [];
|
|
2798
|
+
for (const p of stream.pts) {
|
|
2799
|
+
const i = ivIndexOf(w, p);
|
|
2800
|
+
if (i >= 0) {
|
|
2801
|
+
pts.push(w[i].start);
|
|
2802
|
+
continue;
|
|
2803
|
+
}
|
|
2804
|
+
// 点が窓の外。三分岐(ADR-37 判断 6/8):
|
|
2805
|
+
if (p >= this.rt.computeEnd || p < this.rt.epoch) { // ①実体化地平線
|
|
2806
|
+
this.rt.warnings.push(`horizon-clip: snapTo ${this.rt.fmt(p)}`
|
|
2807
|
+
+ '(計算範囲 to+400日 の実体化地平線——言語の地平線ではない。ADR-37 判断 8)');
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
const hit = annAt(wAnn, p);
|
|
2811
|
+
if (hit.length > 0) {
|
|
2812
|
+
extra = annUnion(extra, hit);
|
|
2813
|
+
continue;
|
|
2814
|
+
} // ②範囲外→落として註釈
|
|
2815
|
+
if (p < this.windowFloorOf(wV)) { // ①' 窓列の実体化下限(紀元は tz 相対=ADR-33。F107)
|
|
2816
|
+
this.rt.warnings.push(`horizon-clip: snapTo ${this.rt.fmt(p)}`
|
|
2817
|
+
+ '(対象窓列の実体化下限より前——紀元は tz 相対・言語の地平線ではない。ADR-37 判断 8・F107)');
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
this.err('snapTo: 点が窓の外'); // ③覆域内→硬エラー維持
|
|
2821
|
+
}
|
|
2822
|
+
// ADR-36 判断 5: 出力は w の要素グリッドを主張(=再整列の明示手段)。
|
|
2823
|
+
// 註釈の像は端点を窓先頭へ床処理(覆域の端を窓粒度で解決)
|
|
2824
|
+
return { ...stream, pts: [...new Set(pts)].sort((a, b) => a - b), align: this.winGrainOf(wV),
|
|
2825
|
+
ann: annUnion(snapAnn(stream.ann, w), extra) };
|
|
2826
|
+
}
|
|
2827
|
+
case 'rebase': {
|
|
2828
|
+
// 日付ラベル保存の再錨(仮称 rebase・ADR-40): 各点(source tz の市民日の先頭点)の日付ラベルを
|
|
2829
|
+
// to tz の同日付の市民日の最初の瞬間へ写す(ADR-33 判断 4)。単射・順序保存
|
|
2830
|
+
const toE = named('to') ?? this.err('rebase は to: が必須(対象座標系の tz 名。ADR-40)');
|
|
2831
|
+
const toV = this.evalExpr(toE, env);
|
|
2832
|
+
if (typeof toV !== 'string') {
|
|
2833
|
+
this.err(`rebase: to: は tz 名の文字列リテラルを取る: ${kindOf(toV)}(premise 名は不可——ADR-40 判断 5)`);
|
|
2834
|
+
}
|
|
2835
|
+
// 入力は既定整列の day グリッド(G=幅 1d・日内オフセット 0・tz 名つき)——source tz は整列から
|
|
2836
|
+
const g = stream.align;
|
|
2837
|
+
if (isVacuous(g))
|
|
2838
|
+
return stream; // 空テーブル=点ゼロの再錨は恒等(空虚適合。ADR-45)
|
|
2839
|
+
if (!g || g.kind !== 'civil' || g.step !== 1 || g.off !== 0 || !g.tz) {
|
|
2840
|
+
this.err('rebase: 入力は既定整列の day グリッド(幅 1d・日内オフセット 0・tz 名つき)を要求'
|
|
2841
|
+
+ `——現在の整列=${gridDesc(g)}。時刻つき・anchor つき・整列なしは静的エラー`
|
|
2842
|
+
+ '(時刻保存の再錨は将来拡張——rebase+同 tz 化後の coincides で書ける。ADR-40 判断 4)');
|
|
2843
|
+
}
|
|
2844
|
+
if (g.tz === toV)
|
|
2845
|
+
return stream; // source == to は恒等(ADR-40 判断 5)
|
|
2846
|
+
const srcTz = getTz(g.tz);
|
|
2847
|
+
const toTz = getTz(toV);
|
|
2848
|
+
const mapDay = (dayStart) => {
|
|
2849
|
+
const f = srcTz.localFields(dayStart);
|
|
2850
|
+
const t = toTz.civilDayStart(f.y, f.mo, f.d);
|
|
2851
|
+
const tf = toTz.localFields(t);
|
|
2852
|
+
if (tf.y !== f.y || tf.mo !== f.mo || tf.d !== f.d) {
|
|
2853
|
+
const pp = (n) => String(n).padStart(2, '0');
|
|
2854
|
+
this.err(`rebase: 存在しない日付 ${f.y}-${pp(f.mo)}-${pp(f.d)}(tz "${toV}" に無い——`
|
|
2855
|
+
+ '日付変更線の移動で消えた日。ADR-33 判断 9 のデータ相対エラー・ADR-40 判断 3)');
|
|
2856
|
+
}
|
|
2857
|
+
return t;
|
|
2858
|
+
};
|
|
2859
|
+
// 日付順は tz に依らず同順=単射・順序保存。写像は東行きで最大 1 日ぶん実体化範囲を
|
|
2860
|
+
// 越え得るため、計算範囲端でクリップ(実装の近似地平線——ADR-37 判断 8 と同じ据え方)
|
|
2861
|
+
const pts = stream.pts.map(mapDay).filter(p => p < this.rt.computeEnd);
|
|
2862
|
+
// 註釈の輸送(ADR-37 輸送表の rebase 行): 端点を source の day 窓へ floor/ceil で膨らませて
|
|
2863
|
+
// からラベル対応で写す(過大近似許容)。存在しない日付の端は安全側の隣日へ広げる
|
|
2864
|
+
const mapEdge = (ms, up) => {
|
|
2865
|
+
if (!isFinite(ms))
|
|
2866
|
+
return ms;
|
|
2867
|
+
let day = srcTz.floorToDay(ms);
|
|
2868
|
+
if (up && day < ms)
|
|
2869
|
+
day = srcTz.addCivilDays(day, 1); // ceil
|
|
2870
|
+
for (let k = 0; k < 3; k++) {
|
|
2871
|
+
const f = srcTz.localFields(day);
|
|
2872
|
+
const t = toTz.civilDayStart(f.y, f.mo, f.d);
|
|
2873
|
+
const tf = toTz.localFields(t);
|
|
2874
|
+
if (tf.y === f.y && tf.mo === f.mo && tf.d === f.d)
|
|
2875
|
+
return t;
|
|
2876
|
+
day = srcTz.addCivilDays(day, up ? 1 : -1);
|
|
2877
|
+
}
|
|
2878
|
+
return up ? Infinity : -Infinity;
|
|
2879
|
+
};
|
|
2880
|
+
const ann = normAnn(stream.ann.map(a => ({ ...a, from: mapEdge(a.from, false), to: mapEdge(a.to, true) })));
|
|
2881
|
+
// 出力整列は to tz の day グリッド(構成的・ADR-36 整列表の rebase 行)
|
|
2882
|
+
const align = { kind: 'civil', step: 1, phase: 0, off: 0, tz: toV };
|
|
2883
|
+
return { ...stream, pts, align, ann };
|
|
2884
|
+
}
|
|
2885
|
+
case 'stride': {
|
|
2886
|
+
const n = this.num(this.evalExpr(positional[0] ?? this.err('stride(n) の n が必要'), env));
|
|
2887
|
+
// n の域(ADR-38 判断 12): stride(0) が JS 剰余で「黙って空」になる故障クラスの根絶
|
|
2888
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
2889
|
+
this.err(`stride: n は 1 以上の整数(${n} は不可。ADR-38 判断 12)`);
|
|
2890
|
+
}
|
|
2891
|
+
const fromE = named('from') ?? this.err('stride: from: が必須(起点の明示。ADR-31・§4.7)');
|
|
2892
|
+
const anchor = this.point(this.evalExpr(fromE, env));
|
|
2893
|
+
// 判断 4(ストライド): 歩行が註釈区間に交差したら最初の交差点から先すべて(位相は状態=汚染は伝染)
|
|
2894
|
+
const tails = stream.ann.filter(a => a.to > anchor)
|
|
2895
|
+
.map(a => ({ ...a, from: Math.max(a.from, anchor), to: Infinity }));
|
|
2896
|
+
const ann = annUnion(stream.ann, tails);
|
|
2897
|
+
const start = stream.pts.findIndex(p => p >= anchor);
|
|
2898
|
+
if (start < 0)
|
|
2899
|
+
return { ...stream, pts: [], ann };
|
|
2900
|
+
return { ...stream, pts: stream.pts.slice(start).filter((_, i) => i % n === 0), ann };
|
|
2901
|
+
}
|
|
2902
|
+
case 'take': {
|
|
2903
|
+
// 先頭 N 選択(ADR-49): from: 以後の入力点の先頭 n 点だけを通す
|
|
2904
|
+
if (stream.wins.length > 0) {
|
|
2905
|
+
this.err('take: 窓付き入力は取らない——「窓ごとの先頭 N」は within の後の nth(第 N)か'
|
|
2906
|
+
+ ' ordinalIn の述語で(take は通し数え。ADR-49)');
|
|
2907
|
+
}
|
|
2908
|
+
const n = this.num(this.evalExpr(positional[0] ?? this.err('take(n) の n が必要'), env));
|
|
2909
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
2910
|
+
this.err(`take: n は 1 以上の整数(${n} は不可。ADR-38 判断 12 と同規約)`);
|
|
2911
|
+
}
|
|
2912
|
+
const fromE = named('from') ?? this.err('take: from: が必須(起点の明示。ADR-31・§4.7)');
|
|
2913
|
+
const anchor = this.point(this.evalExpr(fromE, env));
|
|
2914
|
+
const start = stream.pts.findIndex(p => p >= anchor);
|
|
2915
|
+
const pts = start < 0 ? [] : stream.pts.slice(start, start + n);
|
|
2916
|
+
// 輸送行(ADR-49): 数えに交差した註釈は交差点から先すべて(stride 行の同型)。
|
|
2917
|
+
// take 固有の縮小——第 n 点が交差より前に確定したら、確定後に始まる註釈は輸送しない
|
|
2918
|
+
// (出力は入力の先頭 n 点にしか依存しない=n 到達後は正当な空・ADR-37 判断 2)
|
|
2919
|
+
const settled = pts.length === n ? pts[n - 1] : Infinity;
|
|
2920
|
+
const relevant = stream.ann.filter(a => a.from <= settled);
|
|
2921
|
+
const tails = relevant.filter(a => a.to > anchor)
|
|
2922
|
+
.map(a => ({ ...a, from: Math.max(a.from, anchor), to: Infinity }));
|
|
2923
|
+
// 出力は静的有限(≤ n)=ADR-39 の有限性分類(ADR-52 の検証で判明した素通しを修正)
|
|
2924
|
+
return { ...stream, pts, ann: annUnion(relevant, tails), endless: false };
|
|
2925
|
+
}
|
|
2926
|
+
case 'takeLast': {
|
|
2927
|
+
// 末尾 N 選択(ADR-52): until: 以前(until: の点を含む)の入力点の末尾 n 点だけを通す(take の鏡像)
|
|
2928
|
+
if (stream.wins.length > 0) {
|
|
2929
|
+
this.err('takeLast: 窓付き入力は取らない——「窓ごとの最後の 1」は within の後の last で・'
|
|
2930
|
+
+ '「最後の N」は窓境界点からの shift(-k, unit: 軸) の和で(takeLast は通し数え。ADR-52)');
|
|
2931
|
+
}
|
|
2932
|
+
const n = this.num(this.evalExpr(positional[0] ?? this.err('takeLast(n) の n が必要'), env));
|
|
2933
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
2934
|
+
this.err(`takeLast: n は 1 以上の整数(${n} は不可。ADR-38 判断 12 と同規約)`);
|
|
2935
|
+
}
|
|
2936
|
+
const untilE = named('until') ?? this.err('takeLast: until: が必須(終端の明示。ADR-31 の対称・§4.7)');
|
|
2937
|
+
const until = this.point(this.evalExpr(untilE, env));
|
|
2938
|
+
// 実装地平線ガード①(ADR-37 判断 8): endless 入力で until: が実体化域を越えると
|
|
2939
|
+
// 「実体化済みの末尾」の取り違えで誤った点が表示窓に出得る——警告して実体化末尾から数える
|
|
2940
|
+
if (stream.endless && until >= this.rt.computeEnd) {
|
|
2941
|
+
this.rt.warnings.push(`horizon-clip: takeLast until ${this.rt.fmt(until)}`
|
|
2942
|
+
+ '(計算範囲 to+400日 の実体化地平線——実体化済みの末尾から数える。言語の地平線ではない。ADR-37 判断 8)');
|
|
2943
|
+
}
|
|
2944
|
+
const end = ptUpperBound(stream.pts, until);
|
|
2945
|
+
const pts = stream.pts.slice(Math.max(0, end - n), end);
|
|
2946
|
+
// 実装地平線ガード②(F107 同族): 生成子由来で n 個に満たない=実体化下限で切れている
|
|
2947
|
+
// (テーブル由来の n 未満は正当——covering の頭は註釈の経路が受け持つ)
|
|
2948
|
+
if (stream.endless && pts.length < n) {
|
|
2949
|
+
this.rt.warnings.push(`horizon-clip: takeLast ${this.rt.fmt(until)}`
|
|
2950
|
+
+ `(実体化下限で切れ——n=${n} 個中 ${pts.length} 個のみ。紀元は tz 相対・言語の地平線ではない。ADR-37 判断 8・F107)`);
|
|
2951
|
+
}
|
|
2952
|
+
// 輸送行(ADR-52=ADR-49 判断 5 の鏡像): 逆向き数えに交差した註釈は交差点から過去側すべて。
|
|
2953
|
+
// 縮小——逆向き数えが交差せず第 n 点(過去端)が確定した場合に限り、それより過去で完結する
|
|
2954
|
+
// 註釈区間は輸送しない(出力は [第 n 点, until] の入力にしか依存しない。交差があれば当該
|
|
2955
|
+
// 区間の head 拡幅 (-∞, …) が過去側を覆うため、この filter は過小近似にならない)
|
|
2956
|
+
const settledL = pts.length === n ? pts[0] : -Infinity;
|
|
2957
|
+
const relevantL = stream.ann.filter(a => a.to >= settledL);
|
|
2958
|
+
const heads = relevantL.filter(a => a.from <= until)
|
|
2959
|
+
.map(a => ({ ...a, from: -Infinity, to: Math.min(a.to, until) }));
|
|
2960
|
+
return { ...stream, pts, ann: annUnion(relevantL, heads), endless: false };
|
|
2961
|
+
}
|
|
2962
|
+
case 'strideBy': {
|
|
2963
|
+
const wV = this.evalExpr(positional[0] ?? this.err('strideBy(w) の幅が必要'), env);
|
|
2964
|
+
if (!isObj(wV) || wV.k !== 'width')
|
|
2965
|
+
this.err('strideBy は幅リテラルを取る');
|
|
2966
|
+
const fromE = named('from') ?? this.err('strideBy: from: が必須(起点の明示。ADR-31・§4.7)');
|
|
2967
|
+
const fromV = this.evalExpr(fromE, env);
|
|
2968
|
+
let anchor;
|
|
2969
|
+
if (isObj(fromV) && fromV.k === 'time') {
|
|
2970
|
+
// 単独時刻リテラル(ADR-51): 市民 1d の壁時計 tick 専用——epoch 錨日で錨打ち。
|
|
2971
|
+
// 他の幅は位相が黙って epoch 日に固定される罠になるため静的エラー(錨の明示を要求)
|
|
2972
|
+
if (!(wV.w.kind === 'civil' && wV.w.days === 1)) {
|
|
2973
|
+
this.err('strideBy: 単独時刻リテラルの from: は市民日幅 1d 専用(壁時計 tick)——'
|
|
2974
|
+
+ '他の幅は日付つきの日時リテラルで錨を明示する(ADR-51)');
|
|
2975
|
+
}
|
|
2976
|
+
anchor = this.anchorTimeOfDay(fromV.todMs, env);
|
|
2977
|
+
}
|
|
2978
|
+
else {
|
|
2979
|
+
anchor = this.point(fromV);
|
|
2980
|
+
}
|
|
2981
|
+
const mod = (a, b) => ((a % b) + b) % b;
|
|
2982
|
+
const pts = [];
|
|
2983
|
+
let align;
|
|
2984
|
+
if (wV.w.kind === 'civil') {
|
|
2985
|
+
// 市民時幅は市民日で歩進=壁時計保存(DST の日は 23/25h。ADR-11/12)。
|
|
2986
|
+
// 日内オフセットは壁時計ラベル読み——grid の時刻付き anchor と同じ規定(ADR-31 改訂 2=F87)
|
|
2987
|
+
const tz = this.tzObjOf(env);
|
|
2988
|
+
const aDay = tz.floorToDay(anchor);
|
|
2989
|
+
const off = anchor === aDay ? 0 : tz.timeOfDayLabel(anchor);
|
|
2990
|
+
const days = tz.dayStarts(this.rt.computeEnd + DAY_MS);
|
|
2991
|
+
const aIdx = tz.dayIndex(aDay);
|
|
2992
|
+
for (let i = aIdx; i < days.length; i += wV.w.days) {
|
|
2993
|
+
const t = off === 0 ? days[i] : tz.atTimeOfDay(days[i], off);
|
|
2994
|
+
if (t >= this.rt.computeEnd)
|
|
2995
|
+
break;
|
|
2996
|
+
pts.push(t);
|
|
2997
|
+
}
|
|
2998
|
+
align = { kind: 'civil', step: wV.w.days, phase: mod(aIdx, wV.w.days), off,
|
|
2999
|
+
tz: this.tzNameOf(env) };
|
|
3000
|
+
}
|
|
3001
|
+
else {
|
|
3002
|
+
const step = wV.w.ms;
|
|
3003
|
+
for (let t = anchor; t < this.rt.computeEnd; t += step)
|
|
3004
|
+
pts.push(t);
|
|
3005
|
+
align = { kind: 'elapsed', step, phase: mod(anchor, step), off: 0, tz: '' };
|
|
3006
|
+
}
|
|
3007
|
+
// ADR-36: strideBy の出力は定義上 anchor 付きグリッドの目盛りそのもの。
|
|
3008
|
+
// 生成子(入力の点に依存しない幅の等差列)=暦法純粋な再生成なので註釈を生まない
|
|
3009
|
+
return { k: 'stream', pts, wins: [], align, ann: [], endless: true };
|
|
3010
|
+
}
|
|
3011
|
+
default: {
|
|
3012
|
+
// 糖衣段: 束縛を呼び出し時の在圏 premise で解決し、右辺を機械的に差し込む(§4.8)
|
|
3013
|
+
const binding = this.lookupBinding(stage.name, env);
|
|
3014
|
+
if (!binding)
|
|
3015
|
+
this.err(`未知の段: ${stage.name}`);
|
|
3016
|
+
const locals = this.bindParams(binding.decl.params, stage.args, env, stage.name);
|
|
3017
|
+
// 標準糖衣 at の名指し検査(ADR-51 追記=還流第 15 便 (a)(c)): 展開形は入力 s を
|
|
3018
|
+
// 述語として読み直すため、窓と anchor 引数の逸脱が黙って誤結果になる——展開前に塞ぐ。
|
|
3019
|
+
// 検査は標準語彙の名の防御(calendar-system 側で解決された at)——利用側 premise の
|
|
3020
|
+
// 同名再定義には掛からない
|
|
3021
|
+
if (stage.name === 'at' && binding.premise !== null
|
|
3022
|
+
&& binding.premise === this.calendarSystemOf(env)) {
|
|
3023
|
+
if (stream.wins.length > 0) {
|
|
3024
|
+
this.err('at: 窓付き入力は取らない——展開形は入力の窓を引き継がない(通し数えや選択子の'
|
|
3025
|
+
+ '型エラーに化ける)。窓は at の後で切る: … |> at(Thh:mm) |> within(…)(外延同値。ADR-51 追記)');
|
|
3026
|
+
}
|
|
3027
|
+
const p0 = binding.decl.params[0];
|
|
3028
|
+
const a0 = p0 ? locals.get(p0.name) : undefined;
|
|
3029
|
+
if (!(a0 !== undefined && isObj(a0) && a0.k === 'time')) {
|
|
3030
|
+
this.err('at: 引数は単独時刻リテラル(Thh:mm)のみ——日付つきの錨は前方専用で anchor より'
|
|
3031
|
+
+ '前の点が黙って空になる(anchor 引数形は不採=ADR-51 判断 8)。日の調整は日の層で: '
|
|
3032
|
+
+ 'shift(±k, unit: day) |> at(Thh:mm)');
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
// 糖衣右辺も呼び出し側 locals 鎖を遮断(ADR-53 の同一原則——実引数は遮断前の env で評価済み)
|
|
3036
|
+
const base = { ...(binding.premise ? { ...env, premise: binding.premise } : env),
|
|
3037
|
+
locals: null, parent: null };
|
|
3038
|
+
const defEnv = childEnv(base, locals);
|
|
3039
|
+
const prevSevered = this.severedLocalsEnv;
|
|
3040
|
+
this.severedLocalsEnv = this.severedLocalsEnv ?? env; // 最外の遮断点=ユーザーのラムダ変数が居る env を保持(ネストで上書きしない)
|
|
3041
|
+
try {
|
|
3042
|
+
return this.applyTransform(binding.decl.rhs, defEnv, stream);
|
|
3043
|
+
}
|
|
3044
|
+
finally {
|
|
3045
|
+
this.severedLocalsEnv = prevSevered;
|
|
3046
|
+
}
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
/** 糖衣右辺の変換適用: 基底 B(s => s |> …)とポイントフリー略記 A の両方(§4.8) */
|
|
3051
|
+
applyTransform(rhs, env, stream) {
|
|
3052
|
+
if (rhs.t === 'lambda') { // 基底 B
|
|
3053
|
+
const v = this.applyValue({ k: 'lambda', params: rhs.params, body: rhs.body, env }, [stream], env);
|
|
3054
|
+
return this.toStream(v);
|
|
3055
|
+
}
|
|
3056
|
+
if (rhs.t === 'pipe') { // 略記 A: 先頭段から合成
|
|
3057
|
+
let s = this.applyTransform(rhs.head, env, stream);
|
|
3058
|
+
for (const st of rhs.stages)
|
|
3059
|
+
s = this.applyStage(s, st, env);
|
|
3060
|
+
return s;
|
|
3061
|
+
}
|
|
3062
|
+
if (rhs.t === 'call' && rhs.callee.t === 'name') {
|
|
3063
|
+
return this.applyStage(stream, { name: rhs.callee.name, args: rhs.args }, env);
|
|
3064
|
+
}
|
|
3065
|
+
if (rhs.t === 'name') {
|
|
3066
|
+
return this.applyStage(stream, { name: rhs.name, args: [] }, env);
|
|
3067
|
+
}
|
|
3068
|
+
this.err('糖衣の右辺を変換として適用できない(生成子を含む糖衣は生成子位置で使う)');
|
|
3069
|
+
}
|
|
3070
|
+
axisMember(env, op) {
|
|
3071
|
+
const m = env.members.get('axis');
|
|
3072
|
+
if (!m)
|
|
3073
|
+
this.err(`${op}: 軸がない(on:/unit: を書くか前文で axis: を宣言。§3.3 は宣言必須)`);
|
|
3074
|
+
return typeof m === 'string' ? { t: 'name', name: m } : m;
|
|
3075
|
+
}
|
|
3076
|
+
// ---------- 型の取り出し・変換 ----------
|
|
3077
|
+
toStream(v, name) {
|
|
3078
|
+
if (isObj(v)) {
|
|
3079
|
+
if (v.k === 'stream')
|
|
3080
|
+
return v;
|
|
3081
|
+
if (v.k === 'windows') {
|
|
3082
|
+
return { k: 'stream', pts: v.units.filter(p => p < this.rt.computeEnd),
|
|
3083
|
+
wins: [{ name: name ?? v.name, iv: v.iv, labelFn: v.labelFn, grain: v.grain, ann: [] }],
|
|
3084
|
+
align: v.grain, ann: [], endless: true };
|
|
3085
|
+
}
|
|
3086
|
+
if (v.k === 'table') {
|
|
3087
|
+
// 覆域の補集合=範囲外註釈(ADR-37 判断 1)。参照は被覆サマリにも記録(判断 7 (b))
|
|
3088
|
+
this.registerCoverage(v.src ?? '(無名テーブル)', v.covDesc, v.covIv, v.concluded, v.asof);
|
|
3089
|
+
return { k: 'stream', pts: v.pts.slice(), wins: [], align: v.align, ann: this.tableAnn(v),
|
|
3090
|
+
endless: false };
|
|
3091
|
+
}
|
|
3092
|
+
if (v.k === 'point')
|
|
3093
|
+
return { k: 'stream', pts: [v.ms], wins: [], align: null, ann: [], endless: false };
|
|
3094
|
+
if (v.k === 'instant')
|
|
3095
|
+
this.err('everyInstant は strideBy(w, from:) と併用する(プロトタイプ)');
|
|
3096
|
+
}
|
|
3097
|
+
this.err(`時間ストリームではない: ${kindOf(v)}`
|
|
3098
|
+
+ (Array.isArray(v) && v.length === 0 ? '(空リストは covering: を付ければ空テーブル。ADR-45)' : ''));
|
|
3099
|
+
}
|
|
3100
|
+
/** snapTo/within の対象窓の要素グリッド(ADR-36 判断 2 の「要素グリッド」)。
|
|
3101
|
+
* windows=生成連鎖の grain・窓つきストリーム=最内窓の grain(segmentBy はマーカーの整列) */
|
|
3102
|
+
winGrainOf(v) {
|
|
3103
|
+
if (isObj(v)) {
|
|
3104
|
+
if (v.k === 'windows')
|
|
3105
|
+
return v.grain;
|
|
3106
|
+
if (v.k === 'stream' && v.wins.length > 0)
|
|
3107
|
+
return v.wins[v.wins.length - 1].grain ?? null;
|
|
3108
|
+
}
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
/** 対象窓列の実体化下限=窓 tz の紀元の市民日開始(紀元は tz 相対=ADR-33。F107)。
|
|
3112
|
+
* 市民グリッド以外(経過・整列なし)は実行既定 tz の紀元(従来判定)に落とす——
|
|
3113
|
+
* anchored grid の位相による頭の切り欠き(紀元以後・窓なし)は従来どおり③硬エラー側 */
|
|
3114
|
+
windowFloorOf(v) {
|
|
3115
|
+
const g = this.winGrainOf(v);
|
|
3116
|
+
return g && g.kind === 'civil' && g.tz ? getTz(g.tz).civilDayStart(1970, 1, 1) : this.rt.epoch;
|
|
3117
|
+
}
|
|
3118
|
+
toPoints(v) {
|
|
3119
|
+
return this.toStream(v).pts;
|
|
3120
|
+
}
|
|
3121
|
+
windowsOf(v) {
|
|
3122
|
+
if (isObj(v) && v.k === 'windows')
|
|
3123
|
+
return v;
|
|
3124
|
+
this.err(`窓(パーティション)ではない: ${kindOf(v)}`);
|
|
3125
|
+
}
|
|
3126
|
+
/** split の親・by: の受理(ADR-48): パーティション窓に加え、規則マーカー由来の実効パーティション
|
|
3127
|
+
* (覆域註釈のない segmentBy 製窓列=within が受けるものの部分集合)を受ける。
|
|
3128
|
+
* データ由来(covering 付き)マーカーの窓列は受理しない——覆域編集で g(i) の序数が動く
|
|
3129
|
+
* (関数 g には labels: の同長性検査に相当する防波堤が無い)。empties: drop の親(非連続)も不可。 */
|
|
3130
|
+
splitWindowsOf(v, role) {
|
|
3131
|
+
if (isObj(v) && v.k === 'windows')
|
|
3132
|
+
return v;
|
|
3133
|
+
if (isObj(v) && v.k === 'stream' && v.wins.length > 0) {
|
|
3134
|
+
const top = v.wins[v.wins.length - 1];
|
|
3135
|
+
if (top.ann.length > 0) {
|
|
3136
|
+
this.err(`split(${role}): データ由来(covering 付き)マーカーの窓列は受理しない——覆域の`
|
|
3137
|
+
+ '編集で g の引く序数が黙って動く(規則マーカーの窓列か segmentBy 正準形へ。ADR-48)');
|
|
3138
|
+
}
|
|
3139
|
+
for (let i = 1; i < top.iv.length; i++) {
|
|
3140
|
+
if (top.iv[i].start !== top.iv[i - 1].end) {
|
|
3141
|
+
this.err(`split(${role}): 窓列が連続でない(empties: drop の親は受理しない——空窓の除去が`
|
|
3142
|
+
+ '序数を詰める。keep/error へ。ADR-48)');
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
return { k: 'windows', iv: top.iv, units: v.pts.slice(), labelFn: top.labelFn, grain: top.grain ?? null };
|
|
3146
|
+
}
|
|
3147
|
+
this.err(`窓(パーティション)ではない: ${kindOf(v)}${isObj(v) && v.k === 'cycle'
|
|
3148
|
+
? '(cycle は並列ラベル束——分割対象の窓列ではない。ADR-48)' : ''}`);
|
|
3149
|
+
}
|
|
3150
|
+
/** 窓相当(windows・cycle・窓つきストリーム)から区間列を得る */
|
|
3151
|
+
intervalsOf(v, env) {
|
|
3152
|
+
if (isObj(v)) {
|
|
3153
|
+
if (v.k === 'windows')
|
|
3154
|
+
return v.iv;
|
|
3155
|
+
if (v.k === 'cycle')
|
|
3156
|
+
return v.iv;
|
|
3157
|
+
if (v.k === 'stream' && v.wins.length > 0)
|
|
3158
|
+
return v.wins[v.wins.length - 1].iv;
|
|
3159
|
+
}
|
|
3160
|
+
this.err(`窓として使えない値: ${kindOf(v)}`);
|
|
3161
|
+
}
|
|
3162
|
+
checkPartition(iv, name) {
|
|
3163
|
+
for (let i = 1; i < iv.length; i++) {
|
|
3164
|
+
if (iv[i].start !== iv[i - 1].end) {
|
|
3165
|
+
this.rt.warnings.push(`I5: 窓 ${name} は連続でない(${this.rt.fmt(iv[i - 1].end)} 付近)`);
|
|
3166
|
+
return;
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
// ---------- プリミティブ取り出し ----------
|
|
3171
|
+
num(v) {
|
|
3172
|
+
if (typeof v === 'number')
|
|
3173
|
+
return v;
|
|
3174
|
+
this.err(`数値ではない: ${kindOf(v)}`);
|
|
3175
|
+
}
|
|
3176
|
+
bool(v) {
|
|
3177
|
+
if (typeof v === 'boolean')
|
|
3178
|
+
return v;
|
|
3179
|
+
this.err(`論理値ではない: ${kindOf(v)}`);
|
|
3180
|
+
}
|
|
3181
|
+
sym(v) {
|
|
3182
|
+
if (typeof v === 'string')
|
|
3183
|
+
return v;
|
|
3184
|
+
this.err(`ラベルではない: ${kindOf(v)}`);
|
|
3185
|
+
}
|
|
3186
|
+
point(v) {
|
|
3187
|
+
if (isObj(v) && v.k === 'point')
|
|
3188
|
+
return v.ms;
|
|
3189
|
+
if (isObj(v) && v.k === 'time') {
|
|
3190
|
+
this.err('単独時刻リテラルは strideBy(1d, from:)(壁時計 tick)の位置でのみ使える——'
|
|
3191
|
+
+ 'ここでは日付つきの日時リテラルで錨を明示する(ADR-51)');
|
|
3192
|
+
}
|
|
3193
|
+
this.err(`時点ではない: ${kindOf(v)}`);
|
|
3194
|
+
}
|
|
3195
|
+
err(msg) {
|
|
3196
|
+
throw new KairosError(msg);
|
|
3197
|
+
}
|
|
3198
|
+
}
|