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.
@@ -0,0 +1,9 @@
1
+ import type { RunOptions, RunResult } from './eval.ts';
2
+ export { parse } from './parser.ts';
3
+ export { lex } from './lexer.ts';
4
+ export { KairosError, SupplyError, formatAnnotation } from './eval.ts';
5
+ export type { RunOptions, RunResult, ExternalDecl, ExternalData, ExternalResolver } from './eval.ts';
6
+ /** Kairos プログラムを評価し、各本体式の時間ストリームを [from, to) で返す */
7
+ export declare function run(source: string, opts: RunOptions): RunResult;
8
+ /** 単一式の評価糖衣: 最後の本体式の日付列を返す */
9
+ export declare function evalDates(source: string, opts: RunOptions): string[];
package/dist/index.js ADDED
@@ -0,0 +1,64 @@
1
+ // Kairos リファレンス実装(プロトタイプ)— 公開 API
2
+ import { readFileSync } from 'node:fs';
3
+ import { parse } from "./parser.js";
4
+ import { Runtime, Evaluator, KairosError } from "./eval.js";
5
+ import { getTz } from "./tz.js";
6
+ export { parse } from "./parser.js";
7
+ export { lex } from "./lexer.js";
8
+ export { KairosError, SupplyError, formatAnnotation } from "./eval.js";
9
+ // 標準 premise の読み込み順は依存順(派生は base の登録が先に要る)。readdir の辞書順は不可
10
+ const STDLIB_FILES = ['gregorian.kairos', 'fiscal.kairos', 'isoweek.kairos'];
11
+ const stdlibSource = STDLIB_FILES
12
+ .map(f => readFileSync(new URL(`../stdlib/${f}`, import.meta.url), 'utf8'))
13
+ .join('\n');
14
+ /** Kairos プログラムを評価し、各本体式の時間ストリームを [from, to) で返す */
15
+ export function run(source, opts) {
16
+ const tz = opts.tz ?? 'Asia/Tokyo';
17
+ const tzObj = getTz(tz);
18
+ const d = (s) => {
19
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
20
+ if (!m)
21
+ throw new KairosError(`日付は YYYY-MM-DD: ${s}`);
22
+ return tzObj.civilDayStart(+m[1], +m[2], +m[3]); // 評価範囲の端は実行 tz の市民日
23
+ };
24
+ const rt = new Runtime(d(opts.from), d(opts.to), tz);
25
+ if (opts.resolve)
26
+ rt.resolver = opts.resolve; // external の解決子(ADR-46)
27
+ const ev = new Evaluator(rt);
28
+ const stdlib = parse(stdlibSource);
29
+ const program = parse(source);
30
+ // stdlib の premise を先に登録(cycle ラベル語彙の走査を含む)
31
+ for (const st of stdlib.statements) {
32
+ if (st.t === 'premiseDef')
33
+ ev.registerPremise(st);
34
+ }
35
+ for (const st of program.statements) {
36
+ if (st.t === 'premiseDef')
37
+ for (const b of st.block?.bindings ?? [])
38
+ ev.scanVocab(b.rhs);
39
+ }
40
+ const defaultMembers = new Map();
41
+ const results = ev.runProgram(program, defaultMembers);
42
+ // 被覆サマリ(ADR-37 判断 7 (b)): クリップしない静的な監視面。残走路=評価 to から覆域終端まで
43
+ const coverage = [...rt.coverage.values()].map(c => ({
44
+ source: c.source,
45
+ covering: c.covering,
46
+ ...(c.asof ? { asof: c.asof } : {}),
47
+ concluded: c.concluded,
48
+ runwayDays: Number.isFinite(c.covEnd) ? Math.round((c.covEnd - rt.toMs) / 86_400_000) : null,
49
+ }));
50
+ return {
51
+ results,
52
+ coverage,
53
+ warnings: rt.warnings,
54
+ format: ms => rt.fmt(ms),
55
+ runtime: rt,
56
+ };
57
+ }
58
+ /** 単一式の評価糖衣: 最後の本体式の日付列を返す */
59
+ export function evalDates(source, opts) {
60
+ const r = run(source, opts);
61
+ if (r.results.length === 0)
62
+ throw new KairosError('本体式がない');
63
+ return r.results[r.results.length - 1].dates;
64
+ }
@@ -0,0 +1,33 @@
1
+ export type TokKind = 'name' | 'number' | 'date' | 'time' | 'width' | 'string' | 'punct' | 'newline' | 'eof';
2
+ export interface DateVal {
3
+ y: number;
4
+ mo: number;
5
+ d: number;
6
+ h: number;
7
+ mi: number;
8
+ s: number;
9
+ hasTime: boolean;
10
+ }
11
+ export type WidthVal = {
12
+ kind: 'civil';
13
+ days: number;
14
+ } | {
15
+ kind: 'elapsed';
16
+ ms: number;
17
+ };
18
+ export interface Token {
19
+ kind: TokKind;
20
+ text: string;
21
+ line: number;
22
+ col: number;
23
+ date?: DateVal;
24
+ width?: WidthVal;
25
+ num?: number;
26
+ tod?: number;
27
+ }
28
+ export declare class LexError extends Error {
29
+ line: number;
30
+ col: number;
31
+ constructor(msg: string, line: number, col: number);
32
+ }
33
+ export declare function lex(src: string): Token[];
package/dist/lexer.js ADDED
@@ -0,0 +1,171 @@
1
+ // Kairos 字句解析器(spec §5.5・ADR-28)
2
+ // 日付リテラル(TZ 指定子なし)・幅リテラル(市民時 d / 経過時間 h m s、混合は静的エラー)・
3
+ // Unicode 識別子(漢字ラベル可)・行コメント #。
4
+ export class LexError extends Error {
5
+ line;
6
+ col;
7
+ constructor(msg, line, col) {
8
+ super(`字句エラー(${line}:${col}): ${msg}`);
9
+ this.line = line;
10
+ this.col = col;
11
+ }
12
+ }
13
+ // 複数文字パンクチュエータは長い順に照合
14
+ const PUNCTS = ['|>', '..', '==', '!=', '<=', '>=', '=>', '(', ')', '[', ']', '{', '}',
15
+ ',', ':', '?', '@', '|', '&', '\\', '=', '<', '>', '+', '-', '*', '/', '.', '_', ';'];
16
+ const isLetter = (c) => /[\p{L}]/u.test(c);
17
+ const isDigit = (c) => c >= '0' && c <= '9';
18
+ const isNamePart = (c) => isLetter(c) || isDigit(c);
19
+ export function lex(src) {
20
+ const toks = [];
21
+ let i = 0, line = 1, col = 1;
22
+ // 丸括弧・角括弧の深さ。内部では newline をトークン化しない(式の継続)。
23
+ // 波括弧 {} 内は premise ブロック=メンバー/束縛が行区切りなので newline を残す。
24
+ let parenDepth = 0;
25
+ const peek = (o = 0) => src[i + o] ?? '';
26
+ const err = (msg) => { throw new LexError(msg, line, col); };
27
+ while (i < src.length) {
28
+ const c = src[i];
29
+ if (c === '#') { // 行コメント
30
+ while (i < src.length && src[i] !== '\n')
31
+ i++;
32
+ continue;
33
+ }
34
+ if (c === '\n') {
35
+ if (parenDepth === 0)
36
+ toks.push({ kind: 'newline', text: '\n', line, col });
37
+ i++;
38
+ line++;
39
+ col = 1;
40
+ continue;
41
+ }
42
+ if (c === ' ' || c === '\t' || c === '\r') {
43
+ i++;
44
+ col++;
45
+ continue;
46
+ }
47
+ // 文字列リテラル(§5.5・ADR-32): " から次の " まで。改行不可・エスケープなし
48
+ if (c === '"') {
49
+ let j = i + 1;
50
+ while (j < src.length && src[j] !== '"' && src[j] !== '\n')
51
+ j++;
52
+ if (src[j] !== '"')
53
+ err('文字列リテラルが閉じていない(改行は含められない。ADR-32)');
54
+ toks.push({ kind: 'string', text: src.slice(i + 1, j), line, col });
55
+ col += j - i + 1;
56
+ i = j + 1;
57
+ continue;
58
+ }
59
+ // 日付リテラル: YYYY-MM-DD(Thh:mm(:ss(.f+)?)?)?
60
+ if (isDigit(c) && /^\d{4}-\d{2}-\d{2}/.test(src.slice(i, i + 10))) {
61
+ const m = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?)?/.exec(src.slice(i));
62
+ const hasTime = m[4] !== undefined;
63
+ // 時刻部の妥当性: hh 00..23・mm 00..59・ss 00..59。23:59:60 は字句エラー——
64
+ // chronos はうるう秒を持たない一様な理想化軸(UTC の各日=86,400 秒。ADR-33)
65
+ if (hasTime && (+m[4] > 23 || +m[5] > 59 || (m[6] !== undefined && +m[6] > 59))) {
66
+ err(`時刻が範囲外: ${m[0]}(hh は 00..23・mm/ss は 00..59。うるう秒は表現しない=ADR-33)`);
67
+ }
68
+ // 日付部の妥当性(F66 (a)・ADR-43): proleptic Gregorian 固定——月 01..12・日は月と閏年規則の
69
+ // 実在日のみ。2026-02-30 は 2026-03-02 への黙ったロールオーバーではなく字句エラー(時刻部と同じ層)
70
+ {
71
+ const y = +m[1], mo = +m[2], d = +m[3];
72
+ const leap = y % 4 === 0 && (y % 100 !== 0 || y % 400 === 0);
73
+ const dim = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
74
+ if (mo < 1 || mo > 12 || d < 1 || d > dim[mo - 1]) {
75
+ err(`実在しない日付: ${m[0].slice(0, 10)}`
76
+ + '(月は 01..12・日は月と閏年規則の実在日のみ——proleptic Gregorian 固定。F66/ADR-43)');
77
+ }
78
+ }
79
+ const date = {
80
+ y: +m[1], mo: +m[2], d: +m[3],
81
+ h: m[4] ? +m[4] : 0, mi: m[5] ? +m[5] : 0,
82
+ s: m[6] ? +m[6] + (m[7] ? +`0.${m[7]}` : 0) : 0,
83
+ hasTime,
84
+ };
85
+ toks.push({ kind: 'date', text: m[0], line, col, date });
86
+ i += m[0].length;
87
+ col += m[0].length;
88
+ continue;
89
+ }
90
+ // 幅リテラル: 数値+単位の並び(1d / 24h39m35.244s)。d と h/m/s の混合は静的エラー(ADR-28)。
91
+ if (isDigit(c)) {
92
+ const w = /^(?:\d+(?:\.\d+)?[dhms])+/.exec(src.slice(i));
93
+ if (w && !/^\d+(?:\.\d+)?(?:[eE]|$|[^dhms\d.])/.test(src.slice(i))) {
94
+ const text = w[0];
95
+ let civil = 0, elapsed = 0, hasCivil = false, hasElapsed = false;
96
+ for (const seg of text.matchAll(/(\d+(?:\.\d+)?)([dhms])/g)) {
97
+ const n = +seg[1];
98
+ if (seg[2] === 'd') {
99
+ hasCivil = true;
100
+ civil += n;
101
+ }
102
+ else {
103
+ hasElapsed = true;
104
+ elapsed += n * (seg[2] === 'h' ? 3600_000 : seg[2] === 'm' ? 60_000 : 1000);
105
+ }
106
+ }
107
+ if (hasCivil && hasElapsed)
108
+ err(`市民時と経過時間の幅は混合できない: ${text}(ADR-28)`);
109
+ const width = hasCivil ? { kind: 'civil', days: civil } : { kind: 'elapsed', ms: elapsed };
110
+ toks.push({ kind: 'width', text, line, col, width });
111
+ i += text.length;
112
+ col += text.length;
113
+ continue;
114
+ }
115
+ // 数値
116
+ const n = /^\d+(?:\.\d+)?/.exec(src.slice(i));
117
+ toks.push({ kind: 'number', text: n[0], line, col, num: +n[0] });
118
+ i += n[0].length;
119
+ col += n[0].length;
120
+ continue;
121
+ }
122
+ // 単独時刻リテラル: Thh:mm(:ss(.f+)?)?(ADR-51——日付部を持たない壁時計時刻)。
123
+ // T 接頭形限定: 裸 hh:mm は三値演算子の合法式(cond ? 10:30)と衝突するため採らない。
124
+ // T\d\d: まで見えたらこの字句の領域(malformed は誘導つき字句エラー——黙って識別子に落とさない)
125
+ if (c === 'T' && /^T\d{2}:/.test(src.slice(i, i + 4))) {
126
+ const m = /^T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?/.exec(src.slice(i));
127
+ if (!m)
128
+ err('単独時刻リテラルの形は Thh:mm(例 T07:00・秒まで書くなら Thh:mm:ss。ADR-51)');
129
+ if (+m[1] > 23 || +m[2] > 59 || (m[3] !== undefined && +m[3] > 59)) {
130
+ err(`時刻が範囲外: ${m[0]}(hh は 00..23・mm/ss は 00..59。うるう秒は表現しない=ADR-33)`);
131
+ }
132
+ const tod = (+m[1] * 3600 + +m[2] * 60 + (m[3] ? +m[3] + (m[4] ? +`0.${m[4]}` : 0) : 0)) * 1000;
133
+ toks.push({ kind: 'time', text: m[0], line, col, tod });
134
+ i += m[0].length;
135
+ col += m[0].length;
136
+ continue;
137
+ }
138
+ // 識別子(Unicode 文字可)。member-key の calendar-system はハイフンを特例で許す。
139
+ if (isLetter(c)) {
140
+ let j = i;
141
+ while (j < src.length && isNamePart(src[j]))
142
+ j++;
143
+ let text = src.slice(i, j);
144
+ if (text === 'calendar' && src.slice(j, j + 7) === '-system') {
145
+ text = 'calendar-system';
146
+ j += 7;
147
+ }
148
+ toks.push({ kind: 'name', text, line, col });
149
+ col += j - i;
150
+ i = j;
151
+ continue;
152
+ }
153
+ // パンクチュエータ
154
+ const p = PUNCTS.find(p => src.startsWith(p, i));
155
+ if (p) {
156
+ if (p === '(' || p === '[')
157
+ parenDepth++;
158
+ if (p === ')' || p === ']')
159
+ parenDepth = Math.max(0, parenDepth - 1);
160
+ toks.push({ kind: 'punct', text: p, line, col });
161
+ i += p.length;
162
+ col += p.length;
163
+ continue;
164
+ }
165
+ if (c === '¥' || c === '¥')
166
+ err('円記号 ¥(U+00A5)は差演算子ではない。バックスラッシュ U+005C を使う(§4.5)');
167
+ err(`不明な文字: ${JSON.stringify(c)}`);
168
+ }
169
+ toks.push({ kind: 'eof', text: '', line, col });
170
+ return toks;
171
+ }
@@ -0,0 +1,11 @@
1
+ import { LexError } from './lexer.ts';
2
+ import type { Token } from './lexer.ts';
3
+ import type { Program, CoveringRange } from './ast.ts';
4
+ export declare class ParseError extends Error {
5
+ constructor(msg: string, tok: Token);
6
+ }
7
+ export declare function parse(src: string): Program;
8
+ /** covering-list 字面の単体パース(external の解決値 wire 用。ADR-46)——
9
+ * 端の含意・年略記・開端・区間リストの意味論をテーブルリテラルと完全共有するための入口 */
10
+ export declare function parseCoveringText(text: string): CoveringRange[];
11
+ export { LexError };