@uwmd/core 1.1.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/README.md +79 -0
- package/dist/agents/bancroft.d.ts +32 -0
- package/dist/agents/bancroft.d.ts.map +1 -0
- package/dist/agents/bancroft.js +292 -0
- package/dist/agents/bancroft.js.map +1 -0
- package/dist/agents/schemas.d.ts +13 -0
- package/dist/agents/schemas.d.ts.map +1 -0
- package/dist/agents/schemas.js +74 -0
- package/dist/agents/schemas.js.map +1 -0
- package/dist/bindings.d.ts +85 -0
- package/dist/bindings.d.ts.map +1 -0
- package/dist/bindings.js +293 -0
- package/dist/bindings.js.map +1 -0
- package/dist/browser.d.ts +66 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +53 -0
- package/dist/browser.js.map +1 -0
- package/dist/calc/builtins.d.ts +4 -0
- package/dist/calc/builtins.d.ts.map +1 -0
- package/dist/calc/builtins.js +329 -0
- package/dist/calc/builtins.js.map +1 -0
- package/dist/calc/dependencies.d.ts +34 -0
- package/dist/calc/dependencies.d.ts.map +1 -0
- package/dist/calc/dependencies.js +101 -0
- package/dist/calc/dependencies.js.map +1 -0
- package/dist/calc/errors.d.ts +8 -0
- package/dist/calc/errors.d.ts.map +1 -0
- package/dist/calc/errors.js +18 -0
- package/dist/calc/errors.js.map +1 -0
- package/dist/calc/evaluator.d.ts +5 -0
- package/dist/calc/evaluator.d.ts.map +1 -0
- package/dist/calc/evaluator.js +184 -0
- package/dist/calc/evaluator.js.map +1 -0
- package/dist/calc/index.d.ts +14 -0
- package/dist/calc/index.d.ts.map +1 -0
- package/dist/calc/index.js +56 -0
- package/dist/calc/index.js.map +1 -0
- package/dist/calc/parser.d.ts +32 -0
- package/dist/calc/parser.d.ts.map +1 -0
- package/dist/calc/parser.js +361 -0
- package/dist/calc/parser.js.map +1 -0
- package/dist/cascade.d.ts +85 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +172 -0
- package/dist/cascade.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +843 -0
- package/dist/cli.js.map +1 -0
- package/dist/codec.d.ts +25 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +62 -0
- package/dist/codec.js.map +1 -0
- package/dist/codecs.d.ts +6 -0
- package/dist/codecs.d.ts.map +1 -0
- package/dist/codecs.js +12 -0
- package/dist/codecs.js.map +1 -0
- package/dist/compactor.d.ts +11 -0
- package/dist/compactor.d.ts.map +1 -0
- package/dist/compactor.js +84 -0
- package/dist/compactor.js.map +1 -0
- package/dist/context-profiles.d.ts +30 -0
- package/dist/context-profiles.d.ts.map +1 -0
- package/dist/context-profiles.js +225 -0
- package/dist/context-profiles.js.map +1 -0
- package/dist/context.d.ts +55 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +288 -0
- package/dist/context.js.map +1 -0
- package/dist/dcf.d.ts +9 -0
- package/dist/dcf.d.ts.map +1 -0
- package/dist/dcf.js +75 -0
- package/dist/dcf.js.map +1 -0
- package/dist/debt.d.ts +11 -0
- package/dist/debt.d.ts.map +1 -0
- package/dist/debt.js +85 -0
- package/dist/debt.js.map +1 -0
- package/dist/defaults.d.ts +104 -0
- package/dist/defaults.d.ts.map +1 -0
- package/dist/defaults.js +622 -0
- package/dist/defaults.js.map +1 -0
- package/dist/derived.d.ts +17 -0
- package/dist/derived.d.ts.map +1 -0
- package/dist/derived.js +29 -0
- package/dist/derived.js.map +1 -0
- package/dist/editor.d.ts +76 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +502 -0
- package/dist/editor.js.map +1 -0
- package/dist/envelope.d.ts +51 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +213 -0
- package/dist/envelope.js.map +1 -0
- package/dist/format.d.ts +60 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +115 -0
- package/dist/format.js.map +1 -0
- package/dist/gaps.d.ts +62 -0
- package/dist/gaps.d.ts.map +1 -0
- package/dist/gaps.js +272 -0
- package/dist/gaps.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +169 -0
- package/dist/init.js.map +1 -0
- package/dist/integrity-canonical.d.ts +17 -0
- package/dist/integrity-canonical.d.ts.map +1 -0
- package/dist/integrity-canonical.js +124 -0
- package/dist/integrity-canonical.js.map +1 -0
- package/dist/integrity.d.ts +54 -0
- package/dist/integrity.d.ts.map +1 -0
- package/dist/integrity.js +244 -0
- package/dist/integrity.js.map +1 -0
- package/dist/modules.d.ts +24 -0
- package/dist/modules.d.ts.map +1 -0
- package/dist/modules.js +322 -0
- package/dist/modules.js.map +1 -0
- package/dist/negotiation.d.ts +19 -0
- package/dist/negotiation.d.ts.map +1 -0
- package/dist/negotiation.js +70 -0
- package/dist/negotiation.js.map +1 -0
- package/dist/opstatement.d.ts +9 -0
- package/dist/opstatement.d.ts.map +1 -0
- package/dist/opstatement.js +80 -0
- package/dist/opstatement.js.map +1 -0
- package/dist/packs/excel-emit.d.ts +21 -0
- package/dist/packs/excel-emit.d.ts.map +1 -0
- package/dist/packs/excel-emit.js +135 -0
- package/dist/packs/excel-emit.js.map +1 -0
- package/dist/packs/index.d.ts +13 -0
- package/dist/packs/index.d.ts.map +1 -0
- package/dist/packs/index.js +37 -0
- package/dist/packs/index.js.map +1 -0
- package/dist/packs/industrial.d.ts +3 -0
- package/dist/packs/industrial.d.ts.map +1 -0
- package/dist/packs/industrial.js +115 -0
- package/dist/packs/industrial.js.map +1 -0
- package/dist/packs/multifamily.d.ts +3 -0
- package/dist/packs/multifamily.d.ts.map +1 -0
- package/dist/packs/multifamily.js +82 -0
- package/dist/packs/multifamily.js.map +1 -0
- package/dist/packs/office.d.ts +3 -0
- package/dist/packs/office.d.ts.map +1 -0
- package/dist/packs/office.js +108 -0
- package/dist/packs/office.js.map +1 -0
- package/dist/packs/retail.d.ts +3 -0
- package/dist/packs/retail.d.ts.map +1 -0
- package/dist/packs/retail.js +115 -0
- package/dist/packs/retail.js.map +1 -0
- package/dist/packs/self-storage.d.ts +3 -0
- package/dist/packs/self-storage.d.ts.map +1 -0
- package/dist/packs/self-storage.js +105 -0
- package/dist/packs/self-storage.js.map +1 -0
- package/dist/parser.d.ts +15 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +430 -0
- package/dist/parser.js.map +1 -0
- package/dist/protocol.d.ts +389 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +714 -0
- package/dist/protocol.js.map +1 -0
- package/dist/refinement.d.ts +63 -0
- package/dist/refinement.d.ts.map +1 -0
- package/dist/refinement.js +242 -0
- package/dist/refinement.js.map +1 -0
- package/dist/renderer.d.ts +22 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +478 -0
- package/dist/renderer.js.map +1 -0
- package/dist/rentroll.d.ts +21 -0
- package/dist/rentroll.d.ts.map +1 -0
- package/dist/rentroll.js +256 -0
- package/dist/rentroll.js.map +1 -0
- package/dist/report.d.ts +29 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +924 -0
- package/dist/report.js.map +1 -0
- package/dist/runner.d.ts +43 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +288 -0
- package/dist/runner.js.map +1 -0
- package/dist/sourcesuses.d.ts +14 -0
- package/dist/sourcesuses.d.ts.map +1 -0
- package/dist/sourcesuses.js +63 -0
- package/dist/sourcesuses.js.map +1 -0
- package/dist/types.d.ts +248 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/dist/uwcsv.d.ts +23 -0
- package/dist/uwcsv.d.ts.map +1 -0
- package/dist/uwcsv.js +877 -0
- package/dist/uwcsv.js.map +1 -0
- package/dist/uwjson.d.ts +19 -0
- package/dist/uwjson.d.ts.map +1 -0
- package/dist/uwjson.js +61 -0
- package/dist/uwjson.js.map +1 -0
- package/dist/uwxml.d.ts +18 -0
- package/dist/uwxml.d.ts.map +1 -0
- package/dist/uwxml.js +297 -0
- package/dist/uwxml.js.map +1 -0
- package/dist/validator.d.ts +35 -0
- package/dist/validator.d.ts.map +1 -0
- package/dist/validator.js +553 -0
- package/dist/validator.js.map +1 -0
- package/dist/valuation.d.ts +9 -0
- package/dist/valuation.d.ts.map +1 -0
- package/dist/valuation.js +34 -0
- package/dist/valuation.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Tier-3 Calc Host — AST evaluator.
|
|
2
|
+
// Variable resolution per §VIII.2; null propagation per §VIII.2.
|
|
3
|
+
import { getSection } from '../parser.js';
|
|
4
|
+
import { BUILTINS } from './builtins.js';
|
|
5
|
+
import { CalcError } from './errors.js';
|
|
6
|
+
const MAX_NODES = 1024;
|
|
7
|
+
export function evaluate(expr, ctx) {
|
|
8
|
+
const state = { nodes: 0 };
|
|
9
|
+
return evalNode(expr, ctx, state);
|
|
10
|
+
}
|
|
11
|
+
function evalNode(expr, ctx, state) {
|
|
12
|
+
if (++state.nodes > MAX_NODES) {
|
|
13
|
+
throw new CalcError('CALC-LIMIT-001', `Expression exceeds ${MAX_NODES} AST nodes.`);
|
|
14
|
+
}
|
|
15
|
+
switch (expr.kind) {
|
|
16
|
+
case 'literal':
|
|
17
|
+
return expr.value;
|
|
18
|
+
case 'ident':
|
|
19
|
+
return resolveIdentifier(expr.name, ctx);
|
|
20
|
+
case 'path': {
|
|
21
|
+
const head = resolveIdentifier(expr.head, ctx);
|
|
22
|
+
if (head === null || head === undefined)
|
|
23
|
+
return null;
|
|
24
|
+
let cur = head;
|
|
25
|
+
for (const seg of expr.segments) {
|
|
26
|
+
if (cur === null || cur === undefined)
|
|
27
|
+
return null;
|
|
28
|
+
cur = cur[seg];
|
|
29
|
+
}
|
|
30
|
+
return coerceCalcValue(cur);
|
|
31
|
+
}
|
|
32
|
+
case 'call': {
|
|
33
|
+
const fn = BUILTINS[expr.name];
|
|
34
|
+
if (!fn) {
|
|
35
|
+
throw new CalcError('CALC-RESOLVE-001', `Unknown function '${expr.name}'.`);
|
|
36
|
+
}
|
|
37
|
+
const args = expr.args.map((a) => evalNode(a, ctx, state));
|
|
38
|
+
return fn(args);
|
|
39
|
+
}
|
|
40
|
+
case 'unary': {
|
|
41
|
+
const v = evalNode(expr.operand, ctx, state);
|
|
42
|
+
if (v === null)
|
|
43
|
+
return null;
|
|
44
|
+
if (expr.op === '-') {
|
|
45
|
+
if (typeof v !== 'number') {
|
|
46
|
+
throw new CalcError('CALC-TYPE-001', `Unary '-' requires number, got ${typeof v}.`);
|
|
47
|
+
}
|
|
48
|
+
return -v;
|
|
49
|
+
}
|
|
50
|
+
// !
|
|
51
|
+
if (typeof v !== 'boolean') {
|
|
52
|
+
throw new CalcError('CALC-TYPE-001', `Unary '!' requires boolean, got ${typeof v}.`);
|
|
53
|
+
}
|
|
54
|
+
return !v;
|
|
55
|
+
}
|
|
56
|
+
case 'binary': {
|
|
57
|
+
// Logical operators short-circuit and propagate null. The right side is
|
|
58
|
+
// only evaluated if the left side does not already determine the answer.
|
|
59
|
+
if (expr.op === '&&' || expr.op === '||') {
|
|
60
|
+
const left = evalNode(expr.left, ctx, state);
|
|
61
|
+
if (left === null)
|
|
62
|
+
return null;
|
|
63
|
+
if (typeof left !== 'boolean') {
|
|
64
|
+
throw new CalcError('CALC-TYPE-001', `Operator '${expr.op}' requires booleans, got ${typeof left} on left.`);
|
|
65
|
+
}
|
|
66
|
+
// Short-circuit: false && _ = false; true || _ = true.
|
|
67
|
+
if (expr.op === '&&' && left === false)
|
|
68
|
+
return false;
|
|
69
|
+
if (expr.op === '||' && left === true)
|
|
70
|
+
return true;
|
|
71
|
+
const right = evalNode(expr.right, ctx, state);
|
|
72
|
+
if (right === null)
|
|
73
|
+
return null;
|
|
74
|
+
if (typeof right !== 'boolean') {
|
|
75
|
+
throw new CalcError('CALC-TYPE-001', `Operator '${expr.op}' requires booleans, got ${typeof right} on right.`);
|
|
76
|
+
}
|
|
77
|
+
return right;
|
|
78
|
+
}
|
|
79
|
+
return evalBinary(expr.op, evalNode(expr.left, ctx, state), evalNode(expr.right, ctx, state));
|
|
80
|
+
}
|
|
81
|
+
case 'cond': {
|
|
82
|
+
const test = evalNode(expr.test, ctx, state);
|
|
83
|
+
if (test === null)
|
|
84
|
+
return null;
|
|
85
|
+
if (typeof test !== 'boolean') {
|
|
86
|
+
throw new CalcError('CALC-TYPE-001', `Ternary condition must be boolean, got ${typeof test}.`);
|
|
87
|
+
}
|
|
88
|
+
return test ? evalNode(expr.consequent, ctx, state) : evalNode(expr.else, ctx, state);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ─── Variable resolution ──────────────────────────────────────────────────────
|
|
93
|
+
function resolveIdentifier(name, ctx) {
|
|
94
|
+
const fm = ctx.parsed.frontmatter;
|
|
95
|
+
if (fm && Object.prototype.hasOwnProperty.call(fm, name)) {
|
|
96
|
+
return coerceCalcValue(fm[name]);
|
|
97
|
+
}
|
|
98
|
+
const section = getSection(ctx.parsed, name);
|
|
99
|
+
if (section) {
|
|
100
|
+
// Per §VIII.2: identifier maps to the canonical block's content (user data
|
|
101
|
+
// inside the JSON envelope). The parser stores the full envelope on
|
|
102
|
+
// block.content; the user-facing data lives at block.content.content.
|
|
103
|
+
const envelope = section.content;
|
|
104
|
+
const inner = envelope && typeof envelope === 'object' && 'content' in envelope
|
|
105
|
+
? envelope.content
|
|
106
|
+
: envelope;
|
|
107
|
+
return coerceCalcValue(inner);
|
|
108
|
+
}
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(ctx.prior_results, name)) {
|
|
110
|
+
return ctx.prior_results[name] ?? null;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
function coerceCalcValue(v) {
|
|
115
|
+
if (v === null || v === undefined)
|
|
116
|
+
return null;
|
|
117
|
+
if (typeof v === 'number' || typeof v === 'string' || typeof v === 'boolean')
|
|
118
|
+
return v;
|
|
119
|
+
// Objects/arrays remain accessible via path traversal — return them as-is so
|
|
120
|
+
// the caller (path walker) can drill in. Convert to null for terminal use.
|
|
121
|
+
return v;
|
|
122
|
+
}
|
|
123
|
+
// ─── Binary operator evaluation ───────────────────────────────────────────────
|
|
124
|
+
function evalBinary(op, l, r) {
|
|
125
|
+
switch (op) {
|
|
126
|
+
case '+':
|
|
127
|
+
if (l === null || r === null)
|
|
128
|
+
return null;
|
|
129
|
+
if (typeof l === 'number' && typeof r === 'number')
|
|
130
|
+
return l + r;
|
|
131
|
+
if (typeof l === 'string' && typeof r === 'string')
|
|
132
|
+
return l + r;
|
|
133
|
+
throw new CalcError('CALC-TYPE-001', `'+' requires number+number or string+string.`);
|
|
134
|
+
case '-':
|
|
135
|
+
case '*':
|
|
136
|
+
case '/':
|
|
137
|
+
case '%': {
|
|
138
|
+
if (l === null || r === null)
|
|
139
|
+
return null;
|
|
140
|
+
if (typeof l !== 'number' || typeof r !== 'number') {
|
|
141
|
+
throw new CalcError('CALC-TYPE-001', `Operator '${op}' requires numbers.`);
|
|
142
|
+
}
|
|
143
|
+
if ((op === '/' || op === '%') && r === 0) {
|
|
144
|
+
throw new CalcError('CALC-DIV-ZERO', 'Division by zero.');
|
|
145
|
+
}
|
|
146
|
+
if (op === '-')
|
|
147
|
+
return l - r;
|
|
148
|
+
if (op === '*')
|
|
149
|
+
return l * r;
|
|
150
|
+
if (op === '/')
|
|
151
|
+
return l / r;
|
|
152
|
+
return l - Math.trunc(l / r) * r; // %
|
|
153
|
+
}
|
|
154
|
+
case '==': return strictEqual(l, r);
|
|
155
|
+
case '!=': return !strictEqual(l, r);
|
|
156
|
+
case '<':
|
|
157
|
+
case '<=':
|
|
158
|
+
case '>':
|
|
159
|
+
case '>=': {
|
|
160
|
+
if (l === null || r === null)
|
|
161
|
+
return null;
|
|
162
|
+
if (typeof l !== typeof r) {
|
|
163
|
+
throw new CalcError('CALC-TYPE-001', `Comparison '${op}' requires same types.`);
|
|
164
|
+
}
|
|
165
|
+
if (typeof l !== 'number' && typeof l !== 'string') {
|
|
166
|
+
throw new CalcError('CALC-TYPE-001', `Comparison '${op}' requires numbers or strings.`);
|
|
167
|
+
}
|
|
168
|
+
if (op === '<')
|
|
169
|
+
return l < r;
|
|
170
|
+
if (op === '<=')
|
|
171
|
+
return l <= r;
|
|
172
|
+
if (op === '>')
|
|
173
|
+
return l > r;
|
|
174
|
+
return l >= r;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw new CalcError('CALC-PARSE-001', `Unknown binary operator '${op}'.`);
|
|
178
|
+
}
|
|
179
|
+
function strictEqual(a, b) {
|
|
180
|
+
if (a === null || b === null)
|
|
181
|
+
return a === b;
|
|
182
|
+
return a === b;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluator.js","sourceRoot":"","sources":["../../src/calc/evaluator.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,iEAAiE;AAEjE,OAAO,EAAW,UAAU,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,QAAQ,EAAkB,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,SAAS,GAAG,IAAI,CAAC;AAMvB,MAAM,UAAU,QAAQ,CAAC,IAAU,EAAE,GAA0B;IAC7D,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IACtC,OAAO,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,QAAQ,CAAC,IAAU,EAAE,GAA0B,EAAE,KAAgB;IACxE,IAAI,EAAE,KAAK,CAAC,KAAK,GAAG,SAAS,EAAE,CAAC;QAC9B,MAAM,IAAI,SAAS,CAAC,gBAAgB,EAAE,sBAAsB,SAAS,aAAa,CAAC,CAAC;IACtF,CAAC;IAED,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,IAAI,CAAC,KAAK,CAAC;QAEpB,KAAK,OAAO;YACV,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAE3C,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAC/C,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS;gBAAE,OAAO,IAAI,CAAC;YACrD,IAAI,GAAG,GAAY,IAAI,CAAC;YACxB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,SAAS;oBAAE,OAAO,IAAI,CAAC;gBACnD,GAAG,GAAI,GAA+B,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QAED,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,SAAS,CAAC,kBAAkB,EAAE,qBAAqB,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3D,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7C,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC5B,IAAI,IAAI,CAAC,EAAE,KAAK,GAAG,EAAE,CAAC;gBACpB,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;oBAC1B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,kCAAkC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACtF,CAAC;gBACD,OAAO,CAAC,CAAC,CAAC;YACZ,CAAC;YACD,IAAI;YACJ,IAAI,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC3B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,mCAAmC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvF,CAAC;YACD,OAAO,CAAC,CAAC,CAAC;QACZ,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,wEAAwE;YACxE,yEAAyE;YACzE,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;gBACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC7C,IAAI,IAAI,KAAK,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAC/B,IAAI,OAAO,IAAI,KAAK,SAAS,EAAE,CAAC;oBAC9B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,aAAa,IAAI,CAAC,EAAE,4BAA4B,OAAO,IAAI,WAAW,CAAC,CAAC;gBAC/G,CAAC;gBACD,uDAAuD;gBACvD,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK;oBAAE,OAAO,KAAK,CAAC;gBACrD,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI;oBAAE,OAAO,IAAI,CAAC;gBACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC/C,IAAI,KAAK,KAAK,IAAI;oBAAE,OAAO,IAAI,CAAC;gBAChC,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;oBAC/B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,aAAa,IAAI,CAAC,EAAE,4BAA4B,OAAO,KAAK,YAAY,CAAC,CAAC;gBACjH,CAAC;gBACD,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAChG,CAAC;QAED,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;YAC7C,IAAI,IAAI,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC/B,IAAI,OAAO,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,0CAA0C,OAAO,IAAI,GAAG,CAAC,CAAC;YACjG,CAAC;YACD,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;AACH,CAAC;AAED,iFAAiF;AAEjF,SAAS,iBAAiB,CAAC,IAAY,EAAE,GAA0B;IACjE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,WAAiD,CAAC;IACxE,IAAI,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QACzD,OAAO,eAAe,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7C,IAAI,OAAO,EAAE,CAAC;QACZ,2EAA2E;QAC3E,oEAAoE;QACpE,sEAAsE;QACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAqD,CAAC;QAC/E,MAAM,KAAK,GAAG,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,SAAS,IAAI,QAAQ;YAC7E,CAAC,CAAC,QAAQ,CAAC,OAAO;YAClB,CAAC,CAAC,QAAQ,CAAC;QACb,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,EAAE,CAAC;QAClE,OAAO,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACzC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,CAAU;IACjC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IACvF,6EAA6E;IAC7E,2EAA2E;IAC3E,OAAO,CAAyB,CAAC;AACnC,CAAC;AAED,iFAAiF;AAEjF,SAAS,UAAU,CAAC,EAAU,EAAE,CAAY,EAAE,CAAY;IACxD,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,GAAG;YACN,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YACjE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YACjE,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,8CAA8C,CAAC,CAAC;QAEvF,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,CAAC;QACT,KAAK,GAAG,CAAC,CAAC,CAAC;YACT,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnD,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,aAAa,EAAE,qBAAqB,CAAC,CAAC;YAC7E,CAAC;YACD,IAAI,CAAC,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC1C,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI;QACxC,CAAC;QAED,KAAK,IAAI,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAErC,KAAK,GAAG,CAAC;QACT,KAAK,IAAI,CAAC;QACV,KAAK,GAAG,CAAC;QACT,KAAK,IAAI,CAAC,CAAC,CAAC;YACV,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC;YAC1C,IAAI,OAAO,CAAC,KAAK,OAAO,CAAC,EAAE,CAAC;gBAC1B,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,eAAe,EAAE,wBAAwB,CAAC,CAAC;YAClF,CAAC;YACD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACnD,MAAM,IAAI,SAAS,CAAC,eAAe,EAAE,eAAe,EAAE,gCAAgC,CAAC,CAAC;YAC1F,CAAC;YACD,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAQ,CAAqB,GAAI,CAAqB,CAAC;YACvE,IAAI,EAAE,KAAK,IAAI;gBAAE,OAAQ,CAAqB,IAAK,CAAqB,CAAC;YACzE,IAAI,EAAE,KAAK,GAAG;gBAAE,OAAQ,CAAqB,GAAI,CAAqB,CAAC;YACvE,OAAQ,CAAqB,IAAK,CAAqB,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,MAAM,IAAI,SAAS,CAAC,gBAAgB,EAAE,4BAA4B,EAAE,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,WAAW,CAAC,CAAY,EAAE,CAAY;IAC7C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CalcEvaluationContext, CalcResult, ModuleCalcDecl } from '../protocol.js';
|
|
2
|
+
export { parseExpression } from './parser.js';
|
|
3
|
+
export { evaluate } from './evaluator.js';
|
|
4
|
+
export { BUILTINS } from './builtins.js';
|
|
5
|
+
export type { CalcValue, Builtin } from './builtins.js';
|
|
6
|
+
export { CalcError, calcError } from './errors.js';
|
|
7
|
+
export type { CalcErrorCode } from './errors.js';
|
|
8
|
+
/**
|
|
9
|
+
* Evaluate a single calculation declaration against a context.
|
|
10
|
+
* Always returns a CalcResult — exceptions from the engine are captured
|
|
11
|
+
* into the `error` field, never thrown.
|
|
12
|
+
*/
|
|
13
|
+
export declare function evaluateCalc(decl: ModuleCalcDecl, ctx: CalcEvaluationContext): CalcResult;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/calc/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAKxF,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,qBAAqB,GAAG,UAAU,CAwBzF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Tier-3 Calc Host — public entry point.
|
|
2
|
+
// `evaluateCalc()` parses a calc declaration's formula, resolves variables
|
|
3
|
+
// against the CalcEvaluationContext, and returns a CalcResult per §VIII.
|
|
4
|
+
import { formatCurrency, formatPercent, formatRatio, formatValue } from '../format.js';
|
|
5
|
+
import { CalcError } from './errors.js';
|
|
6
|
+
import { evaluate } from './evaluator.js';
|
|
7
|
+
import { parseExpression } from './parser.js';
|
|
8
|
+
export { parseExpression } from './parser.js';
|
|
9
|
+
export { evaluate } from './evaluator.js';
|
|
10
|
+
export { BUILTINS } from './builtins.js';
|
|
11
|
+
export { CalcError, calcError } from './errors.js';
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate a single calculation declaration against a context.
|
|
14
|
+
* Always returns a CalcResult — exceptions from the engine are captured
|
|
15
|
+
* into the `error` field, never thrown.
|
|
16
|
+
*/
|
|
17
|
+
export function evaluateCalc(decl, ctx) {
|
|
18
|
+
try {
|
|
19
|
+
const ast = parseExpression(decl.formula);
|
|
20
|
+
const value = evaluate(ast, ctx);
|
|
21
|
+
return {
|
|
22
|
+
calc_id: decl.id,
|
|
23
|
+
ok: true,
|
|
24
|
+
value: value,
|
|
25
|
+
...(decl.unit ? { unit: decl.unit } : {}),
|
|
26
|
+
display: formatForDisplay(value, decl.unit),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
const proto = e instanceof CalcError
|
|
31
|
+
? e.proto
|
|
32
|
+
: { category: 'calc', code: 'CALC-PARSE-001', message: e instanceof Error ? e.message : String(e) };
|
|
33
|
+
return {
|
|
34
|
+
calc_id: decl.id,
|
|
35
|
+
ok: false,
|
|
36
|
+
value: null,
|
|
37
|
+
...(decl.unit ? { unit: decl.unit } : {}),
|
|
38
|
+
error: proto,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function formatForDisplay(value, unit) {
|
|
43
|
+
if (value === null || value === undefined)
|
|
44
|
+
return formatValue(null);
|
|
45
|
+
if (typeof value !== 'number')
|
|
46
|
+
return String(value);
|
|
47
|
+
switch (unit) {
|
|
48
|
+
case '%': return formatPercent(value);
|
|
49
|
+
case '$': return formatCurrency(value);
|
|
50
|
+
case 'x': return formatRatio(value);
|
|
51
|
+
default:
|
|
52
|
+
// Fall back to a fixed-decimal display.
|
|
53
|
+
return Number.isInteger(value) ? value.toString() : value.toFixed(4);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/calc/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,2EAA2E;AAC3E,yEAAyE;AAEzE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGnD;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAoB,EAAE,GAA0B;IAC3E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAEjC,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,EAAE,EAAE,IAAI;YACR,KAAK,EAAE,KAAyC;YAChD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;SAC5C,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,KAAK,GAAG,CAAC,YAAY,SAAS;YAClC,CAAC,CAAC,CAAC,CAAC,KAAK;YACT,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/G,OAAO;YACL,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,IAAI;YACX,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzC,KAAK,EAAE,KAAK;SACb,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc,EAAE,IAAa;IACrD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG,CAAC,CAAC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC;QACtC,KAAK,GAAG,CAAC,CAAC,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;QACvC,KAAK,GAAG,CAAC,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;QACpC;YACE,wCAAwC;YACxC,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type Expr = {
|
|
2
|
+
kind: 'literal';
|
|
3
|
+
value: number | string | boolean | null;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'ident';
|
|
6
|
+
name: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: 'path';
|
|
9
|
+
head: string;
|
|
10
|
+
segments: string[];
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'call';
|
|
13
|
+
name: string;
|
|
14
|
+
args: Expr[];
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'unary';
|
|
17
|
+
op: '-' | '!';
|
|
18
|
+
operand: Expr;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'binary';
|
|
21
|
+
op: BinaryOp;
|
|
22
|
+
left: Expr;
|
|
23
|
+
right: Expr;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'cond';
|
|
26
|
+
test: Expr;
|
|
27
|
+
consequent: Expr;
|
|
28
|
+
else: Expr;
|
|
29
|
+
};
|
|
30
|
+
export type BinaryOp = '+' | '-' | '*' | '/' | '%' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||';
|
|
31
|
+
export declare function parseExpression(input: string): Expr;
|
|
32
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/calc/parser.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,IAAI,GACZ;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,GAClD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,EAAE,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAChB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAC3B,IAAI,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,GAAG,IAAI,GACrC,IAAI,GAAG,IAAI,CAAC;AAsShB,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAGnD"}
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// Tier-3 Calc Host — recursive-descent parser for the safe-expression
|
|
2
|
+
// grammar in UW_PROTOCOL_v1.md §VIII.1. Produces an AST that evaluator.ts
|
|
3
|
+
// walks. Any input that does not parse against the grammar is rejected
|
|
4
|
+
// with CALC-PARSE-001.
|
|
5
|
+
import { CalcError } from './errors.js';
|
|
6
|
+
const _KEYWORDS = new Set(['true', 'false', 'null']);
|
|
7
|
+
// Maximum input length — guards against accidental enormous inputs.
|
|
8
|
+
const MAX_INPUT_LEN = 4096;
|
|
9
|
+
function tokenize(input) {
|
|
10
|
+
if (input.length > MAX_INPUT_LEN) {
|
|
11
|
+
throw new CalcError('CALC-LIMIT-001', `Expression exceeds ${MAX_INPUT_LEN} characters.`);
|
|
12
|
+
}
|
|
13
|
+
const out = [];
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < input.length) {
|
|
16
|
+
const c = input[i];
|
|
17
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
|
18
|
+
i++;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (c >= '0' && c <= '9') {
|
|
22
|
+
const start = i;
|
|
23
|
+
while (i < input.length && input[i] >= '0' && input[i] <= '9')
|
|
24
|
+
i++;
|
|
25
|
+
if (input[i] === '.' && input[i + 1] && input[i + 1] >= '0' && input[i + 1] <= '9') {
|
|
26
|
+
i++;
|
|
27
|
+
while (i < input.length && input[i] >= '0' && input[i] <= '9')
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
out.push({ kind: 'number', value: input.slice(start, i), pos: start });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (c === "'") {
|
|
34
|
+
const start = i;
|
|
35
|
+
i++;
|
|
36
|
+
const sb = [];
|
|
37
|
+
while (i < input.length && input[i] !== "'") {
|
|
38
|
+
sb.push(input[i]);
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
if (input[i] !== "'") {
|
|
42
|
+
throw new CalcError('CALC-PARSE-001', `Unterminated string starting at position ${start}.`);
|
|
43
|
+
}
|
|
44
|
+
i++;
|
|
45
|
+
out.push({ kind: 'string', value: sb.join(''), pos: start });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_') {
|
|
49
|
+
const start = i;
|
|
50
|
+
while (i < input.length) {
|
|
51
|
+
const k = input[i];
|
|
52
|
+
if ((k >= 'A' && k <= 'Z') || (k >= 'a' && k <= 'z') || (k >= '0' && k <= '9') || k === '_')
|
|
53
|
+
i++;
|
|
54
|
+
else
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
const text = input.slice(start, i);
|
|
58
|
+
if (text === 'true' || text === 'false')
|
|
59
|
+
out.push({ kind: 'bool', value: text, pos: start });
|
|
60
|
+
else if (text === 'null')
|
|
61
|
+
out.push({ kind: 'null', value: text, pos: start });
|
|
62
|
+
else
|
|
63
|
+
out.push({ kind: 'ident', value: text, pos: start });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Two-char operators first.
|
|
67
|
+
const two = input.slice(i, i + 2);
|
|
68
|
+
if (two === '==') {
|
|
69
|
+
out.push({ kind: 'eq', value: two, pos: i });
|
|
70
|
+
i += 2;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (two === '!=') {
|
|
74
|
+
out.push({ kind: 'neq', value: two, pos: i });
|
|
75
|
+
i += 2;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (two === '<=') {
|
|
79
|
+
out.push({ kind: 'lte', value: two, pos: i });
|
|
80
|
+
i += 2;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (two === '>=') {
|
|
84
|
+
out.push({ kind: 'gte', value: two, pos: i });
|
|
85
|
+
i += 2;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (two === '&&') {
|
|
89
|
+
out.push({ kind: 'amp_amp', value: two, pos: i });
|
|
90
|
+
i += 2;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (two === '||') {
|
|
94
|
+
out.push({ kind: 'pipe_pipe', value: two, pos: i });
|
|
95
|
+
i += 2;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
switch (c) {
|
|
99
|
+
case '(':
|
|
100
|
+
out.push({ kind: 'lparen', value: c, pos: i });
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
case ')':
|
|
104
|
+
out.push({ kind: 'rparen', value: c, pos: i });
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
case '[':
|
|
108
|
+
out.push({ kind: 'lbracket', value: c, pos: i });
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
case ']':
|
|
112
|
+
out.push({ kind: 'rbracket', value: c, pos: i });
|
|
113
|
+
i++;
|
|
114
|
+
continue;
|
|
115
|
+
case ',':
|
|
116
|
+
out.push({ kind: 'comma', value: c, pos: i });
|
|
117
|
+
i++;
|
|
118
|
+
continue;
|
|
119
|
+
case '.':
|
|
120
|
+
out.push({ kind: 'dot', value: c, pos: i });
|
|
121
|
+
i++;
|
|
122
|
+
continue;
|
|
123
|
+
case '?':
|
|
124
|
+
out.push({ kind: 'qmark', value: c, pos: i });
|
|
125
|
+
i++;
|
|
126
|
+
continue;
|
|
127
|
+
case ':':
|
|
128
|
+
out.push({ kind: 'colon', value: c, pos: i });
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
case '+':
|
|
132
|
+
out.push({ kind: 'plus', value: c, pos: i });
|
|
133
|
+
i++;
|
|
134
|
+
continue;
|
|
135
|
+
case '-':
|
|
136
|
+
out.push({ kind: 'minus', value: c, pos: i });
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
case '*':
|
|
140
|
+
out.push({ kind: 'star', value: c, pos: i });
|
|
141
|
+
i++;
|
|
142
|
+
continue;
|
|
143
|
+
case '/':
|
|
144
|
+
out.push({ kind: 'slash', value: c, pos: i });
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
case '%':
|
|
148
|
+
out.push({ kind: 'percent', value: c, pos: i });
|
|
149
|
+
i++;
|
|
150
|
+
continue;
|
|
151
|
+
case '!':
|
|
152
|
+
out.push({ kind: 'bang', value: c, pos: i });
|
|
153
|
+
i++;
|
|
154
|
+
continue;
|
|
155
|
+
case '<':
|
|
156
|
+
out.push({ kind: 'lt', value: c, pos: i });
|
|
157
|
+
i++;
|
|
158
|
+
continue;
|
|
159
|
+
case '>':
|
|
160
|
+
out.push({ kind: 'gt', value: c, pos: i });
|
|
161
|
+
i++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
throw new CalcError('CALC-PARSE-001', `Unexpected character '${c}' at position ${i}.`);
|
|
165
|
+
}
|
|
166
|
+
out.push({ kind: 'eof', value: '', pos: input.length });
|
|
167
|
+
return out;
|
|
168
|
+
}
|
|
169
|
+
// ─── Parser ───────────────────────────────────────────────────────────────────
|
|
170
|
+
class Parser {
|
|
171
|
+
tokens;
|
|
172
|
+
pos = 0;
|
|
173
|
+
constructor(tokens) {
|
|
174
|
+
this.tokens = tokens;
|
|
175
|
+
}
|
|
176
|
+
parse() {
|
|
177
|
+
const expr = this.parseExpr();
|
|
178
|
+
this.expect('eof', 'Expected end of expression');
|
|
179
|
+
return expr;
|
|
180
|
+
}
|
|
181
|
+
peek() { return this.tokens[this.pos]; }
|
|
182
|
+
advance() { return this.tokens[this.pos++]; }
|
|
183
|
+
match(...kinds) {
|
|
184
|
+
return kinds.includes(this.peek().kind);
|
|
185
|
+
}
|
|
186
|
+
expect(kind, msg) {
|
|
187
|
+
const tok = this.peek();
|
|
188
|
+
if (tok.kind !== kind) {
|
|
189
|
+
throw new CalcError('CALC-PARSE-001', `${msg} (got '${tok.value || tok.kind}' at ${tok.pos}).`);
|
|
190
|
+
}
|
|
191
|
+
return this.advance();
|
|
192
|
+
}
|
|
193
|
+
// expr := conditional
|
|
194
|
+
parseExpr() {
|
|
195
|
+
return this.parseConditional();
|
|
196
|
+
}
|
|
197
|
+
// conditional := logicalOr ( "?" expr ":" expr )?
|
|
198
|
+
parseConditional() {
|
|
199
|
+
const test = this.parseLogicalOr();
|
|
200
|
+
if (this.match('qmark')) {
|
|
201
|
+
this.advance();
|
|
202
|
+
const consequent = this.parseExpr();
|
|
203
|
+
this.expect('colon', "Expected ':' in ternary");
|
|
204
|
+
const alternate = this.parseExpr();
|
|
205
|
+
return { kind: 'cond', test, consequent, else: alternate };
|
|
206
|
+
}
|
|
207
|
+
return test;
|
|
208
|
+
}
|
|
209
|
+
// logicalOr := logicalAnd ( "||" logicalAnd )*
|
|
210
|
+
parseLogicalOr() {
|
|
211
|
+
let left = this.parseLogicalAnd();
|
|
212
|
+
while (this.match('pipe_pipe')) {
|
|
213
|
+
this.advance();
|
|
214
|
+
const right = this.parseLogicalAnd();
|
|
215
|
+
left = { kind: 'binary', op: '||', left, right };
|
|
216
|
+
}
|
|
217
|
+
return left;
|
|
218
|
+
}
|
|
219
|
+
// logicalAnd := comparison ( "&&" comparison )*
|
|
220
|
+
parseLogicalAnd() {
|
|
221
|
+
let left = this.parseComparison();
|
|
222
|
+
while (this.match('amp_amp')) {
|
|
223
|
+
this.advance();
|
|
224
|
+
const right = this.parseComparison();
|
|
225
|
+
left = { kind: 'binary', op: '&&', left, right };
|
|
226
|
+
}
|
|
227
|
+
return left;
|
|
228
|
+
}
|
|
229
|
+
// comparison := additive ( ( "==" | "!=" | "<=" | ">=" | "<" | ">" ) additive )?
|
|
230
|
+
parseComparison() {
|
|
231
|
+
const left = this.parseAdditive();
|
|
232
|
+
const tok = this.peek();
|
|
233
|
+
let op = null;
|
|
234
|
+
switch (tok.kind) {
|
|
235
|
+
case 'eq':
|
|
236
|
+
op = '==';
|
|
237
|
+
break;
|
|
238
|
+
case 'neq':
|
|
239
|
+
op = '!=';
|
|
240
|
+
break;
|
|
241
|
+
case 'lte':
|
|
242
|
+
op = '<=';
|
|
243
|
+
break;
|
|
244
|
+
case 'gte':
|
|
245
|
+
op = '>=';
|
|
246
|
+
break;
|
|
247
|
+
case 'lt':
|
|
248
|
+
op = '<';
|
|
249
|
+
break;
|
|
250
|
+
case 'gt':
|
|
251
|
+
op = '>';
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
if (op !== null) {
|
|
255
|
+
this.advance();
|
|
256
|
+
const right = this.parseAdditive();
|
|
257
|
+
return { kind: 'binary', op, left, right };
|
|
258
|
+
}
|
|
259
|
+
return left;
|
|
260
|
+
}
|
|
261
|
+
// additive := multiplicative ( ("+" | "-") multiplicative )*
|
|
262
|
+
parseAdditive() {
|
|
263
|
+
let left = this.parseMultiplicative();
|
|
264
|
+
while (this.match('plus', 'minus')) {
|
|
265
|
+
const op = this.advance().kind === 'plus' ? '+' : '-';
|
|
266
|
+
const right = this.parseMultiplicative();
|
|
267
|
+
left = { kind: 'binary', op, left, right };
|
|
268
|
+
}
|
|
269
|
+
return left;
|
|
270
|
+
}
|
|
271
|
+
// multiplicative := unary ( ("*" | "/" | "%") unary )*
|
|
272
|
+
parseMultiplicative() {
|
|
273
|
+
let left = this.parseUnary();
|
|
274
|
+
while (this.match('star', 'slash', 'percent')) {
|
|
275
|
+
const t = this.advance().kind;
|
|
276
|
+
const op = t === 'star' ? '*' : t === 'slash' ? '/' : '%';
|
|
277
|
+
const right = this.parseUnary();
|
|
278
|
+
left = { kind: 'binary', op, left, right };
|
|
279
|
+
}
|
|
280
|
+
return left;
|
|
281
|
+
}
|
|
282
|
+
// unary := ( "-" | "!" )? primary
|
|
283
|
+
parseUnary() {
|
|
284
|
+
if (this.match('minus')) {
|
|
285
|
+
this.advance();
|
|
286
|
+
return { kind: 'unary', op: '-', operand: this.parsePrimary() };
|
|
287
|
+
}
|
|
288
|
+
if (this.match('bang')) {
|
|
289
|
+
this.advance();
|
|
290
|
+
return { kind: 'unary', op: '!', operand: this.parsePrimary() };
|
|
291
|
+
}
|
|
292
|
+
return this.parsePrimary();
|
|
293
|
+
}
|
|
294
|
+
// primary := literal | identifier | call | path | "(" expr ")"
|
|
295
|
+
parsePrimary() {
|
|
296
|
+
const tok = this.peek();
|
|
297
|
+
if (tok.kind === 'number') {
|
|
298
|
+
this.advance();
|
|
299
|
+
return { kind: 'literal', value: Number.parseFloat(tok.value) };
|
|
300
|
+
}
|
|
301
|
+
if (tok.kind === 'string') {
|
|
302
|
+
this.advance();
|
|
303
|
+
return { kind: 'literal', value: tok.value };
|
|
304
|
+
}
|
|
305
|
+
if (tok.kind === 'bool') {
|
|
306
|
+
this.advance();
|
|
307
|
+
return { kind: 'literal', value: tok.value === 'true' };
|
|
308
|
+
}
|
|
309
|
+
if (tok.kind === 'null') {
|
|
310
|
+
this.advance();
|
|
311
|
+
return { kind: 'literal', value: null };
|
|
312
|
+
}
|
|
313
|
+
if (tok.kind === 'lparen') {
|
|
314
|
+
this.advance();
|
|
315
|
+
const inner = this.parseExpr();
|
|
316
|
+
this.expect('rparen', "Expected ')'");
|
|
317
|
+
return inner;
|
|
318
|
+
}
|
|
319
|
+
if (tok.kind === 'ident') {
|
|
320
|
+
this.advance();
|
|
321
|
+
// Function call?
|
|
322
|
+
if (this.match('lparen')) {
|
|
323
|
+
this.advance();
|
|
324
|
+
const args = [];
|
|
325
|
+
if (!this.match('rparen')) {
|
|
326
|
+
args.push(this.parseExpr());
|
|
327
|
+
while (this.match('comma')) {
|
|
328
|
+
this.advance();
|
|
329
|
+
args.push(this.parseExpr());
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
this.expect('rparen', "Expected ')' to close call");
|
|
333
|
+
return { kind: 'call', name: tok.value, args };
|
|
334
|
+
}
|
|
335
|
+
// Member chain (dot or bracket-string)?
|
|
336
|
+
const segments = [];
|
|
337
|
+
while (this.match('dot', 'lbracket')) {
|
|
338
|
+
if (this.match('dot')) {
|
|
339
|
+
this.advance();
|
|
340
|
+
const idTok = this.expect('ident', 'Expected identifier after .');
|
|
341
|
+
segments.push(idTok.value);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
this.advance();
|
|
345
|
+
const strTok = this.expect('string', "Expected string inside [ ]");
|
|
346
|
+
this.expect('rbracket', "Expected ']'");
|
|
347
|
+
segments.push(strTok.value);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (segments.length > 0)
|
|
351
|
+
return { kind: 'path', head: tok.value, segments };
|
|
352
|
+
return { kind: 'ident', name: tok.value };
|
|
353
|
+
}
|
|
354
|
+
throw new CalcError('CALC-PARSE-001', `Unexpected token '${tok.value || tok.kind}' at position ${tok.pos}.`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
export function parseExpression(input) {
|
|
358
|
+
const tokens = tokenize(input);
|
|
359
|
+
return new Parser(tokens).parse();
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=parser.js.map
|