@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.
Files changed (214) hide show
  1. package/README.md +79 -0
  2. package/dist/agents/bancroft.d.ts +32 -0
  3. package/dist/agents/bancroft.d.ts.map +1 -0
  4. package/dist/agents/bancroft.js +292 -0
  5. package/dist/agents/bancroft.js.map +1 -0
  6. package/dist/agents/schemas.d.ts +13 -0
  7. package/dist/agents/schemas.d.ts.map +1 -0
  8. package/dist/agents/schemas.js +74 -0
  9. package/dist/agents/schemas.js.map +1 -0
  10. package/dist/bindings.d.ts +85 -0
  11. package/dist/bindings.d.ts.map +1 -0
  12. package/dist/bindings.js +293 -0
  13. package/dist/bindings.js.map +1 -0
  14. package/dist/browser.d.ts +66 -0
  15. package/dist/browser.d.ts.map +1 -0
  16. package/dist/browser.js +53 -0
  17. package/dist/browser.js.map +1 -0
  18. package/dist/calc/builtins.d.ts +4 -0
  19. package/dist/calc/builtins.d.ts.map +1 -0
  20. package/dist/calc/builtins.js +329 -0
  21. package/dist/calc/builtins.js.map +1 -0
  22. package/dist/calc/dependencies.d.ts +34 -0
  23. package/dist/calc/dependencies.d.ts.map +1 -0
  24. package/dist/calc/dependencies.js +101 -0
  25. package/dist/calc/dependencies.js.map +1 -0
  26. package/dist/calc/errors.d.ts +8 -0
  27. package/dist/calc/errors.d.ts.map +1 -0
  28. package/dist/calc/errors.js +18 -0
  29. package/dist/calc/errors.js.map +1 -0
  30. package/dist/calc/evaluator.d.ts +5 -0
  31. package/dist/calc/evaluator.d.ts.map +1 -0
  32. package/dist/calc/evaluator.js +184 -0
  33. package/dist/calc/evaluator.js.map +1 -0
  34. package/dist/calc/index.d.ts +14 -0
  35. package/dist/calc/index.d.ts.map +1 -0
  36. package/dist/calc/index.js +56 -0
  37. package/dist/calc/index.js.map +1 -0
  38. package/dist/calc/parser.d.ts +32 -0
  39. package/dist/calc/parser.d.ts.map +1 -0
  40. package/dist/calc/parser.js +361 -0
  41. package/dist/calc/parser.js.map +1 -0
  42. package/dist/cascade.d.ts +85 -0
  43. package/dist/cascade.d.ts.map +1 -0
  44. package/dist/cascade.js +172 -0
  45. package/dist/cascade.js.map +1 -0
  46. package/dist/cli.d.ts +3 -0
  47. package/dist/cli.d.ts.map +1 -0
  48. package/dist/cli.js +843 -0
  49. package/dist/cli.js.map +1 -0
  50. package/dist/codec.d.ts +25 -0
  51. package/dist/codec.d.ts.map +1 -0
  52. package/dist/codec.js +62 -0
  53. package/dist/codec.js.map +1 -0
  54. package/dist/codecs.d.ts +6 -0
  55. package/dist/codecs.d.ts.map +1 -0
  56. package/dist/codecs.js +12 -0
  57. package/dist/codecs.js.map +1 -0
  58. package/dist/compactor.d.ts +11 -0
  59. package/dist/compactor.d.ts.map +1 -0
  60. package/dist/compactor.js +84 -0
  61. package/dist/compactor.js.map +1 -0
  62. package/dist/context-profiles.d.ts +30 -0
  63. package/dist/context-profiles.d.ts.map +1 -0
  64. package/dist/context-profiles.js +225 -0
  65. package/dist/context-profiles.js.map +1 -0
  66. package/dist/context.d.ts +55 -0
  67. package/dist/context.d.ts.map +1 -0
  68. package/dist/context.js +288 -0
  69. package/dist/context.js.map +1 -0
  70. package/dist/dcf.d.ts +9 -0
  71. package/dist/dcf.d.ts.map +1 -0
  72. package/dist/dcf.js +75 -0
  73. package/dist/dcf.js.map +1 -0
  74. package/dist/debt.d.ts +11 -0
  75. package/dist/debt.d.ts.map +1 -0
  76. package/dist/debt.js +85 -0
  77. package/dist/debt.js.map +1 -0
  78. package/dist/defaults.d.ts +104 -0
  79. package/dist/defaults.d.ts.map +1 -0
  80. package/dist/defaults.js +622 -0
  81. package/dist/defaults.js.map +1 -0
  82. package/dist/derived.d.ts +17 -0
  83. package/dist/derived.d.ts.map +1 -0
  84. package/dist/derived.js +29 -0
  85. package/dist/derived.js.map +1 -0
  86. package/dist/editor.d.ts +76 -0
  87. package/dist/editor.d.ts.map +1 -0
  88. package/dist/editor.js +502 -0
  89. package/dist/editor.js.map +1 -0
  90. package/dist/envelope.d.ts +51 -0
  91. package/dist/envelope.d.ts.map +1 -0
  92. package/dist/envelope.js +213 -0
  93. package/dist/envelope.js.map +1 -0
  94. package/dist/format.d.ts +60 -0
  95. package/dist/format.d.ts.map +1 -0
  96. package/dist/format.js +115 -0
  97. package/dist/format.js.map +1 -0
  98. package/dist/gaps.d.ts +62 -0
  99. package/dist/gaps.d.ts.map +1 -0
  100. package/dist/gaps.js +272 -0
  101. package/dist/gaps.js.map +1 -0
  102. package/dist/index.d.ts +75 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +47 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/init.d.ts +15 -0
  107. package/dist/init.d.ts.map +1 -0
  108. package/dist/init.js +169 -0
  109. package/dist/init.js.map +1 -0
  110. package/dist/integrity-canonical.d.ts +17 -0
  111. package/dist/integrity-canonical.d.ts.map +1 -0
  112. package/dist/integrity-canonical.js +124 -0
  113. package/dist/integrity-canonical.js.map +1 -0
  114. package/dist/integrity.d.ts +54 -0
  115. package/dist/integrity.d.ts.map +1 -0
  116. package/dist/integrity.js +244 -0
  117. package/dist/integrity.js.map +1 -0
  118. package/dist/modules.d.ts +24 -0
  119. package/dist/modules.d.ts.map +1 -0
  120. package/dist/modules.js +322 -0
  121. package/dist/modules.js.map +1 -0
  122. package/dist/negotiation.d.ts +19 -0
  123. package/dist/negotiation.d.ts.map +1 -0
  124. package/dist/negotiation.js +70 -0
  125. package/dist/negotiation.js.map +1 -0
  126. package/dist/opstatement.d.ts +9 -0
  127. package/dist/opstatement.d.ts.map +1 -0
  128. package/dist/opstatement.js +80 -0
  129. package/dist/opstatement.js.map +1 -0
  130. package/dist/packs/excel-emit.d.ts +21 -0
  131. package/dist/packs/excel-emit.d.ts.map +1 -0
  132. package/dist/packs/excel-emit.js +135 -0
  133. package/dist/packs/excel-emit.js.map +1 -0
  134. package/dist/packs/index.d.ts +13 -0
  135. package/dist/packs/index.d.ts.map +1 -0
  136. package/dist/packs/index.js +37 -0
  137. package/dist/packs/index.js.map +1 -0
  138. package/dist/packs/industrial.d.ts +3 -0
  139. package/dist/packs/industrial.d.ts.map +1 -0
  140. package/dist/packs/industrial.js +115 -0
  141. package/dist/packs/industrial.js.map +1 -0
  142. package/dist/packs/multifamily.d.ts +3 -0
  143. package/dist/packs/multifamily.d.ts.map +1 -0
  144. package/dist/packs/multifamily.js +82 -0
  145. package/dist/packs/multifamily.js.map +1 -0
  146. package/dist/packs/office.d.ts +3 -0
  147. package/dist/packs/office.d.ts.map +1 -0
  148. package/dist/packs/office.js +108 -0
  149. package/dist/packs/office.js.map +1 -0
  150. package/dist/packs/retail.d.ts +3 -0
  151. package/dist/packs/retail.d.ts.map +1 -0
  152. package/dist/packs/retail.js +115 -0
  153. package/dist/packs/retail.js.map +1 -0
  154. package/dist/packs/self-storage.d.ts +3 -0
  155. package/dist/packs/self-storage.d.ts.map +1 -0
  156. package/dist/packs/self-storage.js +105 -0
  157. package/dist/packs/self-storage.js.map +1 -0
  158. package/dist/parser.d.ts +15 -0
  159. package/dist/parser.d.ts.map +1 -0
  160. package/dist/parser.js +430 -0
  161. package/dist/parser.js.map +1 -0
  162. package/dist/protocol.d.ts +389 -0
  163. package/dist/protocol.d.ts.map +1 -0
  164. package/dist/protocol.js +714 -0
  165. package/dist/protocol.js.map +1 -0
  166. package/dist/refinement.d.ts +63 -0
  167. package/dist/refinement.d.ts.map +1 -0
  168. package/dist/refinement.js +242 -0
  169. package/dist/refinement.js.map +1 -0
  170. package/dist/renderer.d.ts +22 -0
  171. package/dist/renderer.d.ts.map +1 -0
  172. package/dist/renderer.js +478 -0
  173. package/dist/renderer.js.map +1 -0
  174. package/dist/rentroll.d.ts +21 -0
  175. package/dist/rentroll.d.ts.map +1 -0
  176. package/dist/rentroll.js +256 -0
  177. package/dist/rentroll.js.map +1 -0
  178. package/dist/report.d.ts +29 -0
  179. package/dist/report.d.ts.map +1 -0
  180. package/dist/report.js +924 -0
  181. package/dist/report.js.map +1 -0
  182. package/dist/runner.d.ts +43 -0
  183. package/dist/runner.d.ts.map +1 -0
  184. package/dist/runner.js +288 -0
  185. package/dist/runner.js.map +1 -0
  186. package/dist/sourcesuses.d.ts +14 -0
  187. package/dist/sourcesuses.d.ts.map +1 -0
  188. package/dist/sourcesuses.js +63 -0
  189. package/dist/sourcesuses.js.map +1 -0
  190. package/dist/types.d.ts +248 -0
  191. package/dist/types.d.ts.map +1 -0
  192. package/dist/types.js +15 -0
  193. package/dist/types.js.map +1 -0
  194. package/dist/uwcsv.d.ts +23 -0
  195. package/dist/uwcsv.d.ts.map +1 -0
  196. package/dist/uwcsv.js +877 -0
  197. package/dist/uwcsv.js.map +1 -0
  198. package/dist/uwjson.d.ts +19 -0
  199. package/dist/uwjson.d.ts.map +1 -0
  200. package/dist/uwjson.js +61 -0
  201. package/dist/uwjson.js.map +1 -0
  202. package/dist/uwxml.d.ts +18 -0
  203. package/dist/uwxml.d.ts.map +1 -0
  204. package/dist/uwxml.js +297 -0
  205. package/dist/uwxml.js.map +1 -0
  206. package/dist/validator.d.ts +35 -0
  207. package/dist/validator.d.ts.map +1 -0
  208. package/dist/validator.js +553 -0
  209. package/dist/validator.js.map +1 -0
  210. package/dist/valuation.d.ts +9 -0
  211. package/dist/valuation.d.ts.map +1 -0
  212. package/dist/valuation.js +34 -0
  213. package/dist/valuation.js.map +1 -0
  214. 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