@divmain/jdm-asm 0.2.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 (397) hide show
  1. package/.github/workflows/ci.yml +53 -0
  2. package/.oxfmtrc.json +16 -0
  3. package/.oxlintrc.json +183 -0
  4. package/AGENTS.md +81 -0
  5. package/README.md +769 -0
  6. package/asconfig.json +23 -0
  7. package/benchmarks/fixtures.ts +111 -0
  8. package/benchmarks/input-fixtures.ts +80 -0
  9. package/benchmarks/run.ts +913 -0
  10. package/benchmarks/worker-pool.ts +223 -0
  11. package/benchmarks/worker.ts +374 -0
  12. package/dist/index.d.ts +996 -0
  13. package/dist/index.js +12239 -0
  14. package/dist/index.js.map +1 -0
  15. package/package.json +49 -0
  16. package/scripts/run-all-tests.ts +220 -0
  17. package/src/compiler/EXPRESSION_SUBSETS.md +228 -0
  18. package/src/compiler/asc-compiler.ts +315 -0
  19. package/src/compiler/ast-types.ts +215 -0
  20. package/src/compiler/build.ts +56 -0
  21. package/src/compiler/cache.ts +414 -0
  22. package/src/compiler/code-generators.ts +211 -0
  23. package/src/compiler/codegen/index.ts +15 -0
  24. package/src/compiler/codegen/js-marshal.ts +999 -0
  25. package/src/compiler/codegen/js-validation.ts +243 -0
  26. package/src/compiler/codegen.ts +19 -0
  27. package/src/compiler/compile-time-validation.ts +507 -0
  28. package/src/compiler/cst-visitor.ts +434 -0
  29. package/src/compiler/errors.ts +227 -0
  30. package/src/compiler/expression-parser.ts +536 -0
  31. package/src/compiler/graph.ts +197 -0
  32. package/src/compiler/index.ts +199 -0
  33. package/src/compiler/input-validation.ts +33 -0
  34. package/src/compiler/marshal-gen.ts +21 -0
  35. package/src/compiler/nodes/context-resolvers.ts +197 -0
  36. package/src/compiler/nodes/decision-table.ts +507 -0
  37. package/src/compiler/nodes/decision.ts +292 -0
  38. package/src/compiler/nodes/expression-compiler.ts +526 -0
  39. package/src/compiler/nodes/expression.ts +425 -0
  40. package/src/compiler/nodes/function.ts +316 -0
  41. package/src/compiler/nodes/input.ts +60 -0
  42. package/src/compiler/nodes/switch.ts +547 -0
  43. package/src/compiler/optimizer.ts +948 -0
  44. package/src/compiler/orchestrator.ts +352 -0
  45. package/src/compiler/parser.ts +115 -0
  46. package/src/compiler/result-selection.ts +161 -0
  47. package/src/compiler/runtime/index.ts +26 -0
  48. package/src/compiler/runtime-codegen.ts +211 -0
  49. package/src/compiler/runtime-validation-codegen.ts +294 -0
  50. package/src/compiler/runtime.ts +452 -0
  51. package/src/compiler/schema.ts +245 -0
  52. package/src/compiler/switch-branch-detection.ts +92 -0
  53. package/src/compiler/types.ts +136 -0
  54. package/src/compiler/unary-ast-transforms.ts +148 -0
  55. package/src/compiler/unary-parser.ts +301 -0
  56. package/src/compiler/unary-transform.ts +161 -0
  57. package/src/compiler/utils.ts +27 -0
  58. package/src/compiler/virtual-fs.ts +90 -0
  59. package/src/compiler/wasm-instantiate.ts +127 -0
  60. package/src/index.ts +1 -0
  61. package/src/runtime/arrays.ts +579 -0
  62. package/src/runtime/context.ts +189 -0
  63. package/src/runtime/expressions.ts +1811 -0
  64. package/src/runtime/index.ts +8 -0
  65. package/src/runtime/memory.ts +607 -0
  66. package/src/runtime/strings.ts +260 -0
  67. package/src/runtime/tables.ts +96 -0
  68. package/src/runtime/tsconfig.json +4 -0
  69. package/src/runtime/values.ts +209 -0
  70. package/test-data/README.md +83 -0
  71. package/test-data/decision-tables/basic/8k.json +87992 -0
  72. package/test-data/decision-tables/basic/affiliate-commission-calculator.json +228 -0
  73. package/test-data/decision-tables/basic/airline-loyalty-points-calculations.json +285 -0
  74. package/test-data/decision-tables/basic/airline-upgrade-eligibility.json +466 -0
  75. package/test-data/decision-tables/basic/auto-insurance-premium-calculator.json +412 -0
  76. package/test-data/decision-tables/basic/booking-personalization-system.json +553 -0
  77. package/test-data/decision-tables/basic/care-team-assignment-system.json +585 -0
  78. package/test-data/decision-tables/basic/claim-validation-system.json +307 -0
  79. package/test-data/decision-tables/basic/clinical-lab-result-interpreter.json +433 -0
  80. package/test-data/decision-tables/basic/clinical-treatment-protocol.json +474 -0
  81. package/test-data/decision-tables/basic/credit-limit-adjustment.json +479 -0
  82. package/test-data/decision-tables/basic/customer-eligibility-engine.json +551 -0
  83. package/test-data/decision-tables/basic/customer-lifetime-value.json +200 -0
  84. package/test-data/decision-tables/basic/customer-onboarding-kyc-verification.json +611 -0
  85. package/test-data/decision-tables/basic/customer-service-escalation.json +191 -0
  86. package/test-data/decision-tables/basic/decision-table-discounts.json +168 -0
  87. package/test-data/decision-tables/basic/decision-table-shipping.json +398 -0
  88. package/test-data/decision-tables/basic/delivery-route-optimizer.json +271 -0
  89. package/test-data/decision-tables/basic/device-compatibility-checker.json +303 -0
  90. package/test-data/decision-tables/basic/disaster-relief-fund-allocation.json +296 -0
  91. package/test-data/decision-tables/basic/dynamic-fx-rate-pricing-system.json +237 -0
  92. package/test-data/decision-tables/basic/dynamic-marketplace-comission-calculator.json +242 -0
  93. package/test-data/decision-tables/basic/dynamic-shipping-cost-calculator.json +378 -0
  94. package/test-data/decision-tables/basic/dynamic-tarrif-engine.json +289 -0
  95. package/test-data/decision-tables/basic/dynamic-ticket-pricing.json +325 -0
  96. package/test-data/decision-tables/basic/empty-column-with-space.json +100 -0
  97. package/test-data/decision-tables/basic/empty-column-without-space.json +100 -0
  98. package/test-data/decision-tables/basic/environment-compliance-assessment.json +386 -0
  99. package/test-data/decision-tables/basic/expression-table-map.json +313 -0
  100. package/test-data/decision-tables/basic/flash-sale-eligibility.json +366 -0
  101. package/test-data/decision-tables/basic/flight-dispatch-decision-system.json +455 -0
  102. package/test-data/decision-tables/basic/flight-rebooking-fee-calculator.json +406 -0
  103. package/test-data/decision-tables/basic/government-assistance.json +299 -0
  104. package/test-data/decision-tables/basic/grant-funding-distribution.json +307 -0
  105. package/test-data/decision-tables/basic/hazardous-materials-management-system.json +414 -0
  106. package/test-data/decision-tables/basic/immigration-eligibility-evaluator.json +765 -0
  107. package/test-data/decision-tables/basic/import-duties-calculator.json +318 -0
  108. package/test-data/decision-tables/basic/insurance-agent-commission.json +228 -0
  109. package/test-data/decision-tables/basic/insurance-coverage-calculator.json +362 -0
  110. package/test-data/decision-tables/basic/insurance-underwriting-risk.json +321 -0
  111. package/test-data/decision-tables/basic/international-roaming-policy-manager.json +199 -0
  112. package/test-data/decision-tables/basic/legacy-plan-management.json +434 -0
  113. package/test-data/decision-tables/basic/marketplace-listing-verification-system.json +334 -0
  114. package/test-data/decision-tables/basic/medication-dosage-calculator.json +318 -0
  115. package/test-data/decision-tables/basic/merch-bags.json +171 -0
  116. package/test-data/decision-tables/basic/municipal-permit-evaluation-system.json +364 -0
  117. package/test-data/decision-tables/basic/mvno-partner-enablement.json +313 -0
  118. package/test-data/decision-tables/basic/partner-revenue-sharing.json +244 -0
  119. package/test-data/decision-tables/basic/payment-routing-and-fee-calculator.json +475 -0
  120. package/test-data/decision-tables/basic/policy-discount-calculator.json +307 -0
  121. package/test-data/decision-tables/basic/policy-eligibility-analyzer.json +299 -0
  122. package/test-data/decision-tables/basic/product-listing-scoring.json +358 -0
  123. package/test-data/decision-tables/basic/realtime-fraud-detection.json +235 -0
  124. package/test-data/decision-tables/basic/regional-compliance-manager.json +278 -0
  125. package/test-data/decision-tables/basic/returns-and-refund-policy.json +366 -0
  126. package/test-data/decision-tables/basic/returns-processing-system.json +448 -0
  127. package/test-data/decision-tables/basic/school-district-resource-allocation.json +282 -0
  128. package/test-data/decision-tables/basic/seat-map-optimization.json +325 -0
  129. package/test-data/decision-tables/basic/seller-fee-calculator.json +307 -0
  130. package/test-data/decision-tables/basic/service-level-agreement-enforcement.json +575 -0
  131. package/test-data/decision-tables/basic/smart-financial-product-matcher.json +249 -0
  132. package/test-data/decision-tables/basic/supply-chain-risk.json +316 -0
  133. package/test-data/decision-tables/basic/table-loop.json +93 -0
  134. package/test-data/decision-tables/basic/table.json +76 -0
  135. package/test-data/decision-tables/basic/traffic-violation-penalty-calculator.json +436 -0
  136. package/test-data/decision-tables/basic/transaction-compliance-classifier.json +525 -0
  137. package/test-data/decision-tables/basic/vehicle-claims-resolution.json +310 -0
  138. package/test-data/decision-tables/basic/warehouse-storage-location.json +345 -0
  139. package/test-data/decision-tables/hit-policy-collect/collect-multiple-matches.json +127 -0
  140. package/test-data/decision-tables/hit-policy-collect/collect-no-match.json +95 -0
  141. package/test-data/decision-tables/hit-policy-first/first-match.json +103 -0
  142. package/test-data/decision-tables/hit-policy-first/no-match.json +95 -0
  143. package/test-data/decision-tables/hit-policy-output-order/output-order-respected.json +94 -0
  144. package/test-data/decision-tables/hit-policy-output-order/string-output-order.json +94 -0
  145. package/test-data/decision-tables/hit-policy-priority/priority-respected.json +86 -0
  146. package/test-data/decision-tables/hit-policy-rule-order/rule-order-respected.json +94 -0
  147. package/test-data/decision-tables/hit-policy-unique/all-match-error.json +89 -0
  148. package/test-data/decision-tables/hit-policy-unique/multiple-match-error.json +89 -0
  149. package/test-data/decision-tables/hit-policy-unique/no-match.json +88 -0
  150. package/test-data/decision-tables/hit-policy-unique/unique-match.json +99 -0
  151. package/test-data/expressions/arithmetic/error-cyclic.json +114 -0
  152. package/test-data/expressions/arithmetic/error-missing-input.json +54 -0
  153. package/test-data/expressions/arithmetic/error-missing-output.json +54 -0
  154. package/test-data/expressions/arithmetic/expression-default.json +93 -0
  155. package/test-data/expressions/arithmetic/expression-fields.json +94 -0
  156. package/test-data/expressions/arithmetic/expression-loop.json +94 -0
  157. package/test-data/expressions/arithmetic/expression-passthrough.json +108 -0
  158. package/test-data/expressions/arithmetic/expression.json +69 -0
  159. package/test-data/expressions/arithmetic/nested-request.json +125 -0
  160. package/test-data/expressions/arithmetic/number-function.json +58 -0
  161. package/test-data/expressions/arithmetic/test-number-functions.json +68 -0
  162. package/test-data/expressions/functions/all.json +149 -0
  163. package/test-data/expressions/functions/avg.json +89 -0
  164. package/test-data/expressions/functions/filter.json +109 -0
  165. package/test-data/expressions/functions/flat.json +167 -0
  166. package/test-data/expressions/functions/map-strings.json +65 -0
  167. package/test-data/expressions/functions/map.json +73 -0
  168. package/test-data/expressions/functions/reduce.json +49 -0
  169. package/test-data/expressions/functions/some.json +175 -0
  170. package/test-data/expressions/functions/sort-strings.json +97 -0
  171. package/test-data/expressions/functions/sort.json +97 -0
  172. package/test-data/expressions/logical/logical-and.json +116 -0
  173. package/test-data/expressions/logical/logical-complex.json +260 -0
  174. package/test-data/expressions/logical/logical-not.json +111 -0
  175. package/test-data/expressions/logical/logical-or.json +123 -0
  176. package/test-data/expressions/string/string-comparison.json +128 -0
  177. package/test-data/expressions/string/string-concat.json +106 -0
  178. package/test-data/expressions/string/string-contains.json +125 -0
  179. package/test-data/expressions/string/string-endsWith.json +113 -0
  180. package/test-data/expressions/string/string-indexOf.json +131 -0
  181. package/test-data/expressions/string/string-join.json +92 -0
  182. package/test-data/expressions/string/string-lower.json +94 -0
  183. package/test-data/expressions/string/string-replace.json +130 -0
  184. package/test-data/expressions/string/string-split.json +101 -0
  185. package/test-data/expressions/string/string-startsWith.json +113 -0
  186. package/test-data/expressions/string/string-substring.json +138 -0
  187. package/test-data/expressions/string/string-trim.json +100 -0
  188. package/test-data/expressions/string/string-upper.json +94 -0
  189. package/test-data/other/custom.json +51 -0
  190. package/test-data/other/customer-input-schema.json +34 -0
  191. package/test-data/other/customer-output-schema.json +34 -0
  192. package/test-data/other/passthrough.json +31 -0
  193. package/test-data/sub-decisions/basic/$nodes-child.json +31 -0
  194. package/test-data/sub-decisions/basic/$nodes-parent.json +49 -0
  195. package/test-data/sub-decisions/basic/recursive-table1.json +49 -0
  196. package/test-data/sub-decisions/basic/recursive-table2.json +49 -0
  197. package/test-data/sub-decisions/complex-multi/approval-decision.json +31 -0
  198. package/test-data/sub-decisions/complex-multi/complex-dag.json +175 -0
  199. package/test-data/sub-decisions/complex-multi/credit-check.json +31 -0
  200. package/test-data/sub-decisions/complex-multi/customer-segmentation.json +31 -0
  201. package/test-data/sub-decisions/complex-multi/discount-eligibility.json +31 -0
  202. package/test-data/sub-decisions/complex-multi/eligibility-check.json +31 -0
  203. package/test-data/sub-decisions/complex-multi/final-offer.json +31 -0
  204. package/test-data/sub-decisions/complex-multi/income-verification.json +31 -0
  205. package/test-data/sub-decisions/complex-multi/linear-chain.json +121 -0
  206. package/test-data/sub-decisions/complex-multi/pricing-calculation.json +31 -0
  207. package/test-data/sub-decisions/complex-multi/product-eligibility.json +31 -0
  208. package/test-data/sub-decisions/complex-multi/risk-assessment.json +31 -0
  209. package/test-data/sub-decisions/complex-multi/shared-validation.json +31 -0
  210. package/test-data/sub-decisions/complex-multi/validation.json +31 -0
  211. package/test-data/sub-decisions/diamond/decision-a.json +31 -0
  212. package/test-data/sub-decisions/diamond/decision-b.json +31 -0
  213. package/test-data/sub-decisions/diamond/decision-c.json +31 -0
  214. package/test-data/sub-decisions/diamond/decision-shared.json +31 -0
  215. package/test-data/sub-decisions/diamond/diamond-pattern.json +109 -0
  216. package/test-data/sub-decisions/error-propagation/parent-calls-error.json +44 -0
  217. package/test-data/sub-decisions/error-propagation/sub-decision-with-error.json +60 -0
  218. package/test-data/switch-nodes/basic/account-dormancy-management.json +245 -0
  219. package/test-data/switch-nodes/basic/application-risk-assessment.json +474 -0
  220. package/test-data/switch-nodes/basic/cellular-data-rollover-system.json +281 -0
  221. package/test-data/switch-nodes/basic/clinical-pathway-selection.json +454 -0
  222. package/test-data/switch-nodes/basic/insurance-prior-authorization.json +467 -0
  223. package/test-data/switch-nodes/basic/last-mile-delivery-assignment.json +373 -0
  224. package/test-data/switch-nodes/basic/loan-approval.json +469 -0
  225. package/test-data/switch-nodes/basic/multi-switch.json +498 -0
  226. package/test-data/switch-nodes/basic/online-checkin-eligibility.json +285 -0
  227. package/test-data/switch-nodes/basic/order-consolidation-system.json +493 -0
  228. package/test-data/switch-nodes/basic/seller-approval-workflow.json +383 -0
  229. package/test-data/switch-nodes/basic/set-fee.json +243 -0
  230. package/test-data/switch-nodes/basic/shipping-carrier-selector.json +379 -0
  231. package/test-data/switch-nodes/basic/switch-node.json +167 -0
  232. package/test-data/switch-nodes/basic/switch-performance-2.json +1307 -0
  233. package/test-data/switch-nodes/basic/switch-performance.json +691 -0
  234. package/test-data/switch-nodes/basic/tax-exemption.json +295 -0
  235. package/test-data/switch-nodes/basic/warehouse-cross-docking.json +313 -0
  236. package/test-data/switch-nodes/default-cases/switch-with-default.json +134 -0
  237. package/test-data/zen-reference/$nodes-child.json +69 -0
  238. package/test-data/zen-reference/$nodes-parent.json +34 -0
  239. package/test-data/zen-reference/8k.json +87992 -0
  240. package/test-data/zen-reference/credit-analysis.json +324 -0
  241. package/test-data/zen-reference/custom.json +51 -0
  242. package/test-data/zen-reference/customer-input-schema.json +34 -0
  243. package/test-data/zen-reference/customer-output-schema.json +34 -0
  244. package/test-data/zen-reference/error-cyclic.json +114 -0
  245. package/test-data/zen-reference/error-missing-input.json +54 -0
  246. package/test-data/zen-reference/error-missing-output.json +54 -0
  247. package/test-data/zen-reference/expression.json +69 -0
  248. package/test-data/zen-reference/function-v2.json +48 -0
  249. package/test-data/zen-reference/function.json +46 -0
  250. package/test-data/zen-reference/graphs/account-dormancy-management.json +245 -0
  251. package/test-data/zen-reference/graphs/affiliate-commission-calculator.json +228 -0
  252. package/test-data/zen-reference/graphs/airline-loyalty-points-calculations.json +285 -0
  253. package/test-data/zen-reference/graphs/airline-upgrade-eligibility.json +466 -0
  254. package/test-data/zen-reference/graphs/aml.json +537 -0
  255. package/test-data/zen-reference/graphs/application-risk-assessment.json +474 -0
  256. package/test-data/zen-reference/graphs/auto-insurance-premium-calculator.json +412 -0
  257. package/test-data/zen-reference/graphs/booking-personalization-system.json +553 -0
  258. package/test-data/zen-reference/graphs/care-team-assignment-system.json +585 -0
  259. package/test-data/zen-reference/graphs/cellular-data-rollover-system.json +281 -0
  260. package/test-data/zen-reference/graphs/claim-validation-system.json +307 -0
  261. package/test-data/zen-reference/graphs/clinical-lab-result-interpreter.json +433 -0
  262. package/test-data/zen-reference/graphs/clinical-pathway-selection.json +454 -0
  263. package/test-data/zen-reference/graphs/clinical-treatment-protocol.json +474 -0
  264. package/test-data/zen-reference/graphs/company-analysis.json +390 -0
  265. package/test-data/zen-reference/graphs/credit-limit-adjustment.json +479 -0
  266. package/test-data/zen-reference/graphs/customer-eligibility-engine.json +551 -0
  267. package/test-data/zen-reference/graphs/customer-lifetime-value.json +200 -0
  268. package/test-data/zen-reference/graphs/customer-onboarding-kyc-verification.json +611 -0
  269. package/test-data/zen-reference/graphs/customer-service-escalation.json +191 -0
  270. package/test-data/zen-reference/graphs/decision-table-discounts.json +168 -0
  271. package/test-data/zen-reference/graphs/decision-table-shipping.json +398 -0
  272. package/test-data/zen-reference/graphs/delivery-route-optimizer.json +271 -0
  273. package/test-data/zen-reference/graphs/device-compatibility-checker.json +303 -0
  274. package/test-data/zen-reference/graphs/disaster-relief-fund-allocation.json +296 -0
  275. package/test-data/zen-reference/graphs/dynamic-fx-rate-pricing-system.json +237 -0
  276. package/test-data/zen-reference/graphs/dynamic-marketplace-comission-calculator.json +242 -0
  277. package/test-data/zen-reference/graphs/dynamic-shipping-cost-calculator.json +378 -0
  278. package/test-data/zen-reference/graphs/dynamic-tarrif-engine.json +289 -0
  279. package/test-data/zen-reference/graphs/dynamic-ticket-pricing.json +325 -0
  280. package/test-data/zen-reference/graphs/empty-column-with-space.json +100 -0
  281. package/test-data/zen-reference/graphs/empty-column-without-space.json +100 -0
  282. package/test-data/zen-reference/graphs/environment-compliance-assessment.json +386 -0
  283. package/test-data/zen-reference/graphs/expression-default.json +93 -0
  284. package/test-data/zen-reference/graphs/expression-fields.json +94 -0
  285. package/test-data/zen-reference/graphs/expression-loop.json +94 -0
  286. package/test-data/zen-reference/graphs/expression-passthrough.json +108 -0
  287. package/test-data/zen-reference/graphs/expression-table-map.json +313 -0
  288. package/test-data/zen-reference/graphs/flash-sale-eligibility.json +366 -0
  289. package/test-data/zen-reference/graphs/flight-dispatch-decision-system.json +455 -0
  290. package/test-data/zen-reference/graphs/flight-rebooking-fee-calculator.json +406 -0
  291. package/test-data/zen-reference/graphs/government-assistance.json +299 -0
  292. package/test-data/zen-reference/graphs/grant-funding-distribution.json +307 -0
  293. package/test-data/zen-reference/graphs/hazardous-materials-management-system.json +414 -0
  294. package/test-data/zen-reference/graphs/immigration-eligibility-evaluator.json +765 -0
  295. package/test-data/zen-reference/graphs/import-duties-calculator.json +318 -0
  296. package/test-data/zen-reference/graphs/insurance-agent-commission.json +228 -0
  297. package/test-data/zen-reference/graphs/insurance-breakdown.json +421 -0
  298. package/test-data/zen-reference/graphs/insurance-coverage-calculator.json +362 -0
  299. package/test-data/zen-reference/graphs/insurance-prior-authorization.json +467 -0
  300. package/test-data/zen-reference/graphs/insurance-underwriting-risk.json +321 -0
  301. package/test-data/zen-reference/graphs/international-roaming-policy-manager.json +199 -0
  302. package/test-data/zen-reference/graphs/last-mile-delivery-assignment.json +373 -0
  303. package/test-data/zen-reference/graphs/legacy-plan-management.json +434 -0
  304. package/test-data/zen-reference/graphs/loan-approval.json +469 -0
  305. package/test-data/zen-reference/graphs/marketplace-listing-verification-system.json +334 -0
  306. package/test-data/zen-reference/graphs/medication-dosage-calculator.json +318 -0
  307. package/test-data/zen-reference/graphs/merch-bags.json +171 -0
  308. package/test-data/zen-reference/graphs/multi-switch.json +498 -0
  309. package/test-data/zen-reference/graphs/municipal-permit-evaluation-system.json +364 -0
  310. package/test-data/zen-reference/graphs/mvno-partner-enablement.json +313 -0
  311. package/test-data/zen-reference/graphs/nested-request.json +125 -0
  312. package/test-data/zen-reference/graphs/online-checkin-eligibility.json +285 -0
  313. package/test-data/zen-reference/graphs/order-consolidation-system.json +493 -0
  314. package/test-data/zen-reference/graphs/partner-revenue-sharing.json +244 -0
  315. package/test-data/zen-reference/graphs/payment-routing-and-fee-calculator.json +475 -0
  316. package/test-data/zen-reference/graphs/policy-discount-calculator.json +307 -0
  317. package/test-data/zen-reference/graphs/policy-eligibility-analyzer.json +299 -0
  318. package/test-data/zen-reference/graphs/product-listing-scoring.json +358 -0
  319. package/test-data/zen-reference/graphs/realtime-fraud-detection.json +235 -0
  320. package/test-data/zen-reference/graphs/regional-compliance-manager.json +278 -0
  321. package/test-data/zen-reference/graphs/returns-and-refund-policy.json +366 -0
  322. package/test-data/zen-reference/graphs/returns-processing-system.json +448 -0
  323. package/test-data/zen-reference/graphs/school-district-resource-allocation.json +282 -0
  324. package/test-data/zen-reference/graphs/seat-map-optimization.json +325 -0
  325. package/test-data/zen-reference/graphs/seller-approval-workflow.json +383 -0
  326. package/test-data/zen-reference/graphs/seller-fee-calculator.json +307 -0
  327. package/test-data/zen-reference/graphs/service-level-agreement-enforcement.json +575 -0
  328. package/test-data/zen-reference/graphs/set-fee.json +243 -0
  329. package/test-data/zen-reference/graphs/shipping-carrier-selector.json +379 -0
  330. package/test-data/zen-reference/graphs/smart-financial-product-matcher.json +249 -0
  331. package/test-data/zen-reference/graphs/supply-chain-risk.json +316 -0
  332. package/test-data/zen-reference/graphs/table-loop.json +93 -0
  333. package/test-data/zen-reference/graphs/tax-exemption.json +295 -0
  334. package/test-data/zen-reference/graphs/traffic-violation-penalty-calculator.json +436 -0
  335. package/test-data/zen-reference/graphs/transaction-compliance-classifier.json +525 -0
  336. package/test-data/zen-reference/graphs/vehicle-claims-resolution.json +310 -0
  337. package/test-data/zen-reference/graphs/warehouse-cross-docking.json +313 -0
  338. package/test-data/zen-reference/graphs/warehouse-storage-location.json +345 -0
  339. package/test-data/zen-reference/http-function.json +34 -0
  340. package/test-data/zen-reference/infinite-function.json +46 -0
  341. package/test-data/zen-reference/js/imports.js +25 -0
  342. package/test-data/zen-reference/passthrough.json +31 -0
  343. package/test-data/zen-reference/recursive-table1.json +49 -0
  344. package/test-data/zen-reference/recursive-table2.json +49 -0
  345. package/test-data/zen-reference/sleep-function.json +34 -0
  346. package/test-data/zen-reference/switch-node.json +167 -0
  347. package/test-data/zen-reference/switch-performance-2.json +1307 -0
  348. package/test-data/zen-reference/switch-performance.json +691 -0
  349. package/test-data/zen-reference/table.json +76 -0
  350. package/tests/helpers/index.ts +73 -0
  351. package/tests/helpers/mock-context.ts +231 -0
  352. package/tests/helpers/round-trip.ts +398 -0
  353. package/tests/helpers/test-harness-comparison.ts +325 -0
  354. package/tests/helpers/test-harness-wasm.ts +710 -0
  355. package/tests/helpers/test-harness.ts +28 -0
  356. package/tests/helpers/wasm-test.ts +659 -0
  357. package/tests/integration/compilation-errors.test.ts +864 -0
  358. package/tests/integration/decision-tables.test.ts +531 -0
  359. package/tests/integration/edge-cases.test.ts +787 -0
  360. package/tests/integration/expressions.test.ts +513 -0
  361. package/tests/integration/function-node-integration.test.ts +182 -0
  362. package/tests/integration/sub-decisions.test.ts +108 -0
  363. package/tests/integration/switch-nodes.test.ts +399 -0
  364. package/tests/integration/unary-or-matching.test.ts +53 -0
  365. package/tests/integration/wasm-data-types.test.ts +398 -0
  366. package/tests/integration/wasm-errors.test.ts +199 -0
  367. package/tests/integration/wasm-execution.test.ts +348 -0
  368. package/tests/integration/wasm-memory.test.ts +228 -0
  369. package/tests/scripts/analyze-coverage.ts +166 -0
  370. package/tests/scripts/categorize-tests.ts +396 -0
  371. package/tests/scripts/coverage-analysis.ts +836 -0
  372. package/tests/unit/compiler/cache.test.ts +238 -0
  373. package/tests/unit/compiler/errors.test.ts +316 -0
  374. package/tests/unit/compiler/graph-scalability.test.ts +510 -0
  375. package/tests/unit/compiler/graph.test.ts +878 -0
  376. package/tests/unit/compiler/input-validation.test.ts +447 -0
  377. package/tests/unit/compiler/logical-and-parser.test.ts +143 -0
  378. package/tests/unit/compiler/logical-not-parser.test.ts +107 -0
  379. package/tests/unit/compiler/logical-or-parser.test.ts +236 -0
  380. package/tests/unit/compiler/marshal-gen/marshal-gen.test.ts +97 -0
  381. package/tests/unit/compiler/nodes/decision-table.test.ts +103 -0
  382. package/tests/unit/compiler/nodes/decision.test.ts +182 -0
  383. package/tests/unit/compiler/nodes/function-compile.test.ts +204 -0
  384. package/tests/unit/compiler/nodes/function.test.ts +176 -0
  385. package/tests/unit/compiler/nodes/input.test.ts +30 -0
  386. package/tests/unit/compiler/nodes/switch.test.ts +127 -0
  387. package/tests/unit/compiler/optimizer-cache.test.ts +327 -0
  388. package/tests/unit/compiler/optimizer-implementation.test.ts +625 -0
  389. package/tests/unit/compiler/parser.test.ts +508 -0
  390. package/tests/unit/compiler/runtime-error-cleanup.test.ts +426 -0
  391. package/tests/unit/compiler/runtime-validation.test.ts +303 -0
  392. package/tests/unit/compiler/runtime.test.ts +221 -0
  393. package/tests/unit/compiler/schema/schema.test.ts +248 -0
  394. package/tests/unit/compiler/unary-ast-transforms.test.ts +245 -0
  395. package/tsconfig.json +27 -0
  396. package/tsup.config.ts +11 -0
  397. package/vitest.config.ts +12 -0
@@ -0,0 +1,948 @@
1
+ /**
2
+ * Optimization Pipeline for JDM to WebAssembly Compilation
3
+ *
4
+ * This module defines the interface for optimization passes and the runner
5
+ * that applies them sequentially to transform the compiled AST into an
6
+ * optimized form before code generation.
7
+ */
8
+
9
+ import { type JDMDecision, type JDMNode, NodeType } from './parser';
10
+ import {
11
+ type ExpressionAST,
12
+ type NumberLiteral,
13
+ type StringLiteral,
14
+ type BooleanLiteral,
15
+ isNumberLiteral,
16
+ isStringLiteral,
17
+ isBooleanLiteral,
18
+ isNullLiteral,
19
+ isBinaryOp,
20
+ isUnaryOp,
21
+ isTernaryOp,
22
+ } from './ast-types';
23
+ import { parseStandardExpression, parseUnaryExpression } from './unary-parser';
24
+
25
+ /**
26
+ * Cache for parsed expression ASTs.
27
+ * Maps expression strings to their parsed AST nodes to avoid redundant parsing.
28
+ */
29
+ type ExpressionCache = Map<string, ExpressionAST>;
30
+
31
+ /**
32
+ * Compiled AST - the intermediate representation after parsing.
33
+ *
34
+ * JDMDecision structure is simple enough for in-place transformation, avoiding the
35
+ * complexity and memory overhead of building a separate intermediate representation.
36
+ */
37
+ export type CompiledAST = JDMDecision;
38
+
39
+ /**
40
+ * Interface for an optimization pass.
41
+ *
42
+ * Each optimization pass receives the compiled AST and returns a
43
+ * potentially modified/optimized AST.
44
+ */
45
+ export interface OptimizationPass {
46
+ /** Human-readable name of the optimization pass */
47
+ name: string;
48
+
49
+ /**
50
+ * Run the optimization pass on the given AST.
51
+ *
52
+ * @param ast - The compiled AST to optimize
53
+ * @returns The optimized AST
54
+ */
55
+ run(ast: CompiledAST): CompiledAST;
56
+ }
57
+
58
+ /**
59
+ * Ordered list of optimization passes.
60
+ *
61
+ * Passes have dependencies - one pass may create opportunities for the next. For example,
62
+ * constant folding might convert "if (2 > 3)" to "if (false)", then dead code elimination
63
+ * removes the unreachable branch. Running DCE before folding would miss this optimization.
64
+ */
65
+ export const optimizer: OptimizationPass[] = [
66
+ { name: 'Constant Folding', run: constantFolding },
67
+ { name: 'Dead Code Elimination', run: deadCodeElimination },
68
+ { name: 'Expression Inlining', run: expressionInlining },
69
+ { name: 'Table Indexing', run: tableIndexing },
70
+ ];
71
+
72
+ /**
73
+ * Run all optimization passes sequentially on the given AST.
74
+ *
75
+ * @param ast - The compiled AST to optimize
76
+ * @returns The fully optimized AST
77
+ */
78
+ export function runOptimizations(ast: CompiledAST): CompiledAST {
79
+ let optimized = ast;
80
+
81
+ for (const pass of optimizer) {
82
+ optimized = pass.run(optimized);
83
+ }
84
+
85
+ return optimized;
86
+ }
87
+
88
+ // ============================================================================
89
+ // Expression Cache Utilities
90
+ // ============================================================================
91
+
92
+ /**
93
+ * Get the expression cache for a node, creating it if it doesn't exist.
94
+ */
95
+ function getExpressionCache(node: JDMNode): ExpressionCache {
96
+ if (!node.parsedContent) {
97
+ node.parsedContent = {};
98
+ }
99
+ if (!node.parsedContent._cachedExprs) {
100
+ node.parsedContent._cachedExprs = new Map<string, ExpressionAST>();
101
+ }
102
+ return node.parsedContent._cachedExprs as ExpressionCache;
103
+ }
104
+
105
+ /**
106
+ * Parse an expression with caching.
107
+ * Checks the cache first, and only parses if not found.
108
+ */
109
+ function cachedParseUnary(node: JDMNode, expr: string): ExpressionAST {
110
+ const cache = getExpressionCache(node);
111
+ const cached = cache.get(expr);
112
+ if (cached) {
113
+ return cached;
114
+ }
115
+ const parsed = parseUnaryExpression(expr);
116
+ cache.set(expr, parsed);
117
+ return parsed;
118
+ }
119
+
120
+ /**
121
+ * Parse a standard expression with caching.
122
+ * Checks the cache first, and only parses if not found.
123
+ */
124
+ function cachedParseStandard(node: JDMNode, expr: string): ExpressionAST {
125
+ const cache = getExpressionCache(node);
126
+ const cached = cache.get(expr);
127
+ if (cached) {
128
+ return cached;
129
+ }
130
+ const parsed = parseStandardExpression(expr);
131
+ cache.set(expr, parsed);
132
+ return parsed;
133
+ }
134
+
135
+ /**
136
+ * Clear the expression cache for a node.
137
+ * Should be called when the node's expressions are modified.
138
+ */
139
+ function clearExpressionCache(node: JDMNode): void {
140
+ if (node.parsedContent && node.parsedContent._cachedExprs) {
141
+ node.parsedContent._cachedExprs.clear();
142
+ }
143
+ }
144
+
145
+ // Export individual optimization passes for testing
146
+ export { constantFolding, deadCodeElimination, expressionInlining, tableIndexing };
147
+
148
+ // Export cache utilities for testing
149
+ export { getExpressionCache, clearExpressionCache };
150
+
151
+ // ============================================================================
152
+ // Constant Folding
153
+ // ============================================================================
154
+
155
+ /**
156
+ * Fold constant expressions at compile time.
157
+ *
158
+ * This pass traverses expression ASTs and evaluates them at compile time
159
+ * when possible. Examples:
160
+ * - Replace (2 + 3) with 5
161
+ * - Replace true and false with false
162
+ * - Replace "hello" + " world" with "hello world"
163
+ *
164
+ * @param ast - The compiled AST to optimize
165
+ * @returns The AST with folded constants
166
+ */
167
+ function constantFolding(ast: CompiledAST): CompiledAST {
168
+ // Create a deep copy to avoid mutating the original
169
+ const optimized = JSON.parse(JSON.stringify(ast)) as JDMDecision;
170
+
171
+ // Apply constant folding to each node's expression content
172
+ optimized.nodes = optimized.nodes.map((node: JDMNode) => foldNodeExpressions(node));
173
+
174
+ return optimized;
175
+ }
176
+
177
+ /**
178
+ * Apply constant folding to expressions within a node.
179
+ */
180
+ function foldNodeExpressions(node: JDMNode): JDMNode {
181
+ if (!node.content) {
182
+ return node;
183
+ }
184
+
185
+ const content = node.content;
186
+
187
+ // Handle decision table nodes
188
+ if (node.type === NodeType.DECISION_TABLE) {
189
+ return foldDecisionTableExpressions(node, content);
190
+ }
191
+
192
+ // Handle switch nodes
193
+ if (node.type === NodeType.SWITCH) {
194
+ return foldSwitchExpressions(node, content);
195
+ }
196
+
197
+ // Handle expression nodes
198
+ if (node.type === NodeType.EXPRESSION) {
199
+ return foldExpressionNode(node, content);
200
+ }
201
+
202
+ return node;
203
+ }
204
+
205
+ /**
206
+ * Fold expressions in a decision table node.
207
+ */
208
+ function foldDecisionTableExpressions(node: JDMNode, content: Record<string, unknown>): JDMNode {
209
+ const { rules, inputs } = content as { rules: any[]; inputs: any[] };
210
+ let modified = false;
211
+
212
+ // Update each rule's input conditions with folded expressions
213
+ const foldedRules = rules.map((rule: Record<string, unknown>) => {
214
+ const foldedRule = { ...rule };
215
+
216
+ for (const input of inputs) {
217
+ const expr = rule[input.id] as string | undefined;
218
+ if (!expr || expr === '' || expr === '-') {
219
+ continue;
220
+ }
221
+
222
+ // Parse the expression in unary mode (using cache)
223
+ try {
224
+ const ast = cachedParseUnary(node, expr);
225
+ const folded = foldExpression(ast);
226
+
227
+ // If folding succeeded, update the expression string
228
+ if (folded !== null && isLiteral(folded)) {
229
+ const newExpr = literalToExprString(folded);
230
+ if (newExpr !== expr) {
231
+ foldedRule[input.id] = newExpr;
232
+ modified = true;
233
+ }
234
+ }
235
+ } catch {
236
+ // If expression can't be parsed or folded, keep original
237
+ }
238
+ }
239
+
240
+ return foldedRule;
241
+ });
242
+
243
+ // Clear cache if expressions were modified
244
+ if (modified) {
245
+ clearExpressionCache(node);
246
+ }
247
+
248
+ return {
249
+ ...node,
250
+ content: {
251
+ ...content,
252
+ rules: foldedRules,
253
+ },
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Fold expressions in a switch node.
259
+ */
260
+ function foldSwitchExpressions(node: JDMNode, content: any): JDMNode {
261
+ const { conditions } = content;
262
+ let modified = false;
263
+
264
+ const foldedConditions = conditions.map((condition: any) => {
265
+ const expr = condition.expression;
266
+ if (!expr || expr === '') {
267
+ return condition;
268
+ }
269
+
270
+ try {
271
+ const ast = cachedParseStandard(node, expr);
272
+ const folded = foldExpression(ast);
273
+
274
+ if (folded !== null && isLiteral(folded)) {
275
+ const newExpr = literalToExprString(folded);
276
+ if (newExpr !== expr) {
277
+ modified = true;
278
+ return {
279
+ ...condition,
280
+ expression: newExpr,
281
+ };
282
+ }
283
+ }
284
+ } catch {
285
+ // If expression can't be parsed or folded, keep original
286
+ }
287
+
288
+ return condition;
289
+ });
290
+
291
+ // Clear cache if expressions were modified
292
+ if (modified) {
293
+ clearExpressionCache(node);
294
+ }
295
+
296
+ return {
297
+ ...node,
298
+ content: {
299
+ ...content,
300
+ conditions: foldedConditions,
301
+ },
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Fold expressions in an expression node.
307
+ */
308
+ function foldExpressionNode(node: JDMNode, content: any): JDMNode {
309
+ const expr = content.expression;
310
+ if (!expr) {
311
+ return node;
312
+ }
313
+
314
+ try {
315
+ const ast = cachedParseStandard(node, expr);
316
+ const folded = foldExpression(ast);
317
+
318
+ if (folded !== null && isLiteral(folded)) {
319
+ const newExpr = literalToExprString(folded);
320
+ if (newExpr !== expr) {
321
+ // Clear cache since we're modifying the expression
322
+ clearExpressionCache(node);
323
+ return {
324
+ ...node,
325
+ content: {
326
+ ...content,
327
+ expression: newExpr,
328
+ },
329
+ };
330
+ }
331
+ }
332
+ } catch {
333
+ // If expression can't be parsed or folded, keep original
334
+ }
335
+
336
+ return node;
337
+ }
338
+
339
+ /**
340
+ * Recursively fold an expression AST.
341
+ * Returns the folded AST node, or null if folding is not possible.
342
+ */
343
+ function foldExpression(expr: ExpressionAST): ExpressionAST | null {
344
+ if (isLiteralExpr(expr)) {
345
+ return expr;
346
+ }
347
+
348
+ if (isBinaryOp(expr)) {
349
+ const leftFolded = foldExpression(expr.left);
350
+ const rightFolded = foldExpression(expr.right);
351
+
352
+ // If both sides are literals, try to evaluate at compilation time
353
+ if (leftFolded && rightFolded && isLiteral(leftFolded) && isLiteral(rightFolded)) {
354
+ const result = evaluateConstantBinaryOp(expr.op, leftFolded, rightFolded);
355
+ if (result !== null) {
356
+ return result;
357
+ }
358
+ }
359
+
360
+ return {
361
+ ...expr,
362
+ left: leftFolded || expr.left,
363
+ right: rightFolded || expr.right,
364
+ };
365
+ }
366
+
367
+ if (isUnaryOp(expr)) {
368
+ const operandFolded = foldExpression(expr.operand);
369
+
370
+ if (operandFolded && isLiteral(operandFolded)) {
371
+ const result = evaluateConstantUnaryOp(expr.op, operandFolded);
372
+ if (result !== null) {
373
+ return result;
374
+ }
375
+ }
376
+
377
+ return {
378
+ ...expr,
379
+ operand: operandFolded || expr.operand,
380
+ };
381
+ }
382
+
383
+ if (isTernaryOp(expr)) {
384
+ const conditionFolded = foldExpression(expr.condition);
385
+ const consequentFolded = foldExpression(expr.consequent);
386
+ const alternateFolded = foldExpression(expr.alternate);
387
+
388
+ if (conditionFolded && isLiteral(conditionFolded) && isBooleanLiteral(conditionFolded)) {
389
+ return conditionFolded.value
390
+ ? consequentFolded || expr.consequent
391
+ : alternateFolded || expr.alternate;
392
+ }
393
+
394
+ return {
395
+ ...expr,
396
+ condition: conditionFolded || expr.condition,
397
+ consequent: consequentFolded || expr.consequent,
398
+ alternate: alternateFolded || expr.alternate,
399
+ };
400
+ }
401
+
402
+ // Can't fold other expression types (identifiers, function calls, etc.)
403
+ return null;
404
+ }
405
+
406
+ /**
407
+ * Check if an expression is a literal value.
408
+ */
409
+ function isLiteralExpr(expr: ExpressionAST): boolean {
410
+ return (
411
+ isNumberLiteral(expr) || isStringLiteral(expr) || isBooleanLiteral(expr) || isNullLiteral(expr)
412
+ );
413
+ }
414
+
415
+ /**
416
+ * Check if a node is a literal.
417
+ */
418
+ function isLiteral(
419
+ node: ExpressionAST | null,
420
+ ): node is NumberLiteral | StringLiteral | BooleanLiteral {
421
+ return node !== null && isLiteralExpr(node);
422
+ }
423
+
424
+ /**
425
+ * Evaluate a binary operation with constant operands.
426
+ */
427
+ function evaluateConstantBinaryOp(
428
+ op: string,
429
+ left: NumberLiteral | StringLiteral | BooleanLiteral,
430
+ right: NumberLiteral | StringLiteral | BooleanLiteral,
431
+ ): ExpressionAST | null {
432
+ // Numeric operations
433
+ if (isNumberLiteral(left) && isNumberLiteral(right)) {
434
+ switch (op) {
435
+ case '+':
436
+ return { type: 'NumberLiteral', value: left.value + right.value };
437
+ case '-':
438
+ return { type: 'NumberLiteral', value: left.value - right.value };
439
+ case '*':
440
+ return { type: 'NumberLiteral', value: left.value * right.value };
441
+ case '/':
442
+ return { type: 'NumberLiteral', value: left.value / right.value };
443
+ case '%':
444
+ return { type: 'NumberLiteral', value: left.value % right.value };
445
+ case '^':
446
+ return { type: 'NumberLiteral', value: Math.pow(left.value, right.value) };
447
+ case '<':
448
+ return { type: 'BooleanLiteral', value: left.value < right.value };
449
+ case '>':
450
+ return { type: 'BooleanLiteral', value: left.value > right.value };
451
+ case '<=':
452
+ return { type: 'BooleanLiteral', value: left.value <= right.value };
453
+ case '>=':
454
+ return { type: 'BooleanLiteral', value: left.value >= right.value };
455
+ case '==':
456
+ return { type: 'BooleanLiteral', value: left.value === right.value };
457
+ case '!=':
458
+ return { type: 'BooleanLiteral', value: left.value !== right.value };
459
+ }
460
+ }
461
+
462
+ // String operations
463
+ if (isStringLiteral(left) && isStringLiteral(right)) {
464
+ switch (op) {
465
+ case '+':
466
+ return { type: 'StringLiteral', value: left.value + right.value };
467
+ case '==':
468
+ return { type: 'BooleanLiteral', value: left.value === right.value };
469
+ case '!=':
470
+ return { type: 'BooleanLiteral', value: left.value !== right.value };
471
+ }
472
+ }
473
+
474
+ // Boolean operations
475
+ if (isBooleanLiteral(left) && isBooleanLiteral(right)) {
476
+ switch (op) {
477
+ case 'and':
478
+ return { type: 'BooleanLiteral', value: left.value && right.value };
479
+ case 'or':
480
+ return { type: 'BooleanLiteral', value: left.value || right.value };
481
+ case '==':
482
+ return { type: 'BooleanLiteral', value: left.value === right.value };
483
+ case '!=':
484
+ return { type: 'BooleanLiteral', value: left.value !== right.value };
485
+ }
486
+ }
487
+
488
+ return null;
489
+ }
490
+
491
+ /**
492
+ * Evaluate a unary operation with a constant operand.
493
+ */
494
+ function evaluateConstantUnaryOp(
495
+ op: string,
496
+ operand: NumberLiteral | StringLiteral | BooleanLiteral,
497
+ ): ExpressionAST | null {
498
+ if (op === 'not' && isBooleanLiteral(operand)) {
499
+ return { type: 'BooleanLiteral', value: !operand.value };
500
+ }
501
+
502
+ if (op === '-' && isNumberLiteral(operand)) {
503
+ return { type: 'NumberLiteral', value: -operand.value };
504
+ }
505
+
506
+ return null;
507
+ }
508
+
509
+ /**
510
+ * Convert a literal AST node to its string representation.
511
+ */
512
+ function literalToExprString(node: NumberLiteral | StringLiteral | BooleanLiteral): string {
513
+ switch (node.type) {
514
+ case 'NumberLiteral':
515
+ return String(node.value);
516
+ case 'StringLiteral':
517
+ return `"${node.value}"`;
518
+ case 'BooleanLiteral':
519
+ return String(node.value);
520
+ }
521
+ }
522
+
523
+ // ============================================================================
524
+ // Dead Code Elimination
525
+ // ============================================================================
526
+
527
+ /**
528
+ * Remove unreachable code.
529
+ *
530
+ * This pass identifies and removes branches that can never be reached.
531
+ * Examples:
532
+ * - Remove if branches with constant false conditions
533
+ * - Remove decision table rows with always-false conditions
534
+ * - Remove decision table rows that can never match after previous rows
535
+ *
536
+ * @param ast - The compiled AST to optimize
537
+ * @returns The AST with dead code removed
538
+ */
539
+ function deadCodeElimination(ast: CompiledAST): CompiledAST {
540
+ const optimized = JSON.parse(JSON.stringify(ast)) as JDMDecision;
541
+
542
+ optimized.nodes = optimized.nodes.map((node: JDMNode) => eliminateDeadCodeInNode(node));
543
+
544
+ return optimized;
545
+ }
546
+
547
+ /**
548
+ * Apply dead code elimination to a node.
549
+ */
550
+ function eliminateDeadCodeInNode(node: JDMNode): JDMNode {
551
+ if (!node.content) {
552
+ return node;
553
+ }
554
+
555
+ const content = node.content;
556
+
557
+ if (node.type === NodeType.DECISION_TABLE) {
558
+ return eliminateDeadCodeInDecisionTable(node, content);
559
+ }
560
+
561
+ if (node.type === NodeType.SWITCH) {
562
+ return eliminateDeadCodeInSwitch(node, content);
563
+ }
564
+
565
+ return node;
566
+ }
567
+
568
+ /**
569
+ * Check if a unary expression is always false.
570
+ * In unary mode, "false" is equivalent to "$ == false".
571
+ */
572
+ function isUnaryAlwaysFalse(expr: ExpressionAST): boolean {
573
+ if (isBinaryOp(expr) && expr.op === '==') {
574
+ const leftIsDollar = expr.left.type === 'DollarRef';
575
+ const rightIsFalse = isBooleanLiteral(expr.right) && !expr.right.value;
576
+ return leftIsDollar && rightIsFalse;
577
+ }
578
+
579
+ if (isBinaryOp(expr) && expr.op === '!=') {
580
+ const leftIsDollar = expr.left.type === 'DollarRef';
581
+ const rightIsTrue = isBooleanLiteral(expr.right) && expr.right.value;
582
+ return leftIsDollar && rightIsTrue;
583
+ }
584
+
585
+ return false;
586
+ }
587
+
588
+ /**
589
+ * Eliminate dead code in decision table nodes.
590
+ */
591
+ function eliminateDeadCodeInSwitch(node: JDMNode, content: any): JDMNode {
592
+ const { conditions } = content;
593
+
594
+ // Filter out conditions that are always false
595
+ const liveConditions = conditions.filter((condition: any) => {
596
+ const expr = condition.expression;
597
+ if (!expr || expr === '') {
598
+ return true;
599
+ }
600
+
601
+ try {
602
+ const parsed = cachedParseStandard(node, expr);
603
+
604
+ if (isBooleanLiteral(parsed) && !parsed.value) {
605
+ return false;
606
+ }
607
+ } catch {}
608
+
609
+ return true;
610
+ });
611
+
612
+ return {
613
+ ...node,
614
+ content: {
615
+ ...content,
616
+ conditions: liveConditions,
617
+ },
618
+ };
619
+ }
620
+
621
+ /**
622
+ * Eliminate dead code in decision table nodes.
623
+ */
624
+ function eliminateDeadCodeInDecisionTable(node: JDMNode, content: any): JDMNode {
625
+ const { rules, inputs } = content;
626
+
627
+ // Filter out rules that have a non-trivial always-false condition
628
+ const liveRules = rules.filter((rule: any) => {
629
+ for (const input of inputs) {
630
+ const expr = rule[input.id];
631
+ if (!expr || expr === '' || expr === '-') {
632
+ continue;
633
+ }
634
+
635
+ try {
636
+ const parsed = cachedParseUnary(node, expr);
637
+
638
+ // Check if this is a boolean literal (not common in unary mode, but possible)
639
+ if (isBooleanLiteral(parsed) && !parsed.value) {
640
+ return false;
641
+ }
642
+
643
+ // Check if this is a unary expression that's always false
644
+ // e.g., "false" in a decision table cell is interpreted as "$ == false"
645
+ if (isUnaryAlwaysFalse(parsed)) {
646
+ return false;
647
+ }
648
+ } catch {
649
+ // If we can't parse it, assume the rule is live
650
+ }
651
+ }
652
+
653
+ return true;
654
+ });
655
+
656
+ return {
657
+ ...node,
658
+ content: {
659
+ ...content,
660
+ rules: liveRules,
661
+ },
662
+ };
663
+ }
664
+
665
+ // ============================================================================
666
+ // Expression Inlining
667
+ // ============================================================================
668
+
669
+ /**
670
+ * Inline small expressions.
671
+ *
672
+ * This pass replaces function calls with evaluated results for pure functions.
673
+ * Examples:
674
+ * - Inline small arithmetic operations in decision table cells
675
+ * - Pre-evaluate expressions that don't depend on runtime values
676
+ *
677
+ * @param ast - The compiled AST to optimize
678
+ * @returns The AST with inlined expressions
679
+ */
680
+ function expressionInlining(ast: CompiledAST): CompiledAST {
681
+ // Create a deep copy to avoid mutating the original
682
+ const optimized = JSON.parse(JSON.stringify(ast)) as JDMDecision;
683
+
684
+ // Only functions without side effects can be safely evaluated at compile time. Arithmetic
685
+ // (sum, max, min, abs, floor, ceil, round) and string functions (upper, lower, len) are
686
+ // pure and deterministic, so their results with constant inputs are predictable.
687
+ optimized.nodes = optimized.nodes.map((node: JDMNode) => inlineExpressionsInNode(node));
688
+
689
+ return optimized;
690
+ }
691
+
692
+ /**
693
+ * Apply expression inlining to a node.
694
+ */
695
+ function inlineExpressionsInNode(node: JDMNode): JDMNode {
696
+ if (!node.content) {
697
+ return node;
698
+ }
699
+
700
+ const content = node.content;
701
+
702
+ // Handle decision table nodes
703
+ if (node.type === NodeType.DECISION_TABLE && content && content.rules && content.inputs) {
704
+ return inlineDecisionTableExpressions(node, content);
705
+ }
706
+
707
+ return node;
708
+ }
709
+
710
+ /**
711
+ * Inline expressions in decision table nodes.
712
+ */
713
+ function inlineDecisionTableExpressions(node: JDMNode, content: any): JDMNode {
714
+ const { rules, inputs } = content;
715
+
716
+ const inlinedRules = rules.map((rule: any) => {
717
+ const inlinedRule = { ...rule };
718
+
719
+ for (const input of inputs) {
720
+ const expr = rule[input.id];
721
+ if (!expr || expr === '' || expr === '-') {
722
+ continue;
723
+ }
724
+
725
+ const inlined = tryInlineExpression(expr);
726
+ if (inlined !== expr) {
727
+ inlinedRule[input.id] = inlined;
728
+ }
729
+ }
730
+
731
+ return inlinedRule;
732
+ });
733
+
734
+ return {
735
+ ...node,
736
+ content: {
737
+ ...content,
738
+ rules: inlinedRules,
739
+ },
740
+ };
741
+ }
742
+
743
+ /**
744
+ * Try to inline an expression by evaluating simple pure function calls.
745
+ * Returns the inlined expression string, or the original if inlining is not possible.
746
+ */
747
+ function tryInlineExpression(expr: string): string {
748
+ const trimmed = expr.trim();
749
+
750
+ // Inline simple arithmetic operations wrapped in function notation
751
+ // Examples: sum(2, 3) → 5, min(10, 5) → 5, abs(-5) → 5, upper("hello") → "HELLO"
752
+
753
+ // Match sum(a, b, ...) pattern where all arguments are numeric literals
754
+ const sumMatch = trimmed.match(/^sum\((.+)\)$/i);
755
+ if (sumMatch) {
756
+ const args = sumMatch[1].split(',').map((s: string) => s.trim());
757
+ if (args.every((arg: string) => /^-?\d+\.?\d*$/.test(arg))) {
758
+ const sum = args.reduce((acc: number, arg: string) => acc + parseFloat(arg), 0);
759
+ return String(sum);
760
+ }
761
+ }
762
+
763
+ // Match max(a, b) pattern where both arguments are numeric literals
764
+ const maxMatch = trimmed.match(/^max\((.+),(.+)\)$/i);
765
+ if (maxMatch) {
766
+ const [_, arg1, arg2] = maxMatch;
767
+ const num1 = parseFloat(arg1.trim());
768
+ const num2 = parseFloat(arg2.trim());
769
+ if (!isNaN(num1) && !isNaN(num2)) {
770
+ return String(Math.max(num1, num2));
771
+ }
772
+ }
773
+
774
+ // Match min(a, b) pattern where both arguments are numeric literals
775
+ const minMatch = trimmed.match(/^min\((.+),(.+)\)$/i);
776
+ if (minMatch) {
777
+ const [_, arg1, arg2] = minMatch;
778
+ const num1 = parseFloat(arg1.trim());
779
+ const num2 = parseFloat(arg2.trim());
780
+ if (!isNaN(num1) && !isNaN(num2)) {
781
+ return String(Math.min(num1, num2));
782
+ }
783
+ }
784
+
785
+ // Match abs(a) pattern where argument is a numeric literal
786
+ const absMatch = trimmed.match(/^abs\((.+)\)$/i);
787
+ if (absMatch) {
788
+ const val = parseFloat(absMatch[1].trim());
789
+ if (!isNaN(val)) {
790
+ return String(Math.abs(val));
791
+ }
792
+ }
793
+
794
+ // Match floor(a) pattern where argument is a numeric literal
795
+ const floorMatch = trimmed.match(/^floor\((.+)\)$/i);
796
+ if (floorMatch) {
797
+ const val = parseFloat(floorMatch[1].trim());
798
+ if (!isNaN(val)) {
799
+ return String(Math.floor(val));
800
+ }
801
+ }
802
+
803
+ // Match ceil(a) pattern where argument is a numeric literal
804
+ const ceilMatch = trimmed.match(/^ceil\((.+)\)$/i);
805
+ if (ceilMatch) {
806
+ const val = parseFloat(ceilMatch[1].trim());
807
+ if (!isNaN(val)) {
808
+ return String(Math.ceil(val));
809
+ }
810
+ }
811
+
812
+ // Match round(a) pattern where argument is a numeric literal
813
+ const roundMatch = trimmed.match(/^round\((.+)\)$/i);
814
+ if (roundMatch) {
815
+ const val = parseFloat(roundMatch[1].trim());
816
+ if (!isNaN(val)) {
817
+ return String(Math.round(val));
818
+ }
819
+ }
820
+
821
+ // Match upper("string") pattern for constant string literals
822
+ const upperMatch = trimmed.match(/^upper\("(.+)"\)$/i);
823
+ if (upperMatch) {
824
+ return `"${upperMatch[1].toUpperCase()}"`;
825
+ }
826
+
827
+ // Match lower("string") pattern for constant string literals
828
+ const lowerMatch = trimmed.match(/^lower\("(.+)"\)$/i);
829
+ if (lowerMatch) {
830
+ return `"${lowerMatch[1].toLowerCase()}"`;
831
+ }
832
+
833
+ // Match len("string") pattern for constant string literals
834
+ const lenMatch = trimmed.match(/^len\("(.*)"\)$/i);
835
+ if (lenMatch) {
836
+ return String(lenMatch[1].length);
837
+ }
838
+
839
+ // Match len(123) pattern for numeric literals (counting digits excluding sign)
840
+ const lenNumMatch = trimmed.match(/^len\(-?\d+\.?\d*\)$/i);
841
+ if (lenNumMatch) {
842
+ const numStr = trimmed.match(/^len\((.+)\)$/i)![1].trim();
843
+ // Count digits ignoring sign and decimal point
844
+ const digitCount = numStr.replace(/^-/, '').replace('.', '').length;
845
+ return String(digitCount);
846
+ }
847
+
848
+ return expr;
849
+ }
850
+
851
+ // ============================================================================
852
+ // Table Indexing
853
+ // ============================================================================
854
+
855
+ /**
856
+ * Build index structures for efficient table lookup.
857
+ *
858
+ * This pass transforms linear search structures into more efficient alternatives.
859
+ * Examples:
860
+ * - Convert decision table rows with equality checks to index-based lookup
861
+ * - Mark tables that are good candidates for switch statement generation
862
+ *
863
+ * Table indexing converts O(n) linear search to O(1) hash lookup when all rules check
864
+ * equality on a single column with unique values - this is common in lookup tables
865
+ * (e.g., pricing tiers by customer type) and can improve evaluation time by 10-100x
866
+ * for large tables.
867
+ *
868
+ * @param ast - The compiled AST to optimize
869
+ * @returns The AST with optimized table lookups
870
+ */
871
+ function tableIndexing(ast: CompiledAST): CompiledAST {
872
+ const optimized = JSON.parse(JSON.stringify(ast)) as JDMDecision;
873
+
874
+ optimized.nodes = optimized.nodes.map((node: JDMNode) => indexTableInNode(node));
875
+
876
+ return optimized;
877
+ }
878
+
879
+ /**
880
+ * Apply table indexing to a node.
881
+ */
882
+ function indexTableInNode(node: JDMNode): JDMNode {
883
+ if (!node.content) {
884
+ return node;
885
+ }
886
+
887
+ const content = node.content;
888
+
889
+ if (node.type === NodeType.DECISION_TABLE) {
890
+ return indexDecisionTable(node, content);
891
+ }
892
+
893
+ return node;
894
+ }
895
+
896
+ /**
897
+ * Index decision table for efficient lookup.
898
+ *
899
+ * This pass analyzes the decision table to identify patterns that can be
900
+ * optimized, such as equality checks on a single column that could be
901
+ * converted to a switch statement or hash map.
902
+ */
903
+ function indexDecisionTable(node: JDMNode, content: any): JDMNode {
904
+ const { rules, inputs } = content;
905
+
906
+ // Analyze each input column to find indexing opportunities
907
+ const indexedInputs = inputs.map((input: any) => {
908
+ const inputValues = rules.map((rule: any) => {
909
+ const expr = rule[input.id];
910
+ if (!expr || expr === '' || expr === '-') {
911
+ return null;
912
+ }
913
+ return expr.trim();
914
+ });
915
+
916
+ // Check if this column is a good candidate for indexing:
917
+ // - All values are string or number literals
918
+ // - No intervals or complex expressions
919
+ // - No duplicate values
920
+ const allLiterals = inputValues.every((val: string | null) => {
921
+ if (!val) {
922
+ return true;
923
+ }
924
+ return /^(["'])(?:(?=(\\?))\2.)*?\1$|^-?\d+\.?\d*$/.test(val);
925
+ });
926
+
927
+ const uniqueValues = new Set(inputValues.filter((v: string | null) => v !== null));
928
+ const hasDuplicates =
929
+ uniqueValues.size < inputValues.filter((v: string | null) => v !== null).length;
930
+
931
+ return {
932
+ ...input,
933
+ // Hash-based lookup becomes beneficial with ~10+ unique values, but overhead matters
934
+ // for small tables. 50 is a practical limit where indexing provides clear wins without
935
+ // excessive memory for the lookup structure.
936
+ _indexable: allLiterals && !hasDuplicates && uniqueValues.size > 0 && uniqueValues.size < 50,
937
+ _uniqueValues: Array.from(uniqueValues),
938
+ };
939
+ });
940
+
941
+ return {
942
+ ...node,
943
+ content: {
944
+ ...content,
945
+ inputs: indexedInputs,
946
+ },
947
+ };
948
+ }