@divmain/jdm-asm 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2227 -29
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/.github/workflows/ci.yml +0 -53
- package/.oxfmtrc.json +0 -16
- package/.oxlintrc.json +0 -183
- package/AGENTS.md +0 -81
- package/asconfig.json +0 -23
- package/benchmarks/fixtures.ts +0 -111
- package/benchmarks/input-fixtures.ts +0 -80
- package/benchmarks/run.ts +0 -913
- package/benchmarks/worker-pool.ts +0 -223
- package/benchmarks/worker.ts +0 -374
- package/scripts/run-all-tests.ts +0 -220
- package/src/compiler/EXPRESSION_SUBSETS.md +0 -228
- package/src/compiler/asc-compiler.ts +0 -315
- package/src/compiler/ast-types.ts +0 -215
- package/src/compiler/build.ts +0 -56
- package/src/compiler/cache.ts +0 -414
- package/src/compiler/code-generators.ts +0 -211
- package/src/compiler/codegen/index.ts +0 -15
- package/src/compiler/codegen/js-marshal.ts +0 -999
- package/src/compiler/codegen/js-validation.ts +0 -243
- package/src/compiler/codegen.ts +0 -19
- package/src/compiler/compile-time-validation.ts +0 -507
- package/src/compiler/cst-visitor.ts +0 -434
- package/src/compiler/errors.ts +0 -227
- package/src/compiler/expression-parser.ts +0 -536
- package/src/compiler/graph.ts +0 -197
- package/src/compiler/index.ts +0 -199
- package/src/compiler/input-validation.ts +0 -33
- package/src/compiler/marshal-gen.ts +0 -21
- package/src/compiler/nodes/context-resolvers.ts +0 -197
- package/src/compiler/nodes/decision-table.ts +0 -507
- package/src/compiler/nodes/decision.ts +0 -292
- package/src/compiler/nodes/expression-compiler.ts +0 -526
- package/src/compiler/nodes/expression.ts +0 -425
- package/src/compiler/nodes/function.ts +0 -316
- package/src/compiler/nodes/input.ts +0 -60
- package/src/compiler/nodes/switch.ts +0 -547
- package/src/compiler/optimizer.ts +0 -948
- package/src/compiler/orchestrator.ts +0 -352
- package/src/compiler/parser.ts +0 -115
- package/src/compiler/result-selection.ts +0 -161
- package/src/compiler/runtime/index.ts +0 -26
- package/src/compiler/runtime-codegen.ts +0 -211
- package/src/compiler/runtime-validation-codegen.ts +0 -294
- package/src/compiler/runtime.ts +0 -452
- package/src/compiler/schema.ts +0 -245
- package/src/compiler/switch-branch-detection.ts +0 -92
- package/src/compiler/types.ts +0 -136
- package/src/compiler/unary-ast-transforms.ts +0 -148
- package/src/compiler/unary-parser.ts +0 -301
- package/src/compiler/unary-transform.ts +0 -161
- package/src/compiler/utils.ts +0 -27
- package/src/compiler/virtual-fs.ts +0 -90
- package/src/compiler/wasm-instantiate.ts +0 -127
- package/src/index.ts +0 -1
- package/src/runtime/arrays.ts +0 -579
- package/src/runtime/context.ts +0 -189
- package/src/runtime/expressions.ts +0 -1811
- package/src/runtime/index.ts +0 -8
- package/src/runtime/memory.ts +0 -607
- package/src/runtime/strings.ts +0 -260
- package/src/runtime/tables.ts +0 -96
- package/src/runtime/tsconfig.json +0 -4
- package/src/runtime/values.ts +0 -209
- package/test-data/README.md +0 -83
- package/test-data/decision-tables/basic/8k.json +0 -87992
- package/test-data/decision-tables/basic/affiliate-commission-calculator.json +0 -228
- package/test-data/decision-tables/basic/airline-loyalty-points-calculations.json +0 -285
- package/test-data/decision-tables/basic/airline-upgrade-eligibility.json +0 -466
- package/test-data/decision-tables/basic/auto-insurance-premium-calculator.json +0 -412
- package/test-data/decision-tables/basic/booking-personalization-system.json +0 -553
- package/test-data/decision-tables/basic/care-team-assignment-system.json +0 -585
- package/test-data/decision-tables/basic/claim-validation-system.json +0 -307
- package/test-data/decision-tables/basic/clinical-lab-result-interpreter.json +0 -433
- package/test-data/decision-tables/basic/clinical-treatment-protocol.json +0 -474
- package/test-data/decision-tables/basic/credit-limit-adjustment.json +0 -479
- package/test-data/decision-tables/basic/customer-eligibility-engine.json +0 -551
- package/test-data/decision-tables/basic/customer-lifetime-value.json +0 -200
- package/test-data/decision-tables/basic/customer-onboarding-kyc-verification.json +0 -611
- package/test-data/decision-tables/basic/customer-service-escalation.json +0 -191
- package/test-data/decision-tables/basic/decision-table-discounts.json +0 -168
- package/test-data/decision-tables/basic/decision-table-shipping.json +0 -398
- package/test-data/decision-tables/basic/delivery-route-optimizer.json +0 -271
- package/test-data/decision-tables/basic/device-compatibility-checker.json +0 -303
- package/test-data/decision-tables/basic/disaster-relief-fund-allocation.json +0 -296
- package/test-data/decision-tables/basic/dynamic-fx-rate-pricing-system.json +0 -237
- package/test-data/decision-tables/basic/dynamic-marketplace-comission-calculator.json +0 -242
- package/test-data/decision-tables/basic/dynamic-shipping-cost-calculator.json +0 -378
- package/test-data/decision-tables/basic/dynamic-tarrif-engine.json +0 -289
- package/test-data/decision-tables/basic/dynamic-ticket-pricing.json +0 -325
- package/test-data/decision-tables/basic/empty-column-with-space.json +0 -100
- package/test-data/decision-tables/basic/empty-column-without-space.json +0 -100
- package/test-data/decision-tables/basic/environment-compliance-assessment.json +0 -386
- package/test-data/decision-tables/basic/expression-table-map.json +0 -313
- package/test-data/decision-tables/basic/flash-sale-eligibility.json +0 -366
- package/test-data/decision-tables/basic/flight-dispatch-decision-system.json +0 -455
- package/test-data/decision-tables/basic/flight-rebooking-fee-calculator.json +0 -406
- package/test-data/decision-tables/basic/government-assistance.json +0 -299
- package/test-data/decision-tables/basic/grant-funding-distribution.json +0 -307
- package/test-data/decision-tables/basic/hazardous-materials-management-system.json +0 -414
- package/test-data/decision-tables/basic/immigration-eligibility-evaluator.json +0 -765
- package/test-data/decision-tables/basic/import-duties-calculator.json +0 -318
- package/test-data/decision-tables/basic/insurance-agent-commission.json +0 -228
- package/test-data/decision-tables/basic/insurance-coverage-calculator.json +0 -362
- package/test-data/decision-tables/basic/insurance-underwriting-risk.json +0 -321
- package/test-data/decision-tables/basic/international-roaming-policy-manager.json +0 -199
- package/test-data/decision-tables/basic/legacy-plan-management.json +0 -434
- package/test-data/decision-tables/basic/marketplace-listing-verification-system.json +0 -334
- package/test-data/decision-tables/basic/medication-dosage-calculator.json +0 -318
- package/test-data/decision-tables/basic/merch-bags.json +0 -171
- package/test-data/decision-tables/basic/municipal-permit-evaluation-system.json +0 -364
- package/test-data/decision-tables/basic/mvno-partner-enablement.json +0 -313
- package/test-data/decision-tables/basic/partner-revenue-sharing.json +0 -244
- package/test-data/decision-tables/basic/payment-routing-and-fee-calculator.json +0 -475
- package/test-data/decision-tables/basic/policy-discount-calculator.json +0 -307
- package/test-data/decision-tables/basic/policy-eligibility-analyzer.json +0 -299
- package/test-data/decision-tables/basic/product-listing-scoring.json +0 -358
- package/test-data/decision-tables/basic/realtime-fraud-detection.json +0 -235
- package/test-data/decision-tables/basic/regional-compliance-manager.json +0 -278
- package/test-data/decision-tables/basic/returns-and-refund-policy.json +0 -366
- package/test-data/decision-tables/basic/returns-processing-system.json +0 -448
- package/test-data/decision-tables/basic/school-district-resource-allocation.json +0 -282
- package/test-data/decision-tables/basic/seat-map-optimization.json +0 -325
- package/test-data/decision-tables/basic/seller-fee-calculator.json +0 -307
- package/test-data/decision-tables/basic/service-level-agreement-enforcement.json +0 -575
- package/test-data/decision-tables/basic/smart-financial-product-matcher.json +0 -249
- package/test-data/decision-tables/basic/supply-chain-risk.json +0 -316
- package/test-data/decision-tables/basic/table-loop.json +0 -93
- package/test-data/decision-tables/basic/table.json +0 -76
- package/test-data/decision-tables/basic/traffic-violation-penalty-calculator.json +0 -436
- package/test-data/decision-tables/basic/transaction-compliance-classifier.json +0 -525
- package/test-data/decision-tables/basic/vehicle-claims-resolution.json +0 -310
- package/test-data/decision-tables/basic/warehouse-storage-location.json +0 -345
- package/test-data/decision-tables/hit-policy-collect/collect-multiple-matches.json +0 -127
- package/test-data/decision-tables/hit-policy-collect/collect-no-match.json +0 -95
- package/test-data/decision-tables/hit-policy-first/first-match.json +0 -103
- package/test-data/decision-tables/hit-policy-first/no-match.json +0 -95
- package/test-data/decision-tables/hit-policy-output-order/output-order-respected.json +0 -94
- package/test-data/decision-tables/hit-policy-output-order/string-output-order.json +0 -94
- package/test-data/decision-tables/hit-policy-priority/priority-respected.json +0 -86
- package/test-data/decision-tables/hit-policy-rule-order/rule-order-respected.json +0 -94
- package/test-data/decision-tables/hit-policy-unique/all-match-error.json +0 -89
- package/test-data/decision-tables/hit-policy-unique/multiple-match-error.json +0 -89
- package/test-data/decision-tables/hit-policy-unique/no-match.json +0 -88
- package/test-data/decision-tables/hit-policy-unique/unique-match.json +0 -99
- package/test-data/expressions/arithmetic/error-cyclic.json +0 -114
- package/test-data/expressions/arithmetic/error-missing-input.json +0 -54
- package/test-data/expressions/arithmetic/error-missing-output.json +0 -54
- package/test-data/expressions/arithmetic/expression-default.json +0 -93
- package/test-data/expressions/arithmetic/expression-fields.json +0 -94
- package/test-data/expressions/arithmetic/expression-loop.json +0 -94
- package/test-data/expressions/arithmetic/expression-passthrough.json +0 -108
- package/test-data/expressions/arithmetic/expression.json +0 -69
- package/test-data/expressions/arithmetic/nested-request.json +0 -125
- package/test-data/expressions/arithmetic/number-function.json +0 -58
- package/test-data/expressions/arithmetic/test-number-functions.json +0 -68
- package/test-data/expressions/functions/all.json +0 -149
- package/test-data/expressions/functions/avg.json +0 -89
- package/test-data/expressions/functions/filter.json +0 -109
- package/test-data/expressions/functions/flat.json +0 -167
- package/test-data/expressions/functions/map-strings.json +0 -65
- package/test-data/expressions/functions/map.json +0 -73
- package/test-data/expressions/functions/reduce.json +0 -49
- package/test-data/expressions/functions/some.json +0 -175
- package/test-data/expressions/functions/sort-strings.json +0 -97
- package/test-data/expressions/functions/sort.json +0 -97
- package/test-data/expressions/logical/logical-and.json +0 -116
- package/test-data/expressions/logical/logical-complex.json +0 -260
- package/test-data/expressions/logical/logical-not.json +0 -111
- package/test-data/expressions/logical/logical-or.json +0 -123
- package/test-data/expressions/string/string-comparison.json +0 -128
- package/test-data/expressions/string/string-concat.json +0 -106
- package/test-data/expressions/string/string-contains.json +0 -125
- package/test-data/expressions/string/string-endsWith.json +0 -113
- package/test-data/expressions/string/string-indexOf.json +0 -131
- package/test-data/expressions/string/string-join.json +0 -92
- package/test-data/expressions/string/string-lower.json +0 -94
- package/test-data/expressions/string/string-replace.json +0 -130
- package/test-data/expressions/string/string-split.json +0 -101
- package/test-data/expressions/string/string-startsWith.json +0 -113
- package/test-data/expressions/string/string-substring.json +0 -138
- package/test-data/expressions/string/string-trim.json +0 -100
- package/test-data/expressions/string/string-upper.json +0 -94
- package/test-data/other/custom.json +0 -51
- package/test-data/other/customer-input-schema.json +0 -34
- package/test-data/other/customer-output-schema.json +0 -34
- package/test-data/other/passthrough.json +0 -31
- package/test-data/sub-decisions/basic/$nodes-child.json +0 -31
- package/test-data/sub-decisions/basic/$nodes-parent.json +0 -49
- package/test-data/sub-decisions/basic/recursive-table1.json +0 -49
- package/test-data/sub-decisions/basic/recursive-table2.json +0 -49
- package/test-data/sub-decisions/complex-multi/approval-decision.json +0 -31
- package/test-data/sub-decisions/complex-multi/complex-dag.json +0 -175
- package/test-data/sub-decisions/complex-multi/credit-check.json +0 -31
- package/test-data/sub-decisions/complex-multi/customer-segmentation.json +0 -31
- package/test-data/sub-decisions/complex-multi/discount-eligibility.json +0 -31
- package/test-data/sub-decisions/complex-multi/eligibility-check.json +0 -31
- package/test-data/sub-decisions/complex-multi/final-offer.json +0 -31
- package/test-data/sub-decisions/complex-multi/income-verification.json +0 -31
- package/test-data/sub-decisions/complex-multi/linear-chain.json +0 -121
- package/test-data/sub-decisions/complex-multi/pricing-calculation.json +0 -31
- package/test-data/sub-decisions/complex-multi/product-eligibility.json +0 -31
- package/test-data/sub-decisions/complex-multi/risk-assessment.json +0 -31
- package/test-data/sub-decisions/complex-multi/shared-validation.json +0 -31
- package/test-data/sub-decisions/complex-multi/validation.json +0 -31
- package/test-data/sub-decisions/diamond/decision-a.json +0 -31
- package/test-data/sub-decisions/diamond/decision-b.json +0 -31
- package/test-data/sub-decisions/diamond/decision-c.json +0 -31
- package/test-data/sub-decisions/diamond/decision-shared.json +0 -31
- package/test-data/sub-decisions/diamond/diamond-pattern.json +0 -109
- package/test-data/sub-decisions/error-propagation/parent-calls-error.json +0 -44
- package/test-data/sub-decisions/error-propagation/sub-decision-with-error.json +0 -60
- package/test-data/switch-nodes/basic/account-dormancy-management.json +0 -245
- package/test-data/switch-nodes/basic/application-risk-assessment.json +0 -474
- package/test-data/switch-nodes/basic/cellular-data-rollover-system.json +0 -281
- package/test-data/switch-nodes/basic/clinical-pathway-selection.json +0 -454
- package/test-data/switch-nodes/basic/insurance-prior-authorization.json +0 -467
- package/test-data/switch-nodes/basic/last-mile-delivery-assignment.json +0 -373
- package/test-data/switch-nodes/basic/loan-approval.json +0 -469
- package/test-data/switch-nodes/basic/multi-switch.json +0 -498
- package/test-data/switch-nodes/basic/online-checkin-eligibility.json +0 -285
- package/test-data/switch-nodes/basic/order-consolidation-system.json +0 -493
- package/test-data/switch-nodes/basic/seller-approval-workflow.json +0 -383
- package/test-data/switch-nodes/basic/set-fee.json +0 -243
- package/test-data/switch-nodes/basic/shipping-carrier-selector.json +0 -379
- package/test-data/switch-nodes/basic/switch-node.json +0 -167
- package/test-data/switch-nodes/basic/switch-performance-2.json +0 -1307
- package/test-data/switch-nodes/basic/switch-performance.json +0 -691
- package/test-data/switch-nodes/basic/tax-exemption.json +0 -295
- package/test-data/switch-nodes/basic/warehouse-cross-docking.json +0 -313
- package/test-data/switch-nodes/default-cases/switch-with-default.json +0 -134
- package/test-data/zen-reference/$nodes-child.json +0 -69
- package/test-data/zen-reference/$nodes-parent.json +0 -34
- package/test-data/zen-reference/8k.json +0 -87992
- package/test-data/zen-reference/credit-analysis.json +0 -324
- package/test-data/zen-reference/custom.json +0 -51
- package/test-data/zen-reference/customer-input-schema.json +0 -34
- package/test-data/zen-reference/customer-output-schema.json +0 -34
- package/test-data/zen-reference/error-cyclic.json +0 -114
- package/test-data/zen-reference/error-missing-input.json +0 -54
- package/test-data/zen-reference/error-missing-output.json +0 -54
- package/test-data/zen-reference/expression.json +0 -69
- package/test-data/zen-reference/function-v2.json +0 -48
- package/test-data/zen-reference/function.json +0 -46
- package/test-data/zen-reference/graphs/account-dormancy-management.json +0 -245
- package/test-data/zen-reference/graphs/affiliate-commission-calculator.json +0 -228
- package/test-data/zen-reference/graphs/airline-loyalty-points-calculations.json +0 -285
- package/test-data/zen-reference/graphs/airline-upgrade-eligibility.json +0 -466
- package/test-data/zen-reference/graphs/aml.json +0 -537
- package/test-data/zen-reference/graphs/application-risk-assessment.json +0 -474
- package/test-data/zen-reference/graphs/auto-insurance-premium-calculator.json +0 -412
- package/test-data/zen-reference/graphs/booking-personalization-system.json +0 -553
- package/test-data/zen-reference/graphs/care-team-assignment-system.json +0 -585
- package/test-data/zen-reference/graphs/cellular-data-rollover-system.json +0 -281
- package/test-data/zen-reference/graphs/claim-validation-system.json +0 -307
- package/test-data/zen-reference/graphs/clinical-lab-result-interpreter.json +0 -433
- package/test-data/zen-reference/graphs/clinical-pathway-selection.json +0 -454
- package/test-data/zen-reference/graphs/clinical-treatment-protocol.json +0 -474
- package/test-data/zen-reference/graphs/company-analysis.json +0 -390
- package/test-data/zen-reference/graphs/credit-limit-adjustment.json +0 -479
- package/test-data/zen-reference/graphs/customer-eligibility-engine.json +0 -551
- package/test-data/zen-reference/graphs/customer-lifetime-value.json +0 -200
- package/test-data/zen-reference/graphs/customer-onboarding-kyc-verification.json +0 -611
- package/test-data/zen-reference/graphs/customer-service-escalation.json +0 -191
- package/test-data/zen-reference/graphs/decision-table-discounts.json +0 -168
- package/test-data/zen-reference/graphs/decision-table-shipping.json +0 -398
- package/test-data/zen-reference/graphs/delivery-route-optimizer.json +0 -271
- package/test-data/zen-reference/graphs/device-compatibility-checker.json +0 -303
- package/test-data/zen-reference/graphs/disaster-relief-fund-allocation.json +0 -296
- package/test-data/zen-reference/graphs/dynamic-fx-rate-pricing-system.json +0 -237
- package/test-data/zen-reference/graphs/dynamic-marketplace-comission-calculator.json +0 -242
- package/test-data/zen-reference/graphs/dynamic-shipping-cost-calculator.json +0 -378
- package/test-data/zen-reference/graphs/dynamic-tarrif-engine.json +0 -289
- package/test-data/zen-reference/graphs/dynamic-ticket-pricing.json +0 -325
- package/test-data/zen-reference/graphs/empty-column-with-space.json +0 -100
- package/test-data/zen-reference/graphs/empty-column-without-space.json +0 -100
- package/test-data/zen-reference/graphs/environment-compliance-assessment.json +0 -386
- package/test-data/zen-reference/graphs/expression-default.json +0 -93
- package/test-data/zen-reference/graphs/expression-fields.json +0 -94
- package/test-data/zen-reference/graphs/expression-loop.json +0 -94
- package/test-data/zen-reference/graphs/expression-passthrough.json +0 -108
- package/test-data/zen-reference/graphs/expression-table-map.json +0 -313
- package/test-data/zen-reference/graphs/flash-sale-eligibility.json +0 -366
- package/test-data/zen-reference/graphs/flight-dispatch-decision-system.json +0 -455
- package/test-data/zen-reference/graphs/flight-rebooking-fee-calculator.json +0 -406
- package/test-data/zen-reference/graphs/government-assistance.json +0 -299
- package/test-data/zen-reference/graphs/grant-funding-distribution.json +0 -307
- package/test-data/zen-reference/graphs/hazardous-materials-management-system.json +0 -414
- package/test-data/zen-reference/graphs/immigration-eligibility-evaluator.json +0 -765
- package/test-data/zen-reference/graphs/import-duties-calculator.json +0 -318
- package/test-data/zen-reference/graphs/insurance-agent-commission.json +0 -228
- package/test-data/zen-reference/graphs/insurance-breakdown.json +0 -421
- package/test-data/zen-reference/graphs/insurance-coverage-calculator.json +0 -362
- package/test-data/zen-reference/graphs/insurance-prior-authorization.json +0 -467
- package/test-data/zen-reference/graphs/insurance-underwriting-risk.json +0 -321
- package/test-data/zen-reference/graphs/international-roaming-policy-manager.json +0 -199
- package/test-data/zen-reference/graphs/last-mile-delivery-assignment.json +0 -373
- package/test-data/zen-reference/graphs/legacy-plan-management.json +0 -434
- package/test-data/zen-reference/graphs/loan-approval.json +0 -469
- package/test-data/zen-reference/graphs/marketplace-listing-verification-system.json +0 -334
- package/test-data/zen-reference/graphs/medication-dosage-calculator.json +0 -318
- package/test-data/zen-reference/graphs/merch-bags.json +0 -171
- package/test-data/zen-reference/graphs/multi-switch.json +0 -498
- package/test-data/zen-reference/graphs/municipal-permit-evaluation-system.json +0 -364
- package/test-data/zen-reference/graphs/mvno-partner-enablement.json +0 -313
- package/test-data/zen-reference/graphs/nested-request.json +0 -125
- package/test-data/zen-reference/graphs/online-checkin-eligibility.json +0 -285
- package/test-data/zen-reference/graphs/order-consolidation-system.json +0 -493
- package/test-data/zen-reference/graphs/partner-revenue-sharing.json +0 -244
- package/test-data/zen-reference/graphs/payment-routing-and-fee-calculator.json +0 -475
- package/test-data/zen-reference/graphs/policy-discount-calculator.json +0 -307
- package/test-data/zen-reference/graphs/policy-eligibility-analyzer.json +0 -299
- package/test-data/zen-reference/graphs/product-listing-scoring.json +0 -358
- package/test-data/zen-reference/graphs/realtime-fraud-detection.json +0 -235
- package/test-data/zen-reference/graphs/regional-compliance-manager.json +0 -278
- package/test-data/zen-reference/graphs/returns-and-refund-policy.json +0 -366
- package/test-data/zen-reference/graphs/returns-processing-system.json +0 -448
- package/test-data/zen-reference/graphs/school-district-resource-allocation.json +0 -282
- package/test-data/zen-reference/graphs/seat-map-optimization.json +0 -325
- package/test-data/zen-reference/graphs/seller-approval-workflow.json +0 -383
- package/test-data/zen-reference/graphs/seller-fee-calculator.json +0 -307
- package/test-data/zen-reference/graphs/service-level-agreement-enforcement.json +0 -575
- package/test-data/zen-reference/graphs/set-fee.json +0 -243
- package/test-data/zen-reference/graphs/shipping-carrier-selector.json +0 -379
- package/test-data/zen-reference/graphs/smart-financial-product-matcher.json +0 -249
- package/test-data/zen-reference/graphs/supply-chain-risk.json +0 -316
- package/test-data/zen-reference/graphs/table-loop.json +0 -93
- package/test-data/zen-reference/graphs/tax-exemption.json +0 -295
- package/test-data/zen-reference/graphs/traffic-violation-penalty-calculator.json +0 -436
- package/test-data/zen-reference/graphs/transaction-compliance-classifier.json +0 -525
- package/test-data/zen-reference/graphs/vehicle-claims-resolution.json +0 -310
- package/test-data/zen-reference/graphs/warehouse-cross-docking.json +0 -313
- package/test-data/zen-reference/graphs/warehouse-storage-location.json +0 -345
- package/test-data/zen-reference/http-function.json +0 -34
- package/test-data/zen-reference/infinite-function.json +0 -46
- package/test-data/zen-reference/js/imports.js +0 -25
- package/test-data/zen-reference/passthrough.json +0 -31
- package/test-data/zen-reference/recursive-table1.json +0 -49
- package/test-data/zen-reference/recursive-table2.json +0 -49
- package/test-data/zen-reference/sleep-function.json +0 -34
- package/test-data/zen-reference/switch-node.json +0 -167
- package/test-data/zen-reference/switch-performance-2.json +0 -1307
- package/test-data/zen-reference/switch-performance.json +0 -691
- package/test-data/zen-reference/table.json +0 -76
- package/tests/helpers/index.ts +0 -73
- package/tests/helpers/mock-context.ts +0 -231
- package/tests/helpers/round-trip.ts +0 -398
- package/tests/helpers/test-harness-comparison.ts +0 -325
- package/tests/helpers/test-harness-wasm.ts +0 -710
- package/tests/helpers/test-harness.ts +0 -28
- package/tests/helpers/wasm-test.ts +0 -659
- package/tests/integration/compilation-errors.test.ts +0 -864
- package/tests/integration/decision-tables.test.ts +0 -531
- package/tests/integration/edge-cases.test.ts +0 -787
- package/tests/integration/expressions.test.ts +0 -513
- package/tests/integration/function-node-integration.test.ts +0 -182
- package/tests/integration/sub-decisions.test.ts +0 -108
- package/tests/integration/switch-nodes.test.ts +0 -399
- package/tests/integration/unary-or-matching.test.ts +0 -53
- package/tests/integration/wasm-data-types.test.ts +0 -398
- package/tests/integration/wasm-errors.test.ts +0 -199
- package/tests/integration/wasm-execution.test.ts +0 -348
- package/tests/integration/wasm-memory.test.ts +0 -228
- package/tests/scripts/analyze-coverage.ts +0 -166
- package/tests/scripts/categorize-tests.ts +0 -396
- package/tests/scripts/coverage-analysis.ts +0 -836
- package/tests/unit/compiler/cache.test.ts +0 -238
- package/tests/unit/compiler/errors.test.ts +0 -316
- package/tests/unit/compiler/graph-scalability.test.ts +0 -510
- package/tests/unit/compiler/graph.test.ts +0 -878
- package/tests/unit/compiler/input-validation.test.ts +0 -447
- package/tests/unit/compiler/logical-and-parser.test.ts +0 -143
- package/tests/unit/compiler/logical-not-parser.test.ts +0 -107
- package/tests/unit/compiler/logical-or-parser.test.ts +0 -236
- package/tests/unit/compiler/marshal-gen/marshal-gen.test.ts +0 -97
- package/tests/unit/compiler/nodes/decision-table.test.ts +0 -103
- package/tests/unit/compiler/nodes/decision.test.ts +0 -182
- package/tests/unit/compiler/nodes/function-compile.test.ts +0 -204
- package/tests/unit/compiler/nodes/function.test.ts +0 -176
- package/tests/unit/compiler/nodes/input.test.ts +0 -30
- package/tests/unit/compiler/nodes/switch.test.ts +0 -127
- package/tests/unit/compiler/optimizer-cache.test.ts +0 -327
- package/tests/unit/compiler/optimizer-implementation.test.ts +0 -625
- package/tests/unit/compiler/parser.test.ts +0 -508
- package/tests/unit/compiler/runtime-error-cleanup.test.ts +0 -426
- package/tests/unit/compiler/runtime-validation.test.ts +0 -303
- package/tests/unit/compiler/runtime.test.ts +0 -221
- package/tests/unit/compiler/schema/schema.test.ts +0 -248
- package/tests/unit/compiler/unary-ast-transforms.test.ts +0 -245
- package/tsconfig.json +0 -27
- package/tsup.config.ts +0 -11
- package/vitest.config.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -8490,41 +8490,2239 @@ import { createHash } from "crypto";
|
|
|
8490
8490
|
import {
|
|
8491
8491
|
existsSync,
|
|
8492
8492
|
mkdirSync,
|
|
8493
|
-
readFileSync
|
|
8493
|
+
readFileSync,
|
|
8494
8494
|
writeFileSync,
|
|
8495
8495
|
readdirSync,
|
|
8496
8496
|
statSync,
|
|
8497
8497
|
unlinkSync
|
|
8498
8498
|
} from "fs";
|
|
8499
|
-
import { join, dirname
|
|
8500
|
-
|
|
8501
|
-
//
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8499
|
+
import { join, dirname } from "path";
|
|
8500
|
+
|
|
8501
|
+
// inline-runtime:inline-runtime
|
|
8502
|
+
var RUNTIME_VALUES = `// assembly/runtime/values.ts
|
|
8503
|
+
// Type tags for runtime type checking
|
|
8504
|
+
export const TYPE_NULL: i32 = 0;
|
|
8505
|
+
export const TYPE_BOOL: i32 = 1;
|
|
8506
|
+
export const TYPE_INT: i32 = 2;
|
|
8507
|
+
export const TYPE_FLOAT: i32 = 3;
|
|
8508
|
+
export const TYPE_STRING: i32 = 4;
|
|
8509
|
+
export const TYPE_ARRAY: i32 = 5;
|
|
8510
|
+
export const TYPE_OBJECT: i32 = 6;
|
|
8511
|
+
|
|
8512
|
+
// Pre-allocated singleton values for frequently used immutable types.
|
|
8513
|
+
// This avoids allocations in hot paths for null/boolean/small integer/common float values.
|
|
8514
|
+
let _nullSingleton: Value | null = null;
|
|
8515
|
+
let _trueSingleton: Value | null = null;
|
|
8516
|
+
let _falseSingleton: Value | null = null;
|
|
8517
|
+
let _emptyStringSingleton: Value | null = null;
|
|
8518
|
+
let _zeroFloatSingleton: Value | null = null;
|
|
8519
|
+
let _oneFloatSingleton: Value | null = null;
|
|
8520
|
+
|
|
8521
|
+
// Cache for small integers (-1 to 10) - covers common array indices, loop counters, etc.
|
|
8522
|
+
// Using 12-element array: index 0 = -1, index 1 = 0, ..., index 11 = 10
|
|
8523
|
+
const SMALL_INT_CACHE_MIN: i64 = -1;
|
|
8524
|
+
const SMALL_INT_CACHE_MAX: i64 = 10;
|
|
8525
|
+
const SMALL_INT_CACHE_SIZE: i32 = 12;
|
|
8526
|
+
let _smallIntCache: StaticArray<Value | null> | null = null;
|
|
8527
|
+
|
|
8528
|
+
/**
|
|
8529
|
+
* Tagged union for dynamic JSON-like values.
|
|
8530
|
+
* Only one field is valid based on the \`type\` tag.
|
|
8531
|
+
*/
|
|
8532
|
+
export class Value {
|
|
8533
|
+
type: i32 = TYPE_NULL;
|
|
8534
|
+
boolVal: bool = false;
|
|
8535
|
+
intVal: i64 = 0;
|
|
8536
|
+
floatVal: f64 = 0.0;
|
|
8537
|
+
stringVal: string | null = null;
|
|
8538
|
+
arrayVal: Array<Value> | null = null;
|
|
8539
|
+
objectVal: Map<string, Value> | null = null;
|
|
8540
|
+
|
|
8541
|
+
static Null(): Value {
|
|
8542
|
+
// Return singleton null value to avoid allocations
|
|
8543
|
+
if (_nullSingleton === null) {
|
|
8544
|
+
const v = new Value();
|
|
8545
|
+
v.type = TYPE_NULL;
|
|
8546
|
+
_nullSingleton = v;
|
|
8547
|
+
}
|
|
8548
|
+
return _nullSingleton!;
|
|
8549
|
+
}
|
|
8550
|
+
|
|
8551
|
+
static Bool(val: bool): Value {
|
|
8552
|
+
// Return singleton boolean values to avoid allocations
|
|
8553
|
+
if (val) {
|
|
8554
|
+
if (_trueSingleton === null) {
|
|
8555
|
+
const v = new Value();
|
|
8556
|
+
v.type = TYPE_BOOL;
|
|
8557
|
+
v.boolVal = true;
|
|
8558
|
+
_trueSingleton = v;
|
|
8559
|
+
}
|
|
8560
|
+
return _trueSingleton!;
|
|
8561
|
+
} else {
|
|
8562
|
+
if (_falseSingleton === null) {
|
|
8563
|
+
const v = new Value();
|
|
8564
|
+
v.type = TYPE_BOOL;
|
|
8565
|
+
v.boolVal = false;
|
|
8566
|
+
_falseSingleton = v;
|
|
8567
|
+
}
|
|
8568
|
+
return _falseSingleton!;
|
|
8569
|
+
}
|
|
8570
|
+
}
|
|
8571
|
+
|
|
8572
|
+
static Int(val: i64): Value {
|
|
8573
|
+
// Fast path for common small integers to avoid allocations
|
|
8574
|
+
if (val >= SMALL_INT_CACHE_MIN && val <= SMALL_INT_CACHE_MAX) {
|
|
8575
|
+
if (_smallIntCache === null) {
|
|
8576
|
+
_smallIntCache = new StaticArray<Value | null>(SMALL_INT_CACHE_SIZE);
|
|
8577
|
+
}
|
|
8578
|
+
const idx = <i32>(val - SMALL_INT_CACHE_MIN);
|
|
8579
|
+
if (unchecked(_smallIntCache![idx]) === null) {
|
|
8580
|
+
const v = new Value();
|
|
8581
|
+
v.type = TYPE_INT;
|
|
8582
|
+
v.intVal = val;
|
|
8583
|
+
unchecked((_smallIntCache![idx] = v));
|
|
8584
|
+
}
|
|
8585
|
+
return unchecked(_smallIntCache![idx])!;
|
|
8586
|
+
}
|
|
8587
|
+
const v = new Value();
|
|
8588
|
+
v.type = TYPE_INT;
|
|
8589
|
+
v.intVal = val;
|
|
8590
|
+
return v;
|
|
8591
|
+
}
|
|
8592
|
+
|
|
8593
|
+
static Float(val: f64): Value {
|
|
8594
|
+
// Fast path for common float values (0.0, 1.0) to avoid allocations
|
|
8595
|
+
if (val == 0.0) {
|
|
8596
|
+
if (_zeroFloatSingleton === null) {
|
|
8597
|
+
const v = new Value();
|
|
8598
|
+
v.type = TYPE_FLOAT;
|
|
8599
|
+
v.floatVal = 0.0;
|
|
8600
|
+
_zeroFloatSingleton = v;
|
|
8601
|
+
}
|
|
8602
|
+
return _zeroFloatSingleton!;
|
|
8603
|
+
}
|
|
8604
|
+
if (val == 1.0) {
|
|
8605
|
+
if (_oneFloatSingleton === null) {
|
|
8606
|
+
const v = new Value();
|
|
8607
|
+
v.type = TYPE_FLOAT;
|
|
8608
|
+
v.floatVal = 1.0;
|
|
8609
|
+
_oneFloatSingleton = v;
|
|
8610
|
+
}
|
|
8611
|
+
return _oneFloatSingleton!;
|
|
8612
|
+
}
|
|
8613
|
+
const v = new Value();
|
|
8614
|
+
v.type = TYPE_FLOAT;
|
|
8615
|
+
v.floatVal = val;
|
|
8616
|
+
return v;
|
|
8617
|
+
}
|
|
8618
|
+
|
|
8619
|
+
static String(val: string): Value {
|
|
8620
|
+
// Fast path for empty string to avoid allocations
|
|
8621
|
+
if (val.length == 0) {
|
|
8622
|
+
if (_emptyStringSingleton === null) {
|
|
8623
|
+
const v = new Value();
|
|
8624
|
+
v.type = TYPE_STRING;
|
|
8625
|
+
v.stringVal = '';
|
|
8626
|
+
_emptyStringSingleton = v;
|
|
8627
|
+
}
|
|
8628
|
+
return _emptyStringSingleton!;
|
|
8629
|
+
}
|
|
8630
|
+
const v = new Value();
|
|
8631
|
+
v.type = TYPE_STRING;
|
|
8632
|
+
v.stringVal = val;
|
|
8633
|
+
return v;
|
|
8634
|
+
}
|
|
8635
|
+
|
|
8636
|
+
static Array(val: Array<Value>): Value {
|
|
8637
|
+
const v = new Value();
|
|
8638
|
+
v.type = TYPE_ARRAY;
|
|
8639
|
+
v.arrayVal = val;
|
|
8640
|
+
return v;
|
|
8641
|
+
}
|
|
8642
|
+
|
|
8643
|
+
static Object(val: Map<string, Value>): Value {
|
|
8644
|
+
const v = new Value();
|
|
8645
|
+
v.type = TYPE_OBJECT;
|
|
8646
|
+
v.objectVal = val;
|
|
8647
|
+
return v;
|
|
8648
|
+
}
|
|
8649
|
+
|
|
8650
|
+
// Type checking
|
|
8651
|
+
isNull(): bool {
|
|
8652
|
+
return this.type == TYPE_NULL;
|
|
8653
|
+
}
|
|
8654
|
+
isBool(): bool {
|
|
8655
|
+
return this.type == TYPE_BOOL;
|
|
8656
|
+
}
|
|
8657
|
+
isNumber(): bool {
|
|
8658
|
+
return this.type == TYPE_INT || this.type == TYPE_FLOAT;
|
|
8659
|
+
}
|
|
8660
|
+
isString(): bool {
|
|
8661
|
+
return this.type == TYPE_STRING;
|
|
8662
|
+
}
|
|
8663
|
+
isArray(): bool {
|
|
8664
|
+
return this.type == TYPE_ARRAY;
|
|
8665
|
+
}
|
|
8666
|
+
isObject(): bool {
|
|
8667
|
+
return this.type == TYPE_OBJECT;
|
|
8668
|
+
}
|
|
8669
|
+
|
|
8670
|
+
// Returning defaults (0, false, "") instead of throwing errors provides more
|
|
8671
|
+
// forgiving evaluation semantics matching JavaScript's type coercion behavior.
|
|
8672
|
+
asBool(): bool {
|
|
8673
|
+
return this.type == TYPE_BOOL ? this.boolVal : false;
|
|
8674
|
+
}
|
|
8675
|
+
asInt(): i64 {
|
|
8676
|
+
return this.type == TYPE_INT ? this.intVal : 0;
|
|
8677
|
+
}
|
|
8678
|
+
asFloat(): f64 {
|
|
8679
|
+
if (this.type == TYPE_FLOAT) return this.floatVal;
|
|
8680
|
+
if (this.type == TYPE_INT) return <f64>this.intVal;
|
|
8681
|
+
return 0.0;
|
|
8682
|
+
}
|
|
8683
|
+
asString(): string {
|
|
8684
|
+
// Convert value to string representation
|
|
8685
|
+
if (this.type == TYPE_STRING && this.stringVal != null) return this.stringVal!;
|
|
8686
|
+
if (this.type == TYPE_NULL) return '';
|
|
8687
|
+
if (this.type == TYPE_BOOL) return this.boolVal ? 'true' : 'false';
|
|
8688
|
+
if (this.type == TYPE_INT) return this.intVal.toString();
|
|
8689
|
+
if (this.type == TYPE_FLOAT) {
|
|
8690
|
+
// JavaScript developers expect "3" not "3.0" for integer-valued floats. We check if
|
|
8691
|
+
// the float is exactly representable as i64 (no fractional part, within range) and
|
|
8692
|
+
// format as integer in that case. Otherwise use AS's default float formatting.
|
|
8693
|
+
const f = this.floatVal;
|
|
8694
|
+
// Check if it's an integer
|
|
8695
|
+
if (f == <f64>(<i64>f) && f >= <f64>i64.MIN_VALUE && f <= <f64>i64.MAX_VALUE) {
|
|
8696
|
+
return (<i64>f).toString();
|
|
8697
|
+
}
|
|
8698
|
+
return f.toString();
|
|
8699
|
+
}
|
|
8700
|
+
return '';
|
|
8701
|
+
}
|
|
8702
|
+
asArray(): Array<Value> {
|
|
8703
|
+
return this.arrayVal != null ? this.arrayVal! : new Array<Value>();
|
|
8704
|
+
}
|
|
8705
|
+
asObject(): Map<string, Value> {
|
|
8706
|
+
return this.objectVal != null ? this.objectVal! : new Map<string, Value>();
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
|
|
8710
|
+
export type Result = Map<string, Value>;
|
|
8711
|
+
`;
|
|
8712
|
+
var RUNTIME_CONTEXT = `// assembly/runtime/context.ts (AssemblyScript)
|
|
8713
|
+
import { Value, TYPE_OBJECT } from './values';
|
|
8714
|
+
|
|
8715
|
+
/**
|
|
8716
|
+
* Stores cached path resolution metadata to avoid redundant string splitting
|
|
8717
|
+
* and prefix matching on frequently-accessed paths.
|
|
8718
|
+
*/
|
|
8719
|
+
class PathResolutionCache {
|
|
8720
|
+
// Cache entry: stores the matched prefix key, its length, and the split parts for a given path
|
|
8721
|
+
prefixKey: string;
|
|
8722
|
+
prefixLen: i32;
|
|
8723
|
+
parts: string[];
|
|
8724
|
+
|
|
8725
|
+
constructor(prefixKey: string, prefixLen: i32, parts: string[]) {
|
|
8726
|
+
this.prefixKey = prefixKey;
|
|
8727
|
+
this.prefixLen = prefixLen;
|
|
8728
|
+
this.parts = parts;
|
|
8729
|
+
}
|
|
8730
|
+
}
|
|
8731
|
+
|
|
8732
|
+
/**
|
|
8733
|
+
* Resolves a potentially dotted path against a data map with caching support.
|
|
8734
|
+
*
|
|
8735
|
+
* Keys like "$nodes.expression2" are stored as literal strings with dots in them,
|
|
8736
|
+
* not as nested objects. This function handles both cases:
|
|
8737
|
+
* 1. Exact key match (e.g., "$nodes.expression2")
|
|
8738
|
+
* 2. Nested path (e.g., "$nodes.expression2.L1" where "$nodes.expression2" is a key
|
|
8739
|
+
* and "L1" is a property inside the object stored at that key)
|
|
8740
|
+
*
|
|
8741
|
+
* The algorithm finds the longest matching prefix in the data map, then navigates
|
|
8742
|
+
* any remaining path segments through nested object properties.
|
|
8743
|
+
*
|
|
8744
|
+
* Performance: Uses an optional cache to avoid redundant path splitting and prefix
|
|
8745
|
+
* matching for frequently-accessed paths within a single evaluation.
|
|
8746
|
+
*/
|
|
8747
|
+
export function resolvePathInData(
|
|
8748
|
+
data: Map<string, Value>,
|
|
8749
|
+
path: string,
|
|
8750
|
+
cache: Map<string, PathResolutionCache> | null = null,
|
|
8751
|
+
): Value {
|
|
8752
|
+
// Check for exact key match first
|
|
8753
|
+
if (data.has(path)) {
|
|
8754
|
+
return data.get(path);
|
|
8755
|
+
}
|
|
8756
|
+
|
|
8757
|
+
// If no dot, the key doesn't exist
|
|
8758
|
+
if (!path.includes('.')) {
|
|
8759
|
+
return Value.Null();
|
|
8760
|
+
}
|
|
8761
|
+
|
|
8762
|
+
// Check cache if available
|
|
8763
|
+
let matchedPrefixLen: i32 = 0;
|
|
8764
|
+
let prefixKey: string = '';
|
|
8765
|
+
let parts: string[];
|
|
8766
|
+
|
|
8767
|
+
if (cache !== null && cache.has(path)) {
|
|
8768
|
+
// Cache hit - use cached resolution metadata AND cached parts array
|
|
8769
|
+
const cached = cache.get(path);
|
|
8770
|
+
prefixKey = cached.prefixKey;
|
|
8771
|
+
matchedPrefixLen = cached.prefixLen;
|
|
8772
|
+
parts = cached.parts;
|
|
8773
|
+
} else {
|
|
8774
|
+
// Cache miss - perform full path resolution
|
|
8775
|
+
parts = path.split('.');
|
|
8776
|
+
|
|
8777
|
+
// Try progressively longer prefixes to find a matching key
|
|
8778
|
+
// e.g., for "$nodes.expression2.L1", try:
|
|
8779
|
+
// 1. "$nodes" (no match)
|
|
8780
|
+
// 2. "$nodes.expression2" (match! then access .L1 inside)
|
|
8781
|
+
let currentPrefix = '';
|
|
8782
|
+
for (let i = 0; i < parts.length; i++) {
|
|
8783
|
+
if (i > 0) currentPrefix += '.';
|
|
8784
|
+
currentPrefix += parts[i];
|
|
8785
|
+
if (data.has(currentPrefix)) {
|
|
8786
|
+
matchedPrefixLen = i + 1;
|
|
8787
|
+
prefixKey = currentPrefix;
|
|
8788
|
+
}
|
|
8789
|
+
}
|
|
8790
|
+
|
|
8791
|
+
// Store in cache if available (including the parts array to avoid future splits)
|
|
8792
|
+
if (cache !== null) {
|
|
8793
|
+
cache.set(path, new PathResolutionCache(prefixKey, matchedPrefixLen, parts));
|
|
8794
|
+
}
|
|
8795
|
+
}
|
|
8796
|
+
|
|
8797
|
+
// If no prefix matched, path doesn't exist
|
|
8798
|
+
if (matchedPrefixLen == 0) {
|
|
8799
|
+
return Value.Null();
|
|
8800
|
+
}
|
|
8801
|
+
|
|
8802
|
+
// Get the value at the matched prefix
|
|
8803
|
+
let current = data.get(prefixKey);
|
|
8804
|
+
|
|
8805
|
+
// Navigate remaining parts as object properties
|
|
8806
|
+
for (let i = matchedPrefixLen; i < parts.length; i++) {
|
|
8807
|
+
if (!current.isObject()) return Value.Null();
|
|
8808
|
+
const obj = current.asObject();
|
|
8809
|
+
const key = parts[i];
|
|
8810
|
+
if (!obj.has(key)) return Value.Null();
|
|
8811
|
+
current = obj.get(key);
|
|
8812
|
+
}
|
|
8813
|
+
return current;
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8816
|
+
export class Context {
|
|
8817
|
+
data: Map<string, Value>;
|
|
8818
|
+
// Cache for decision node results to avoid redundant evaluation. Caching prevents
|
|
8819
|
+
// exponential evaluation time for DAG structures where multiple nodes reference
|
|
8820
|
+
// the same upstream computation.
|
|
8821
|
+
decisionResults: Map<string, Value>;
|
|
8822
|
+
// Stack to track currently evaluating decisions for recursion detection
|
|
8823
|
+
decisionStack: string[];
|
|
8824
|
+
// When merging results, we need to know which keys are original inputs (should be
|
|
8825
|
+
// preserved) vs computed outputs (may be overwritten). This prevents input shadowing.
|
|
8826
|
+
inputKeys: Set<string>;
|
|
8827
|
+
// Cache for path resolution to avoid redundant string splitting and prefix matching.
|
|
8828
|
+
// For decisions with many field accesses (e.g., 100-rule decision table with 5 inputs
|
|
8829
|
+
// per rule = 500 path lookups), this eliminates most splitting overhead.
|
|
8830
|
+
pathCache: Map<string, PathResolutionCache>;
|
|
8831
|
+
|
|
8832
|
+
constructor(input: Map<string, Value>) {
|
|
8833
|
+
this.data = input;
|
|
8834
|
+
this.decisionResults = new Map<string, Value>();
|
|
8835
|
+
this.decisionStack = new Array<string>();
|
|
8836
|
+
this.pathCache = new Map<string, PathResolutionCache>();
|
|
8837
|
+
// Store original input keys
|
|
8838
|
+
this.inputKeys = new Set<string>();
|
|
8839
|
+
const keys = input.keys();
|
|
8840
|
+
for (let i = 0; i < keys.length; i++) {
|
|
8841
|
+
this.inputKeys.add(keys[i]);
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
|
|
8845
|
+
// Unlike JS Map which returns undefined for missing keys, AS Map.get() throws.
|
|
8846
|
+
// We must check has() first or handle dots carefully to avoid runtime errors.
|
|
8847
|
+
get(path: string): Value {
|
|
8848
|
+
return resolvePathInData(this.data, path, this.pathCache);
|
|
8849
|
+
}
|
|
8850
|
+
|
|
8851
|
+
set(path: string, value: Value): void {
|
|
8852
|
+
this.data.set(path, value);
|
|
8853
|
+
}
|
|
8854
|
+
|
|
8855
|
+
// Merge another result into this context (for passthrough mode)
|
|
8856
|
+
merge(other: Map<string, Value>): void {
|
|
8857
|
+
const keys = other.keys();
|
|
8858
|
+
for (let i = 0; i < keys.length; i++) {
|
|
8859
|
+
const key = keys[i];
|
|
8860
|
+
this.data.set(key, other.get(key));
|
|
8861
|
+
}
|
|
8862
|
+
}
|
|
8863
|
+
|
|
8864
|
+
// Convert the context data to a Value (for passthrough sub-decisions)
|
|
8865
|
+
toValue(): Value {
|
|
8866
|
+
return Value.Object(this.data);
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
|
|
8870
|
+
/**
|
|
8871
|
+
* ScopedContext is a lightweight context wrapper for scoped expression evaluation.
|
|
8872
|
+
* Scoped evaluation (loops, sub-object navigation) needs simple Map-based get/set
|
|
8873
|
+
* without decision caching/recursion tracking. Separate class avoids polluting
|
|
8874
|
+
* Context with unnecessary state and provides clearer semantics.
|
|
8875
|
+
*
|
|
8876
|
+
* Used for:
|
|
8877
|
+
* - inputField scoping (expressions evaluated within a sub-object scope)
|
|
8878
|
+
* - executionMode: "loop" (expressions evaluated for each array item)
|
|
8879
|
+
*
|
|
8880
|
+
* Performance: Includes path caching for loop-mode expressions where the same paths
|
|
8881
|
+
* are accessed repeatedly across array items.
|
|
8882
|
+
*/
|
|
8883
|
+
export class ScopedContext {
|
|
8884
|
+
data: Map<string, Value>;
|
|
8885
|
+
// Path resolution cache for frequently-accessed paths in loops
|
|
8886
|
+
pathCache: Map<string, PathResolutionCache>;
|
|
8887
|
+
|
|
8888
|
+
constructor(data: Map<string, Value>) {
|
|
8889
|
+
this.data = data;
|
|
8890
|
+
this.pathCache = new Map<string, PathResolutionCache>();
|
|
8891
|
+
}
|
|
8892
|
+
|
|
8893
|
+
get(path: string): Value {
|
|
8894
|
+
return resolvePathInData(this.data, path, this.pathCache);
|
|
8895
|
+
}
|
|
8896
|
+
|
|
8897
|
+
set(path: string, value: Value): void {
|
|
8898
|
+
this.data.set(path, value);
|
|
8899
|
+
}
|
|
8900
|
+
}
|
|
8901
|
+
`;
|
|
8902
|
+
var RUNTIME_EXPRESSIONS = `// assembly/runtime/expressions.ts (AssemblyScript)
|
|
8903
|
+
import { Value, TYPE_ARRAY, TYPE_STRING, TYPE_FLOAT, TYPE_INT, TYPE_BOOL } from './values';
|
|
8904
|
+
|
|
8905
|
+
/**
|
|
8906
|
+
* Helper function to create an argument array.
|
|
8907
|
+
* AssemblyScript doesn't support true variadic functions or rest parameters, so we provide
|
|
8908
|
+
* fixed-arity helpers (makeArgs1, makeArgs2, makeArgs3) for common function arities.
|
|
8909
|
+
*
|
|
8910
|
+
* These use thread-local reusable arrays to avoid allocations in hot paths.
|
|
8911
|
+
* Since WASM evaluation is single-threaded, reusing arrays is safe.
|
|
8912
|
+
*/
|
|
8913
|
+
|
|
8914
|
+
// Reusable argument arrays - lazily initialized
|
|
8915
|
+
let _args1: Array<Value> | null = null;
|
|
8916
|
+
let _args2: Array<Value> | null = null;
|
|
8917
|
+
let _args3: Array<Value> | null = null;
|
|
8918
|
+
|
|
8919
|
+
export function makeArgs(a: Value, b: Value, c: Value = Value.Null()): Array<Value> {
|
|
8920
|
+
if (_args3 === null) {
|
|
8921
|
+
_args3 = new Array<Value>(3);
|
|
8922
|
+
}
|
|
8923
|
+
unchecked((_args3![0] = a));
|
|
8924
|
+
unchecked((_args3![1] = b));
|
|
8925
|
+
unchecked((_args3![2] = c));
|
|
8926
|
+
return _args3!;
|
|
8927
|
+
}
|
|
8928
|
+
|
|
8929
|
+
export function makeArgs1(a: Value): Array<Value> {
|
|
8930
|
+
if (_args1 === null) {
|
|
8931
|
+
_args1 = new Array<Value>(1);
|
|
8932
|
+
}
|
|
8933
|
+
unchecked((_args1![0] = a));
|
|
8934
|
+
return _args1!;
|
|
8935
|
+
}
|
|
8936
|
+
|
|
8937
|
+
export function makeArgs2(a: Value, b: Value): Array<Value> {
|
|
8938
|
+
if (_args2 === null) {
|
|
8939
|
+
_args2 = new Array<Value>(2);
|
|
8940
|
+
}
|
|
8941
|
+
unchecked((_args2![0] = a));
|
|
8942
|
+
unchecked((_args2![1] = b));
|
|
8943
|
+
return _args2!;
|
|
8944
|
+
}
|
|
8945
|
+
|
|
8946
|
+
export function makeArgs3(a: Value, b: Value, c: Value): Array<Value> {
|
|
8947
|
+
if (_args3 === null) {
|
|
8948
|
+
_args3 = new Array<Value>(3);
|
|
8949
|
+
}
|
|
8950
|
+
unchecked((_args3![0] = a));
|
|
8951
|
+
unchecked((_args3![1] = b));
|
|
8952
|
+
unchecked((_args3![2] = c));
|
|
8953
|
+
return _args3!;
|
|
8954
|
+
}
|
|
8955
|
+
|
|
8956
|
+
// Type alias for function handlers
|
|
8957
|
+
type FunctionHandler = (args: Array<Value>) => Value;
|
|
8958
|
+
|
|
8959
|
+
// Global function registry - Map provides O(1) lookup instead of O(n) if-else chain
|
|
8960
|
+
const functionRegistry = new Map<string, FunctionHandler>();
|
|
8961
|
+
|
|
8962
|
+
/**
|
|
8963
|
+
* Initialize the function registry with all built-in functions.
|
|
8964
|
+
* This is called once at module initialization.
|
|
8965
|
+
*/
|
|
8966
|
+
function initializeFunctionRegistry(): void {
|
|
8967
|
+
// Array functions
|
|
8968
|
+
functionRegistry.set('sum', (args: Array<Value>): Value => sumArray(args[0]));
|
|
8969
|
+
functionRegistry.set('avg', (args: Array<Value>): Value => avgArray(args[0]));
|
|
8970
|
+
functionRegistry.set('min', (args: Array<Value>): Value => minArray(args[0]));
|
|
8971
|
+
functionRegistry.set('max', (args: Array<Value>): Value => maxArray(args[0]));
|
|
8972
|
+
functionRegistry.set('sort', (args: Array<Value>): Value => sortArray(args[0]));
|
|
8973
|
+
|
|
8974
|
+
// Flatten has two aliases
|
|
8975
|
+
const flatHandler = (args: Array<Value>): Value => {
|
|
8976
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
8977
|
+
return flatArray(args[0], arg1);
|
|
8978
|
+
};
|
|
8979
|
+
functionRegistry.set('flat', flatHandler);
|
|
8980
|
+
functionRegistry.set('flatten', flatHandler);
|
|
8981
|
+
|
|
8982
|
+
// Count/length has two aliases
|
|
8983
|
+
const lengthHandler = (args: Array<Value>): Value => lengthOf(args[0]);
|
|
8984
|
+
functionRegistry.set('count', lengthHandler);
|
|
8985
|
+
functionRegistry.set('len', lengthHandler);
|
|
8986
|
+
|
|
8987
|
+
functionRegistry.set('contains', (args: Array<Value>): Value => {
|
|
8988
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
8989
|
+
return containsValue(args[0], arg1);
|
|
8990
|
+
});
|
|
8991
|
+
|
|
8992
|
+
// Type conversion functions
|
|
8993
|
+
functionRegistry.set('number', (args: Array<Value>): Value => numberValue(args[0]));
|
|
8994
|
+
functionRegistry.set('string', (args: Array<Value>): Value => stringValue(args[0]));
|
|
8995
|
+
|
|
8996
|
+
// Math functions
|
|
8997
|
+
functionRegistry.set('abs', (args: Array<Value>): Value => absValue(args[0]));
|
|
8998
|
+
functionRegistry.set('floor', (args: Array<Value>): Value => floorValue(args[0]));
|
|
8999
|
+
functionRegistry.set('ceil', (args: Array<Value>): Value => ceilValue(args[0]));
|
|
9000
|
+
functionRegistry.set('round', (args: Array<Value>): Value => roundValue(args[0]));
|
|
9001
|
+
|
|
9002
|
+
// String functions
|
|
9003
|
+
functionRegistry.set('upper', (args: Array<Value>): Value => upperString(args[0]));
|
|
9004
|
+
functionRegistry.set('lower', (args: Array<Value>): Value => lowerString(args[0]));
|
|
9005
|
+
functionRegistry.set('trim', (args: Array<Value>): Value => trimString(args[0]));
|
|
9006
|
+
|
|
9007
|
+
functionRegistry.set('substring', (args: Array<Value>): Value => {
|
|
9008
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9009
|
+
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
9010
|
+
return substringString(args[0], arg1, arg2);
|
|
9011
|
+
});
|
|
9012
|
+
|
|
9013
|
+
functionRegistry.set('indexOf', (args: Array<Value>): Value => {
|
|
9014
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9015
|
+
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
9016
|
+
return indexOfString(args[0], arg1, arg2.isNull() ? Value.Int(<i64>0) : arg2);
|
|
9017
|
+
});
|
|
9018
|
+
|
|
9019
|
+
functionRegistry.set('startsWith', (args: Array<Value>): Value => {
|
|
9020
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9021
|
+
return startsWithString(args[0], arg1);
|
|
9022
|
+
});
|
|
9023
|
+
|
|
9024
|
+
functionRegistry.set('endsWith', (args: Array<Value>): Value => {
|
|
9025
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9026
|
+
return endsWithString(args[0], arg1);
|
|
9027
|
+
});
|
|
9028
|
+
|
|
9029
|
+
functionRegistry.set('split', (args: Array<Value>): Value => {
|
|
9030
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9031
|
+
return splitString(args[0], arg1);
|
|
9032
|
+
});
|
|
9033
|
+
|
|
9034
|
+
functionRegistry.set('join', (args: Array<Value>): Value => {
|
|
9035
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9036
|
+
return joinArray(args[0], arg1);
|
|
9037
|
+
});
|
|
9038
|
+
|
|
9039
|
+
functionRegistry.set('replace', (args: Array<Value>): Value => {
|
|
9040
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9041
|
+
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
9042
|
+
return replaceString(args[0], arg1, arg2);
|
|
9043
|
+
});
|
|
9044
|
+
|
|
9045
|
+
functionRegistry.set('replaceAll', (args: Array<Value>): Value => {
|
|
9046
|
+
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
9047
|
+
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
9048
|
+
return replaceAllString(args[0], arg1, arg2);
|
|
9049
|
+
});
|
|
9050
|
+
|
|
9051
|
+
// Date/time functions
|
|
9052
|
+
functionRegistry.set('date', (args: Array<Value>): Value => parseDate(args[0]));
|
|
9053
|
+
|
|
9054
|
+
// now() returns 0 because WASM doesn't have direct access to system time in a sandboxed way.
|
|
9055
|
+
// To get the current timestamp, use date("now") instead, which uses Date.now().
|
|
9056
|
+
functionRegistry.set('now', (args: Array<Value>): Value => Value.Float(0));
|
|
9057
|
+
|
|
9058
|
+
functionRegistry.set('time', (args: Array<Value>): Value => parseTime(args[0]));
|
|
9059
|
+
functionRegistry.set('duration', (args: Array<Value>): Value => parseDuration(args[0]));
|
|
9060
|
+
|
|
9061
|
+
// Object functions
|
|
9062
|
+
functionRegistry.set('values', (args: Array<Value>): Value => objectValues(args[0]));
|
|
9063
|
+
functionRegistry.set('keys', (args: Array<Value>): Value => objectKeys(args[0]));
|
|
9064
|
+
}
|
|
9065
|
+
|
|
9066
|
+
// Initialize the registry immediately
|
|
9067
|
+
initializeFunctionRegistry();
|
|
9068
|
+
|
|
9069
|
+
/**
|
|
9070
|
+
* Evaluate built-in functions.
|
|
9071
|
+
* All inputs and outputs use the Value tagged union.
|
|
9072
|
+
*
|
|
9073
|
+
* Uses a Map-based registry for O(1) function lookup instead of O(n) if-else chain.
|
|
9074
|
+
*/
|
|
9075
|
+
export function evaluateFunction(name: string, args: Array<Value>): Value {
|
|
9076
|
+
// Safety check for args array
|
|
9077
|
+
if (args == null || args.length == 0) return Value.Null();
|
|
9078
|
+
|
|
9079
|
+
// Look up handler in the registry
|
|
9080
|
+
if (functionRegistry.has(name)) {
|
|
9081
|
+
const handler = functionRegistry.get(name);
|
|
9082
|
+
return handler(args);
|
|
9083
|
+
}
|
|
9084
|
+
|
|
9085
|
+
// Unknown function - return null
|
|
9086
|
+
return Value.Null();
|
|
9087
|
+
}
|
|
9088
|
+
|
|
9089
|
+
// === Object Functions ===
|
|
9090
|
+
|
|
9091
|
+
/**
|
|
9092
|
+
* Get all values from an object as an array.
|
|
9093
|
+
* If the input is not an object, returns an empty array.
|
|
9094
|
+
*/
|
|
9095
|
+
export function objectValues(val: Value): Value {
|
|
9096
|
+
if (!val.isObject()) return Value.Array(new Array<Value>(0));
|
|
9097
|
+
const obj = val.asObject();
|
|
9098
|
+
const keys = obj.keys();
|
|
9099
|
+
const result = new Array<Value>(keys.length);
|
|
9100
|
+
for (let i = 0; i < keys.length; i++) {
|
|
9101
|
+
unchecked((result[i] = obj.get(unchecked(keys[i]))));
|
|
9102
|
+
}
|
|
9103
|
+
return Value.Array(result);
|
|
9104
|
+
}
|
|
9105
|
+
|
|
9106
|
+
/**
|
|
9107
|
+
* Get all keys from an object as an array of strings.
|
|
9108
|
+
* If the input is not an object, returns an empty array.
|
|
9109
|
+
*/
|
|
9110
|
+
export function objectKeys(val: Value): Value {
|
|
9111
|
+
if (!val.isObject()) return Value.Array(new Array<Value>(0));
|
|
9112
|
+
const obj = val.asObject();
|
|
9113
|
+
const keys = obj.keys();
|
|
9114
|
+
const result = new Array<Value>(keys.length);
|
|
9115
|
+
for (let i = 0; i < keys.length; i++) {
|
|
9116
|
+
unchecked((result[i] = Value.String(unchecked(keys[i]))));
|
|
9117
|
+
}
|
|
9118
|
+
return Value.Array(result);
|
|
9119
|
+
}
|
|
9120
|
+
|
|
9121
|
+
// === Array Functions ===
|
|
9122
|
+
|
|
9123
|
+
export function sumArray(val: Value): Value {
|
|
9124
|
+
if (!val.isArray()) return Value.Float(0.0);
|
|
9125
|
+
const arr = val.asArray();
|
|
9126
|
+
let total: f64 = 0.0;
|
|
9127
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9128
|
+
total += unchecked(arr[i]).asFloat();
|
|
9129
|
+
}
|
|
9130
|
+
return Value.Float(total);
|
|
9131
|
+
}
|
|
9132
|
+
|
|
9133
|
+
export function minArray(val: Value): Value {
|
|
9134
|
+
if (!val.isArray()) return Value.Null();
|
|
9135
|
+
const arr = val.asArray();
|
|
9136
|
+
if (arr.length == 0) return Value.Null();
|
|
9137
|
+
let min = arr[0].asFloat();
|
|
9138
|
+
for (let i = 1; i < arr.length; i++) {
|
|
9139
|
+
const v = unchecked(arr[i]).asFloat();
|
|
9140
|
+
if (v < min) min = v;
|
|
9141
|
+
}
|
|
9142
|
+
return Value.Float(min);
|
|
9143
|
+
}
|
|
9144
|
+
|
|
9145
|
+
export function maxArray(val: Value): Value {
|
|
9146
|
+
if (!val.isArray()) return Value.Null();
|
|
9147
|
+
const arr = val.asArray();
|
|
9148
|
+
if (arr.length == 0) return Value.Null();
|
|
9149
|
+
let max = arr[0].asFloat();
|
|
9150
|
+
for (let i = 1; i < arr.length; i++) {
|
|
9151
|
+
const v = unchecked(arr[i]).asFloat();
|
|
9152
|
+
if (v > max) max = v;
|
|
9153
|
+
}
|
|
9154
|
+
return Value.Float(max);
|
|
9155
|
+
}
|
|
9156
|
+
|
|
9157
|
+
export function avgArray(val: Value): Value {
|
|
9158
|
+
if (!val.isArray()) return Value.Null();
|
|
9159
|
+
const arr = val.asArray();
|
|
9160
|
+
if (arr.length == 0) return Value.Null();
|
|
9161
|
+
let total: f64 = 0.0;
|
|
9162
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9163
|
+
total += unchecked(arr[i]).asFloat();
|
|
9164
|
+
}
|
|
9165
|
+
return Value.Float(total / <f64>arr.length);
|
|
9166
|
+
}
|
|
9167
|
+
|
|
9168
|
+
/**
|
|
9169
|
+
* Sort an array in ascending order.
|
|
9170
|
+
*
|
|
9171
|
+
* Supports sorting numbers and strings in ascending order.
|
|
9172
|
+
* Creates and returns a new sorted array without modifying the original.
|
|
9173
|
+
*
|
|
9174
|
+
* Parameters:
|
|
9175
|
+
* - val: The array value to sort
|
|
9176
|
+
*
|
|
9177
|
+
* Behavior:
|
|
9178
|
+
* - Numbers: sorted in ascending numeric order
|
|
9179
|
+
* - Strings: sorted in lexicographic (ASCII) order
|
|
9180
|
+
* - Empty arrays: returns a new empty array
|
|
9181
|
+
* - Non-array inputs: returns null
|
|
9182
|
+
*
|
|
9183
|
+
* Examples:
|
|
9184
|
+
* - sortArray([3, 1, 2]) -> [1, 2, 3]
|
|
9185
|
+
* - sortArray(["c", "a", "b"]) -> ["a", "b", "c"]
|
|
9186
|
+
* - sortArray([]) -> []
|
|
9187
|
+
*
|
|
9188
|
+
* @param val - The array value to sort
|
|
9189
|
+
* @returns A new sorted array, or null for invalid input
|
|
9190
|
+
*/
|
|
9191
|
+
export function sortArray(val: Value): Value {
|
|
9192
|
+
if (!val.isArray()) return Value.Null();
|
|
9193
|
+
const arr = val.asArray();
|
|
9194
|
+
|
|
9195
|
+
// Create a copy of the array to avoid mutating the original
|
|
9196
|
+
const sorted = new Array<Value>(arr.length);
|
|
9197
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9198
|
+
unchecked((sorted[i] = unchecked(arr[i])));
|
|
9199
|
+
}
|
|
9200
|
+
|
|
9201
|
+
// Sort the copy using the compareValues function
|
|
9202
|
+
sorted.sort((a: Value, b: Value): i32 => compareValues(a, b));
|
|
9203
|
+
|
|
9204
|
+
return Value.Array(sorted);
|
|
9205
|
+
}
|
|
9206
|
+
|
|
9207
|
+
/**
|
|
9208
|
+
* Flatten nested arrays into a single-level array.
|
|
9209
|
+
*
|
|
9210
|
+
* Parameters:
|
|
9211
|
+
* - val: The array value to flatten
|
|
9212
|
+
* - depthVal: Optional depth (default: 1). Specifies how many levels deep to flatten.
|
|
9213
|
+
*
|
|
9214
|
+
* Behavior:
|
|
9215
|
+
* - If depth is 0, returns a shallow copy of the array
|
|
9216
|
+
* - Flattens arrays recursively up to the specified depth
|
|
9217
|
+
* - Empty arrays are returned as empty arrays
|
|
9218
|
+
* - Non-array inputs return null
|
|
9219
|
+
*
|
|
9220
|
+
* Examples:
|
|
9221
|
+
* - flatArray([[1, 2], [3, 4]]) -> [1, 2, 3, 4] (depth 1)
|
|
9222
|
+
* - flatArray([1, [2, [3]]]) -> [1, 2, [3]] (depth 1)
|
|
9223
|
+
* - flatArray([1, [2, [3]]], 2) -> [1, 2, 3] (depth 2)
|
|
9224
|
+
* - flatArray([1, 2, 3]) -> [1, 2, 3] (no nesting, shallow copy)
|
|
9225
|
+
* - flatArray([]) -> [] (empty array)
|
|
9226
|
+
* - flatArray([[1], [2]], 0) -> [[1], [2]] (depth 0, no flattening)
|
|
9227
|
+
*
|
|
9228
|
+
* @param val - The array value to flatten
|
|
9229
|
+
* @param depthVal - Optional depth for flattening (default: 1)
|
|
9230
|
+
* @returns A new flattened array, or null for invalid input
|
|
9231
|
+
*/
|
|
9232
|
+
export function flatArray(val: Value, depthVal: Value = Value.Int(<i64>1)): Value {
|
|
9233
|
+
if (!val.isArray()) return Value.Null();
|
|
9234
|
+
const arr = val.asArray();
|
|
9235
|
+
|
|
9236
|
+
// Parse depth parameter matches JavaScript Array.flat() default behavior, which
|
|
9237
|
+
// flattens one level deep unless specified otherwise. Most common use case.
|
|
9238
|
+
let depth: i32 = 1;
|
|
9239
|
+
if (!depthVal.isNull() && depthVal.isNumber()) {
|
|
9240
|
+
depth = <i32>depthVal.asFloat(); // Use asFloat() since numbers may come as floats
|
|
9241
|
+
// Clamp depth to non-negative values
|
|
9242
|
+
if (depth < 0) depth = 1;
|
|
9243
|
+
}
|
|
9244
|
+
|
|
9245
|
+
// Use a simple iterative approach that maintains order
|
|
9246
|
+
let result = new Array<Value>();
|
|
9247
|
+
flattenRecursive(arr, depth, result);
|
|
9248
|
+
return Value.Array(result);
|
|
9249
|
+
}
|
|
9250
|
+
|
|
9251
|
+
// Helper function for flatArray
|
|
9252
|
+
function flattenRecursive(arr: Array<Value>, currentDepth: i32, result: Array<Value>): void {
|
|
9253
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9254
|
+
const element = unchecked(arr[i]);
|
|
9255
|
+
if (element.isArray() && currentDepth > 0) {
|
|
9256
|
+
// Recursively flatten nested array
|
|
9257
|
+
flattenRecursive(element.asArray(), currentDepth - 1, result);
|
|
9258
|
+
} else {
|
|
9259
|
+
// Add element to results
|
|
9260
|
+
result.push(element);
|
|
9261
|
+
}
|
|
9262
|
+
}
|
|
9263
|
+
}
|
|
9264
|
+
|
|
9265
|
+
export function lengthOf(val: Value): Value {
|
|
9266
|
+
if (val.isArray()) {
|
|
9267
|
+
return Value.Int(<i64>val.asArray().length);
|
|
9268
|
+
}
|
|
9269
|
+
if (val.isString()) {
|
|
9270
|
+
return Value.Int(<i64>val.asString().length);
|
|
9271
|
+
}
|
|
9272
|
+
return Value.Int(0);
|
|
9273
|
+
}
|
|
9274
|
+
|
|
9275
|
+
/**
|
|
9276
|
+
* Check if a value contains a subvalue.
|
|
9277
|
+
*
|
|
9278
|
+
* For strings: checks if the string contains a given substring.
|
|
9279
|
+
* For arrays: checks if the array contains the given value.
|
|
9280
|
+
*
|
|
9281
|
+
* Parameters:
|
|
9282
|
+
* - container: The string or array to search in
|
|
9283
|
+
* - needle: The substring or value to search for
|
|
9284
|
+
*
|
|
9285
|
+
* Behavior:
|
|
9286
|
+
* - If container is a string, checks if it contains the substring
|
|
9287
|
+
* - If container is an array, checks if any element equals the needle
|
|
9288
|
+
* - For strings, returns null if either argument is not a string
|
|
9289
|
+
* - For arrays, always returns a boolean
|
|
9290
|
+
*
|
|
9291
|
+
* String Examples:
|
|
9292
|
+
* - contains("hello world", "world") \u2192 true
|
|
9293
|
+
* - contains("hello world", "xyz") \u2192 false
|
|
9294
|
+
* - contains("abc", "abc") \u2192 true
|
|
9295
|
+
* - contains("hello", "") \u2192 true (empty string is always contained)
|
|
9296
|
+
* - contains("", "") \u2192 true
|
|
9297
|
+
* - contains("", "x") \u2192 false
|
|
9298
|
+
* - contains("Hello", "hello") \u2192 false (case-sensitive)
|
|
9299
|
+
* - contains("hello", 123) \u2192 null (needle not a string)
|
|
9300
|
+
* - contains(123, "hello") \u2192 null (container not a string)
|
|
9301
|
+
*
|
|
9302
|
+
* Array Examples:
|
|
9303
|
+
* - contains([1, 2, 3], 2) \u2192 true
|
|
9304
|
+
* - contains([1, 2, 3], 4) \u2192 false
|
|
9305
|
+
* - contains(["a"], "a") \u2192 true
|
|
9306
|
+
* - contains(123, 1) \u2192 false
|
|
9307
|
+
*
|
|
9308
|
+
* @param container - The string or array to search in
|
|
9309
|
+
* @param needle - The substring or value to search for
|
|
9310
|
+
* @returns true if contained, false if not, or null for invalid string arguments
|
|
9311
|
+
*/
|
|
9312
|
+
export function containsValue(container: Value, needle: Value): Value {
|
|
9313
|
+
// Handle string containment
|
|
9314
|
+
if (container.isString()) {
|
|
9315
|
+
if (!needle.isString()) return Value.Null();
|
|
9316
|
+
const str = container.asString();
|
|
9317
|
+
const search = needle.asString();
|
|
9318
|
+
// Use indexOf to check containment (returns -1 if not found)
|
|
9319
|
+
const index = str.indexOf(search);
|
|
9320
|
+
return Value.Bool(index >= 0);
|
|
9321
|
+
}
|
|
9322
|
+
|
|
9323
|
+
// Handle array containment (original behavior)
|
|
9324
|
+
if (!container.isArray()) return Value.Bool(false);
|
|
9325
|
+
const arr = container.asArray();
|
|
9326
|
+
for (let i = 0; i < arr.length; i++) {
|
|
9327
|
+
if (valuesEqual(unchecked(arr[i]), needle)) return Value.Bool(true);
|
|
9328
|
+
}
|
|
9329
|
+
return Value.Bool(false);
|
|
9330
|
+
}
|
|
9331
|
+
|
|
9332
|
+
// === Date/Time Functions ===
|
|
9333
|
+
|
|
9334
|
+
/**
|
|
9335
|
+
* Parse a date string into a numeric timestamp.
|
|
9336
|
+
*
|
|
9337
|
+
* Supports ISO 8601 date format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS[Z|+/-HH:MM]
|
|
9338
|
+
* Returns Unix timestamp in seconds since epoch (1970-01-01 00:00:00 UTC).
|
|
9339
|
+
* This matches zen-engine's behavior and allows date arithmetic operations.
|
|
9340
|
+
*
|
|
9341
|
+
* Examples:
|
|
9342
|
+
* - date("2025-03-20T10:30:00Z") \u2192 1742466600 (seconds since epoch)
|
|
9343
|
+
* - date("1970-01-01") \u2192 0
|
|
9344
|
+
* - date("1970-01-01T00:00:00Z") \u2192 0
|
|
9345
|
+
*
|
|
9346
|
+
* @param val - A string value containing a date
|
|
9347
|
+
* @returns A float Value representing seconds since epoch, or Null for invalid input
|
|
9348
|
+
*/
|
|
9349
|
+
export function parseDate(val: Value): Value {
|
|
9350
|
+
if (!val.isString()) return Value.Null();
|
|
9351
|
+
|
|
9352
|
+
const str = val.asString();
|
|
9353
|
+
|
|
9354
|
+
// Handle special 'now' keyword - returns Unix timestamp in seconds
|
|
9355
|
+
// Using Date.now() which returns milliseconds, convert to seconds
|
|
9356
|
+
if (str == 'now') {
|
|
9357
|
+
return Value.Float(<f64>Date.now() / 1000.0);
|
|
9358
|
+
}
|
|
9359
|
+
|
|
9360
|
+
// Fast path for simple YYYY-MM-DD format (10 characters)
|
|
9361
|
+
// This avoids string allocations from split() and substring() for common cases
|
|
9362
|
+
if (str.length == 10) {
|
|
9363
|
+
// Check dash positions at indices 4 and 7
|
|
9364
|
+
const dashPos1 = unchecked(<i32>str.charCodeAt(4));
|
|
9365
|
+
const dashPos2 = unchecked(<i32>str.charCodeAt(7));
|
|
9366
|
+
if (dashPos1 == 45 && dashPos2 == 45) {
|
|
9367
|
+
// Hyphen is ASCII 45, digits start at ASCII 48
|
|
9368
|
+
// Parse year, month, day directly without split()
|
|
9369
|
+
const year =
|
|
9370
|
+
unchecked(<i32>str.charCodeAt(0) - 48) * 1000 +
|
|
9371
|
+
unchecked(<i32>str.charCodeAt(1) - 48) * 100 +
|
|
9372
|
+
unchecked(<i32>str.charCodeAt(2) - 48) * 10 +
|
|
9373
|
+
unchecked(<i32>str.charCodeAt(3) - 48);
|
|
9374
|
+
const month =
|
|
9375
|
+
unchecked(<i32>str.charCodeAt(5) - 48) * 10 + unchecked(<i32>str.charCodeAt(6) - 48);
|
|
9376
|
+
const day =
|
|
9377
|
+
unchecked(<i32>str.charCodeAt(8) - 48) * 10 + unchecked(<i32>str.charCodeAt(9) - 48);
|
|
9378
|
+
|
|
9379
|
+
// Validate ranges
|
|
9380
|
+
if (year >= 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
9381
|
+
const daysSinceEpoch = calculateDaysSinceEpoch(year, month, day);
|
|
9382
|
+
return Value.Float(<f64>daysSinceEpoch * 86400.0);
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
|
|
9387
|
+
// Parse ISO 8601 date format: YYYY-MM-DD
|
|
9388
|
+
// Also handle datetime format: YYYY-MM-DDTHH:MM:SS[Z|+/-HH:MM]
|
|
9389
|
+
let dateStr = str;
|
|
9390
|
+
let timeStr = '';
|
|
9391
|
+
let tzOffsetSeconds: i32 = 0; // Timezone offset in seconds (positive = ahead of UTC)
|
|
9392
|
+
|
|
9393
|
+
const tIndex = str.indexOf('T');
|
|
9394
|
+
if (tIndex >= 0) {
|
|
9395
|
+
dateStr = str.substring(0, tIndex);
|
|
9396
|
+
let timePart = str.substring(tIndex + 1);
|
|
9397
|
+
|
|
9398
|
+
// Handle timezone: Z, +HH:MM, -HH:MM
|
|
9399
|
+
const zIndex = timePart.indexOf('Z');
|
|
9400
|
+
if (zIndex >= 0) {
|
|
9401
|
+
timeStr = timePart.substring(0, zIndex);
|
|
9402
|
+
tzOffsetSeconds = 0; // UTC
|
|
9403
|
+
} else {
|
|
9404
|
+
const plusIndex = timePart.indexOf('+');
|
|
9405
|
+
const minusIndex = timePart.indexOf('-');
|
|
9406
|
+
// Find timezone offset (look for +/- after the time part, not in middle of time)
|
|
9407
|
+
let tzStartIndex = -1;
|
|
9408
|
+
if (plusIndex >= 5) {
|
|
9409
|
+
// At least HH:MM before timezone
|
|
9410
|
+
tzStartIndex = plusIndex;
|
|
9411
|
+
} else if (minusIndex >= 5) {
|
|
9412
|
+
tzStartIndex = minusIndex;
|
|
9413
|
+
}
|
|
9414
|
+
|
|
9415
|
+
if (tzStartIndex >= 0) {
|
|
9416
|
+
timeStr = timePart.substring(0, tzStartIndex);
|
|
9417
|
+
const tzStr = timePart.substring(tzStartIndex);
|
|
9418
|
+
// Parse timezone offset (+/-HH:MM or +/-HH)
|
|
9419
|
+
const isNegative = tzStr.charAt(0) == '-';
|
|
9420
|
+
const tzParts = tzStr.substring(1).split(':');
|
|
9421
|
+
if (tzParts.length >= 1) {
|
|
9422
|
+
const tzHours = parseIntValue(tzParts[0]);
|
|
9423
|
+
const tzMinutes = tzParts.length >= 2 ? parseIntValue(tzParts[1]) : 0;
|
|
9424
|
+
if (tzHours >= 0 && tzMinutes >= 0) {
|
|
9425
|
+
tzOffsetSeconds = tzHours * 3600 + tzMinutes * 60;
|
|
9426
|
+
if (isNegative) tzOffsetSeconds = -tzOffsetSeconds;
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
} else {
|
|
9430
|
+
timeStr = timePart;
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
}
|
|
9434
|
+
|
|
9435
|
+
// Split date by dash
|
|
9436
|
+
const parts = dateStr.split('-');
|
|
9437
|
+
if (parts.length < 3) return Value.Null();
|
|
9438
|
+
|
|
9439
|
+
// Parse year, month, day
|
|
9440
|
+
const year = parseIntValue(parts[0]);
|
|
9441
|
+
const month = parseIntValue(parts[1]);
|
|
9442
|
+
const day = parseIntValue(parts[2]);
|
|
9443
|
+
|
|
9444
|
+
if (year < 0 || month < 1 || month > 12 || day < 1 || day > 31) {
|
|
9445
|
+
return Value.Null();
|
|
9446
|
+
}
|
|
9447
|
+
|
|
9448
|
+
// Calculate days since epoch
|
|
9449
|
+
const daysSinceEpoch = calculateDaysSinceEpoch(year, month, day);
|
|
9450
|
+
|
|
9451
|
+
// Parse time if present (HH:MM:SS or HH:MM)
|
|
9452
|
+
let hours: i32 = 0;
|
|
9453
|
+
let minutes: i32 = 0;
|
|
9454
|
+
let seconds: i32 = 0;
|
|
9455
|
+
|
|
9456
|
+
if (timeStr.length > 0) {
|
|
9457
|
+
const timeParts = timeStr.split(':');
|
|
9458
|
+
if (timeParts.length >= 2) {
|
|
9459
|
+
hours = parseIntValue(timeParts[0]);
|
|
9460
|
+
minutes = parseIntValue(timeParts[1]);
|
|
9461
|
+
if (timeParts.length >= 3) {
|
|
9462
|
+
// Handle fractional seconds (e.g., "10:30:00.123")
|
|
9463
|
+
const secStr = timeParts[2];
|
|
9464
|
+
const dotIndex = secStr.indexOf('.');
|
|
9465
|
+
if (dotIndex >= 0) {
|
|
9466
|
+
seconds = parseIntValue(secStr.substring(0, dotIndex));
|
|
9467
|
+
} else {
|
|
9468
|
+
seconds = parseIntValue(secStr);
|
|
9469
|
+
}
|
|
9470
|
+
}
|
|
9471
|
+
}
|
|
9472
|
+
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
|
|
9473
|
+
// Invalid time - use 0
|
|
9474
|
+
hours = 0;
|
|
9475
|
+
minutes = 0;
|
|
9476
|
+
seconds = 0;
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
|
|
9480
|
+
// Calculate total seconds since epoch
|
|
9481
|
+
// daysSinceEpoch * 86400 + hours * 3600 + minutes * 60 + seconds - tzOffsetSeconds
|
|
9482
|
+
// We subtract tzOffsetSeconds to convert local time to UTC
|
|
9483
|
+
const totalSeconds: f64 =
|
|
9484
|
+
<f64>daysSinceEpoch * 86400.0 +
|
|
9485
|
+
<f64>hours * 3600.0 +
|
|
9486
|
+
<f64>minutes * 60.0 +
|
|
9487
|
+
<f64>seconds -
|
|
9488
|
+
<f64>tzOffsetSeconds;
|
|
9489
|
+
|
|
9490
|
+
return Value.Float(totalSeconds);
|
|
9491
|
+
}
|
|
9492
|
+
|
|
9493
|
+
/**
|
|
9494
|
+
* Parse a time string into a numeric value.
|
|
9495
|
+
*
|
|
9496
|
+
* Supports format: HH:MM:SS or HH:MM
|
|
9497
|
+
* Returns the number of seconds since midnight.
|
|
9498
|
+
*
|
|
9499
|
+
* @param val - A string value containing a time
|
|
9500
|
+
* @returns A float Value representing seconds since midnight
|
|
9501
|
+
*/
|
|
9502
|
+
export function parseTime(val: Value): Value {
|
|
9503
|
+
if (!val.isString()) return Value.Null();
|
|
9504
|
+
|
|
9505
|
+
const str = val.asString();
|
|
9506
|
+
const parts = str.split(':');
|
|
9507
|
+
|
|
9508
|
+
if (parts.length < 2) return Value.Null();
|
|
9509
|
+
|
|
9510
|
+
const hours = parseIntValue(parts[0]);
|
|
9511
|
+
const minutes = parseIntValue(parts[1]);
|
|
9512
|
+
const seconds = parts.length >= 3 ? parseIntValue(parts[2]) : 0;
|
|
9513
|
+
|
|
9514
|
+
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
|
|
9515
|
+
return Value.Null();
|
|
9516
|
+
}
|
|
9517
|
+
|
|
9518
|
+
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
|
9519
|
+
return Value.Float(<f64>totalSeconds);
|
|
9520
|
+
}
|
|
9521
|
+
|
|
9522
|
+
/**
|
|
9523
|
+
* Parse a duration string into a numeric value.
|
|
9524
|
+
*
|
|
9525
|
+
* Supports ISO 8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n]S
|
|
9526
|
+
* Also supports simple formats like "30d", "24h", "60m", "3600s"
|
|
9527
|
+
*
|
|
9528
|
+
* @param val - A string value containing a duration
|
|
9529
|
+
* @returns A float Value representing duration in seconds
|
|
9530
|
+
*/
|
|
9531
|
+
export function parseDuration(val: Value): Value {
|
|
9532
|
+
if (!val.isString()) return Value.Null();
|
|
9533
|
+
|
|
9534
|
+
const str = val.asString();
|
|
9535
|
+
|
|
9536
|
+
// Handle simple formats: "30d", "24h", "60m", "3600s"
|
|
9537
|
+
if (str.length > 1) {
|
|
9538
|
+
const unit = str.charAt(str.length - 1);
|
|
9539
|
+
const numStr = str.substring(0, str.length - 1);
|
|
9540
|
+
const num = parseFloatValue(numStr);
|
|
9541
|
+
|
|
9542
|
+
if (num >= 0) {
|
|
9543
|
+
if (unit == 's') return Value.Float(num);
|
|
9544
|
+
if (unit == 'm') return Value.Float(num * 60);
|
|
9545
|
+
if (unit == 'h') return Value.Float(num * 3600);
|
|
9546
|
+
if (unit == 'd') return Value.Float(num * 86400);
|
|
9547
|
+
}
|
|
9548
|
+
}
|
|
9549
|
+
|
|
9550
|
+
// Full ISO 8601 duration format (P[n]Y[n]M[n]DT[n]H[n]M[n]S) not yet supported
|
|
9551
|
+
return Value.Null();
|
|
9552
|
+
}
|
|
9553
|
+
|
|
9554
|
+
/**
|
|
9555
|
+
* Parse a string to an integer value.
|
|
9556
|
+
*/
|
|
9557
|
+
function parseIntValue(str: string): i32 {
|
|
9558
|
+
let result: i32 = 0;
|
|
9559
|
+
let negative = false;
|
|
9560
|
+
let i = 0;
|
|
9561
|
+
|
|
9562
|
+
// Handle leading whitespace
|
|
9563
|
+
while (i < str.length && str.charCodeAt(i) == 32) {
|
|
9564
|
+
i++;
|
|
9565
|
+
}
|
|
9566
|
+
|
|
9567
|
+
// Handle negative sign
|
|
9568
|
+
if (i < str.length && str.charCodeAt(i) == 45) {
|
|
9569
|
+
// '-'
|
|
9570
|
+
negative = true;
|
|
9571
|
+
i++;
|
|
9572
|
+
}
|
|
9573
|
+
|
|
9574
|
+
// Parse digits
|
|
9575
|
+
let hasDigits = false;
|
|
9576
|
+
while (i < str.length) {
|
|
9577
|
+
const c = str.charCodeAt(i);
|
|
9578
|
+
if (c >= 48 && c <= 57) {
|
|
9579
|
+
// '0'-'9'
|
|
9580
|
+
result = result * 10 + (c - 48);
|
|
9581
|
+
hasDigits = true;
|
|
9582
|
+
i++;
|
|
9583
|
+
} else {
|
|
9584
|
+
break;
|
|
9585
|
+
}
|
|
9586
|
+
}
|
|
9587
|
+
|
|
9588
|
+
// AssemblyScript doesn't have optional types or Result<T,E>, so we use -1 as sentinel
|
|
9589
|
+
// value for parse errors. Callers check for negative values to detect failures.
|
|
9590
|
+
if (!hasDigits) return -1;
|
|
9591
|
+
|
|
9592
|
+
return negative ? -result : result;
|
|
9593
|
+
}
|
|
9594
|
+
|
|
9595
|
+
/**
|
|
9596
|
+
* Parse a string to a float value.
|
|
9597
|
+
* Returns NaN for invalid input.
|
|
9598
|
+
*/
|
|
9599
|
+
function parseFloatValue(str: string): f64 {
|
|
9600
|
+
return parseFloat(str);
|
|
9601
|
+
}
|
|
9602
|
+
|
|
9603
|
+
/**
|
|
9604
|
+
* Calculate days since Unix epoch (1970-01-01).
|
|
9605
|
+
* Uses a simplified algorithm that handles leap years.
|
|
9606
|
+
*/
|
|
9607
|
+
function calculateDaysSinceEpoch(year: i32, month: i32, day: i32): i32 {
|
|
9608
|
+
// Days in each month (non-leap year)
|
|
9609
|
+
const daysInMonth: i32[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
9610
|
+
|
|
9611
|
+
// Calculate days from years
|
|
9612
|
+
let days: i32 = 0;
|
|
9613
|
+
|
|
9614
|
+
// Calculate years from 1970 to target year
|
|
9615
|
+
if (year >= 1970) {
|
|
9616
|
+
for (let y = 1970; y < year; y++) {
|
|
9617
|
+
days += isLeapYear(y) ? 366 : 365;
|
|
9618
|
+
}
|
|
9619
|
+
} else {
|
|
9620
|
+
for (let y = year; y < 1970; y++) {
|
|
9621
|
+
days -= isLeapYear(y) ? 366 : 365;
|
|
9622
|
+
}
|
|
9623
|
+
}
|
|
9624
|
+
|
|
9625
|
+
// Add days from months
|
|
9626
|
+
for (let m = 1; m < month; m++) {
|
|
9627
|
+
days += unchecked(daysInMonth[m - 1]);
|
|
9628
|
+
// Add leap day for February in leap years
|
|
9629
|
+
if (m == 2 && isLeapYear(year)) {
|
|
9630
|
+
days += 1;
|
|
9631
|
+
}
|
|
9632
|
+
}
|
|
9633
|
+
|
|
9634
|
+
// Add days
|
|
9635
|
+
days += day - 1; // day 1 is the first day, so subtract 1
|
|
9636
|
+
|
|
9637
|
+
return days;
|
|
9638
|
+
}
|
|
9639
|
+
|
|
9640
|
+
/**
|
|
9641
|
+
* Check if a year is a leap year.
|
|
9642
|
+
*/
|
|
9643
|
+
function isLeapYear(year: i32): bool {
|
|
9644
|
+
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
|
9645
|
+
}
|
|
9646
|
+
|
|
9647
|
+
// === Math Functions ===
|
|
9648
|
+
|
|
9649
|
+
export function absValue(val: Value): Value {
|
|
9650
|
+
if (val.isNumber()) {
|
|
9651
|
+
const n = val.asFloat();
|
|
9652
|
+
return Value.Float(n < 0 ? -n : n);
|
|
9653
|
+
}
|
|
9654
|
+
return Value.Null();
|
|
9655
|
+
}
|
|
9656
|
+
|
|
9657
|
+
export function floorValue(val: Value): Value {
|
|
9658
|
+
if (val.isNumber()) {
|
|
9659
|
+
return Value.Float(Math.floor(val.asFloat()));
|
|
9660
|
+
}
|
|
9661
|
+
return Value.Null();
|
|
9662
|
+
}
|
|
9663
|
+
|
|
9664
|
+
export function ceilValue(val: Value): Value {
|
|
9665
|
+
if (val.isNumber()) {
|
|
9666
|
+
return Value.Float(Math.ceil(val.asFloat()));
|
|
9667
|
+
}
|
|
9668
|
+
return Value.Null();
|
|
9669
|
+
}
|
|
9670
|
+
|
|
9671
|
+
export function roundValue(val: Value): Value {
|
|
9672
|
+
if (val.isNumber()) {
|
|
9673
|
+
return Value.Float(Math.round(val.asFloat()));
|
|
9674
|
+
}
|
|
9675
|
+
return Value.Null();
|
|
9676
|
+
}
|
|
9677
|
+
|
|
9678
|
+
// === String Functions ===
|
|
9679
|
+
|
|
9680
|
+
/**
|
|
9681
|
+
* Convert a string to uppercase.
|
|
9682
|
+
*
|
|
9683
|
+
* Non-alphabetic characters (numbers, symbols, spaces) remain unchanged.
|
|
9684
|
+
* If the input is not a string, returns null.
|
|
9685
|
+
*
|
|
9686
|
+
* Examples:
|
|
9687
|
+
* - upper("hello") \u2192 "HELLO"
|
|
9688
|
+
* - upper("Hello World") \u2192 "HELLO WORLD"
|
|
9689
|
+
* - upper("123abc") \u2192 "123ABC"
|
|
9690
|
+
* - upper("!@#") \u2192 "!@#"
|
|
9691
|
+
* - upper(123) \u2192 null
|
|
9692
|
+
* - upper(null) \u2192 null
|
|
9693
|
+
*
|
|
9694
|
+
* @param val - The value to convert to uppercase
|
|
9695
|
+
* @returns The uppercase string, or null if input is not a string
|
|
9696
|
+
*/
|
|
9697
|
+
export function upperString(val: Value): Value {
|
|
9698
|
+
if (!val.isString()) return Value.Null();
|
|
9699
|
+
const str = val.asString();
|
|
9700
|
+
return Value.String(str.toUpperCase());
|
|
9701
|
+
}
|
|
9702
|
+
|
|
9703
|
+
/**
|
|
9704
|
+
* Convert a string to lowercase.
|
|
9705
|
+
*
|
|
9706
|
+
* Non-alphabetic characters (numbers, symbols, spaces) remain unchanged.
|
|
9707
|
+
* If the input is not a string, returns null.
|
|
9708
|
+
*
|
|
9709
|
+
* Examples:
|
|
9710
|
+
* - lower("HELLO") \u2192 "hello"
|
|
9711
|
+
* - lower("Hello World") \u2192 "hello world"
|
|
9712
|
+
* - lower("123ABC") \u2192 "123abc"
|
|
9713
|
+
* - lower("!@#") \u2192 "!@#"
|
|
9714
|
+
* - lower(123) \u2192 null
|
|
9715
|
+
* - lower(null) \u2192 null
|
|
9716
|
+
*
|
|
9717
|
+
* @param val - The value to convert to lowercase
|
|
9718
|
+
* @returns The lowercase string, or null if input is not a string
|
|
9719
|
+
*/
|
|
9720
|
+
export function lowerString(val: Value): Value {
|
|
9721
|
+
if (!val.isString()) return Value.Null();
|
|
9722
|
+
const str = val.asString();
|
|
9723
|
+
return Value.String(str.toLowerCase());
|
|
9724
|
+
}
|
|
9725
|
+
|
|
9726
|
+
/**
|
|
9727
|
+
* Remove leading and trailing whitespace from a string.
|
|
9728
|
+
*
|
|
9729
|
+
* Whitespace includes spaces, tabs, newlines, and other whitespace characters.
|
|
9730
|
+
* If the input is not a string, returns null.
|
|
9731
|
+
*
|
|
9732
|
+
* Examples:
|
|
9733
|
+
* - trim(" hello ") \u2192 "hello"
|
|
9734
|
+
* - trim("\\t\\nhello\\n\\t") \u2192 "hello"
|
|
9735
|
+
* - trim("hello") \u2192 "hello" (no change)
|
|
9736
|
+
* - trim(" hello world ") \u2192 "hello world"
|
|
9737
|
+
* - trim("") \u2192 "" (empty string)
|
|
9738
|
+
* - trim(" ") \u2192 "" (only whitespace)
|
|
9739
|
+
* - trim(123) \u2192 null
|
|
9740
|
+
* - trim(null) \u2192 null
|
|
9741
|
+
*
|
|
9742
|
+
* @param val - The value to trim whitespace from
|
|
9743
|
+
* @returns The trimmed string, or null if input is not a string
|
|
9744
|
+
*/
|
|
9745
|
+
export function trimString(val: Value): Value {
|
|
9746
|
+
if (!val.isString()) return Value.Null();
|
|
9747
|
+
const str = val.asString();
|
|
9748
|
+
return Value.String(str.trim());
|
|
9749
|
+
}
|
|
9750
|
+
|
|
9751
|
+
/**
|
|
9752
|
+
* Extract a portion of a string.
|
|
9753
|
+
*
|
|
9754
|
+
* Returns a substring starting at the specified index and optionally
|
|
9755
|
+
* ending at the specified index (or length).
|
|
9756
|
+
*
|
|
9757
|
+
* Parameters:
|
|
9758
|
+
* - str: The input string
|
|
9759
|
+
* - start: The starting index (0-based, inclusive)
|
|
9760
|
+
* - endOrLength: Optional end index (exclusive) or length. If omitted,
|
|
9761
|
+
* extracts from start to the end of the string.
|
|
9762
|
+
*
|
|
9763
|
+
* Behavior:
|
|
9764
|
+
* - If start is negative, it's treated as an offset from the end of the string
|
|
9765
|
+
* - If start < 0, it's clamped to 0 (empty prefix)
|
|
9766
|
+
* - If start >= str.length, returns empty string
|
|
9767
|
+
* - If endOrLength is omitted or null, extracts to end of string
|
|
9768
|
+
* - If endOrLength is negative, it's treated as an offset from the end
|
|
9769
|
+
* - If endOrLength > str.length, it's clamped to str.length
|
|
9770
|
+
* - If endOrLength <= start, returns empty string
|
|
9771
|
+
*
|
|
9772
|
+
* Examples:
|
|
9773
|
+
* - substring("hello", 0, 2) \u2192 "he"
|
|
9774
|
+
* - substring("hello", 1, 3) \u2192 "el"
|
|
9775
|
+
* - substring("hello", 2) \u2192 "llo"
|
|
9776
|
+
* - substring("hello", 0) \u2192 "hello"
|
|
9777
|
+
* - substring("hello", 0, 5) \u2192 "hello"
|
|
9778
|
+
* - substring("hello", 0, 10) \u2192 "hello" (clamped to end)
|
|
9779
|
+
* - substring("hello", 3, 5) \u2192 "lo"
|
|
9780
|
+
* - substring("hello", -3) \u2192 "llo" (from end)
|
|
9781
|
+
* - substring("hello", 0, -2) \u2192 "hel" (from end)
|
|
9782
|
+
* - substring("hello", 10) \u2192 "" (start beyond length)
|
|
9783
|
+
* - substring("", 0, 5) \u2192 "" (empty string)
|
|
9784
|
+
* - substring(123, 0, 2) \u2192 null (not a string)
|
|
9785
|
+
* - substring(null, 0, 2) \u2192 null (not a string)
|
|
9786
|
+
*
|
|
9787
|
+
* @param strVal - The string value to extract from
|
|
9788
|
+
* @param startVal - The starting index
|
|
9789
|
+
* @param endOrLengthVal - Optional end index (exclusive) or length
|
|
9790
|
+
* @returns The substring, or null if input is not a string
|
|
9791
|
+
*/
|
|
9792
|
+
export function substringString(strVal: Value, startVal: Value, endOrLengthVal: Value): Value {
|
|
9793
|
+
if (!strVal.isString()) return Value.Null();
|
|
9794
|
+
const str = strVal.asString();
|
|
9795
|
+
const len = str.length;
|
|
9796
|
+
|
|
9797
|
+
if (len == 0) return Value.String('');
|
|
9798
|
+
|
|
9799
|
+
// Parse start index - use asFloat() to handle both Int and Float types
|
|
9800
|
+
let start: i32 = 0;
|
|
9801
|
+
if (startVal.isNumber()) {
|
|
9802
|
+
start = <i32>startVal.asFloat();
|
|
9803
|
+
}
|
|
9804
|
+
|
|
9805
|
+
// Handle negative start (offset from end)
|
|
9806
|
+
if (start < 0) {
|
|
9807
|
+
start = len + start;
|
|
9808
|
+
if (start < 0) start = 0; // Clamp to 0
|
|
9809
|
+
}
|
|
9810
|
+
|
|
9811
|
+
// Handle start beyond length
|
|
9812
|
+
if (start >= len) {
|
|
9813
|
+
return Value.String('');
|
|
9814
|
+
}
|
|
9815
|
+
|
|
9816
|
+
// Parse end index if provided
|
|
9817
|
+
let hasEnd = !endOrLengthVal.isNull();
|
|
9818
|
+
let end: i32 = len; // Default to end of string
|
|
9819
|
+
|
|
9820
|
+
if (hasEnd && endOrLengthVal.isNumber()) {
|
|
9821
|
+
end = <i32>endOrLengthVal.asFloat();
|
|
9822
|
+
|
|
9823
|
+
// Handle negative end (offset from end)
|
|
9824
|
+
if (end < 0) {
|
|
9825
|
+
end = len + end;
|
|
9826
|
+
if (end < 0) end = 0;
|
|
9827
|
+
}
|
|
9828
|
+
|
|
9829
|
+
// Clamp end to string length
|
|
9830
|
+
if (end > len) end = len;
|
|
9831
|
+
|
|
9832
|
+
// Ensure end >= start
|
|
9833
|
+
if (end < start) end = start;
|
|
9834
|
+
}
|
|
9835
|
+
|
|
9836
|
+
// Extract substring
|
|
9837
|
+
return Value.String(str.substring(start, end));
|
|
9838
|
+
}
|
|
9839
|
+
|
|
9840
|
+
/**
|
|
9841
|
+
* Find the index of the first occurrence of a substring.
|
|
9842
|
+
*
|
|
9843
|
+
* Returns the index of the first occurrence of the search string,
|
|
9844
|
+
* starting from the specified position. Returns -1 if not found.
|
|
9845
|
+
*
|
|
9846
|
+
* Parameters:
|
|
9847
|
+
* - str: The string to search in
|
|
9848
|
+
* - searchVal: The substring to search for
|
|
9849
|
+
* - startVal: Optional starting position (default: 0)
|
|
9850
|
+
*
|
|
9851
|
+
* Behavior:
|
|
9852
|
+
* - If the source string is not a string, returns null
|
|
9853
|
+
* - If the search value is not a string, returns null
|
|
9854
|
+
* - If start is negative or omitted, defaults to 0
|
|
9855
|
+
* - If start > str.length, returns -1 (not found)
|
|
9856
|
+
* - If search string is empty, returns the start position
|
|
9857
|
+
* - Returns -1 if the substring is not found
|
|
9858
|
+
*
|
|
9859
|
+
* Examples:
|
|
9860
|
+
* - indexOf("hello", "l") \u2192 2
|
|
9861
|
+
* - indexOf("hello", "x") \u2192 -1
|
|
9862
|
+
* - indexOf("hello", "l", 3) \u2192 3
|
|
9863
|
+
* - indexOf("hello", "ll") \u2192 2
|
|
9864
|
+
* - indexOf("hello", "lo", 0) \u2192 3
|
|
9865
|
+
* - indexOf("hello", "") \u2192 0 (empty search string)
|
|
9866
|
+
* - indexOf("hello", "o", 5) \u2192 -1 (start at end)
|
|
9867
|
+
* - indexOf("hello", "o", 10) \u2192 -1 (start beyond length)
|
|
9868
|
+
* - indexOf("", "x") \u2192 -1 (empty source)
|
|
9869
|
+
* - indexOf("", "") \u2192 0 (both empty)
|
|
9870
|
+
* - indexOf(123, "1") \u2192 null (not a string)
|
|
9871
|
+
*
|
|
9872
|
+
* @param strVal - The string value to search in
|
|
9873
|
+
* @param searchVal - The substring value to search for
|
|
9874
|
+
* @param startVal - Optional starting position
|
|
9875
|
+
* @returns The index of the first occurrence, or -1 if not found, or null if inputs are not strings
|
|
9876
|
+
*/
|
|
9877
|
+
export function indexOfString(strVal: Value, searchVal: Value, startVal: Value): Value {
|
|
9878
|
+
if (!strVal.isString() || !searchVal.isString()) return Value.Null();
|
|
9879
|
+
const str = strVal.asString();
|
|
9880
|
+
const search = searchVal.asString();
|
|
9881
|
+
|
|
9882
|
+
// Parse start index
|
|
9883
|
+
let start: i32 = 0;
|
|
9884
|
+
if (!startVal.isNull() && startVal.isNumber()) {
|
|
9885
|
+
start = <i32>startVal.asFloat();
|
|
9886
|
+
}
|
|
9887
|
+
|
|
9888
|
+
// Handle negative start
|
|
9889
|
+
if (start < 0) {
|
|
9890
|
+
start = 0;
|
|
9891
|
+
}
|
|
9892
|
+
|
|
9893
|
+
// Special case: empty search string (matches JavaScript behavior)
|
|
9894
|
+
// When searching for empty string, return the start position clamped to string length
|
|
9895
|
+
if (search.length == 0) {
|
|
9896
|
+
const len = <i32>str.length;
|
|
9897
|
+
if (start >= len) {
|
|
9898
|
+
return Value.Int(<i64>len);
|
|
9899
|
+
}
|
|
9900
|
+
return Value.Int(<i64>start);
|
|
9901
|
+
}
|
|
9902
|
+
|
|
9903
|
+
// AssemblyScript's indexOf returns -1 for not found
|
|
9904
|
+
const index = str.indexOf(search, start);
|
|
9905
|
+
return Value.Int(<i64>index);
|
|
9906
|
+
}
|
|
9907
|
+
|
|
9908
|
+
/**
|
|
9909
|
+
* Check if a string starts with a given prefix.
|
|
9910
|
+
*
|
|
9911
|
+
* Returns true if the string begins with the specified prefix, false otherwise.
|
|
9912
|
+
*
|
|
9913
|
+
* Parameters:
|
|
9914
|
+
* - str: The string to check
|
|
9915
|
+
* - prefix: The prefix to look for
|
|
9916
|
+
*
|
|
9917
|
+
* Behavior:
|
|
9918
|
+
* - If either input is not a string, returns null
|
|
9919
|
+
* - If the prefix is an empty string, returns true
|
|
9920
|
+
* - Case-sensitive comparison
|
|
9921
|
+
*
|
|
9922
|
+
* Examples:
|
|
9923
|
+
* - startsWith("hello", "he") \u2192 true
|
|
9924
|
+
* - startsWith("hello", "lo") \u2192 false
|
|
9925
|
+
* - startsWith("hello", "hello") \u2192 true
|
|
9926
|
+
* - startsWith("hello", "hello world") \u2192 false (prefix longer than string)
|
|
9927
|
+
* - startsWith("", "") \u2192 true (empty strings)
|
|
9928
|
+
* - startsWith("hello", "") \u2192 true (empty prefix)
|
|
9929
|
+
* - startsWith("", "he") \u2192 false (prefix longer than empty string)
|
|
9930
|
+
* - startsWith("Hello", "he") \u2192 false (case-sensitive)
|
|
9931
|
+
* - startsWith(123, "1") \u2192 null (not a string)
|
|
9932
|
+
* - startsWith(null, "he") \u2192 null (not a string)
|
|
9933
|
+
*
|
|
9934
|
+
* @param strVal - The string value to check
|
|
9935
|
+
* @param prefixVal - The prefix value to look for
|
|
9936
|
+
* @returns true if the string starts with the prefix, false otherwise, or null if inputs are not strings
|
|
9937
|
+
*/
|
|
9938
|
+
export function startsWithString(strVal: Value, prefixVal: Value): Value {
|
|
9939
|
+
if (!strVal.isString() || !prefixVal.isString()) return Value.Null();
|
|
9940
|
+
const str = strVal.asString();
|
|
9941
|
+
const prefix = prefixVal.asString();
|
|
9942
|
+
|
|
9943
|
+
// AssemblyScript's startsWith returns a bool
|
|
9944
|
+
const result = str.startsWith(prefix);
|
|
9945
|
+
return Value.Bool(result);
|
|
9946
|
+
}
|
|
9947
|
+
|
|
9948
|
+
/**
|
|
9949
|
+
* Check if a string ends with a given suffix.
|
|
9950
|
+
*
|
|
9951
|
+
* Returns true if the string ends with the specified suffix, false otherwise.
|
|
9952
|
+
*
|
|
9953
|
+
* Parameters:
|
|
9954
|
+
* - str: The string to check
|
|
9955
|
+
* - suffix: The suffix to look for
|
|
9956
|
+
*
|
|
9957
|
+
* Behavior:
|
|
9958
|
+
* - If either input is not a string, returns null
|
|
9959
|
+
* - If the suffix is an empty string, returns true
|
|
9960
|
+
* - Case-sensitive comparison
|
|
9961
|
+
*
|
|
9962
|
+
* Examples:
|
|
9963
|
+
* - endsWith("hello", "lo") \u2192 true
|
|
9964
|
+
* - endsWith("hello", "he") \u2192 false
|
|
9965
|
+
* - endsWith("hello", "hello") \u2192 true
|
|
9966
|
+
* - endsWith("hello", "hello world") \u2192 false (suffix longer than string)
|
|
9967
|
+
* - endsWith("", "") \u2192 true (empty strings)
|
|
9968
|
+
* - endsWith("hello", "") \u2192 true (empty suffix)
|
|
9969
|
+
* - endsWith("", "lo") \u2192 false (suffix longer than empty string)
|
|
9970
|
+
* - endsWith("Hello", "LO") \u2192 false (case-sensitive)
|
|
9971
|
+
* - endsWith(123, "1") \u2192 null (not a string)
|
|
9972
|
+
* - endsWith(null, "lo") \u2192 null (not a string)
|
|
9973
|
+
*
|
|
9974
|
+
* @param strVal - The string value to check
|
|
9975
|
+
* @param suffixVal - The suffix value to look for
|
|
9976
|
+
* @returns true if the string ends with the suffix, false otherwise, or null if inputs are not strings
|
|
9977
|
+
*/
|
|
9978
|
+
export function endsWithString(strVal: Value, suffixVal: Value): Value {
|
|
9979
|
+
if (!strVal.isString() || !suffixVal.isString()) return Value.Null();
|
|
9980
|
+
const str = strVal.asString();
|
|
9981
|
+
const suffix = suffixVal.asString();
|
|
9982
|
+
|
|
9983
|
+
// AssemblyScript's endsWith returns a bool
|
|
9984
|
+
const result = str.endsWith(suffix);
|
|
9985
|
+
return Value.Bool(result);
|
|
9986
|
+
}
|
|
9987
|
+
|
|
9988
|
+
/**
|
|
9989
|
+
* Split a string into an array based on a delimiter.
|
|
9990
|
+
*
|
|
9991
|
+
* Returns an array of substrings by dividing the input string at each
|
|
9992
|
+
* occurrence of the specified delimiter.
|
|
9993
|
+
*
|
|
9994
|
+
* Parameters:
|
|
9995
|
+
* - str: The string to split
|
|
9996
|
+
* - delimiter: The delimiter to split on
|
|
9997
|
+
*
|
|
9998
|
+
* Behavior:
|
|
9999
|
+
* - If the input string is not a string, returns null
|
|
10000
|
+
* - If the delimiter is not a string, returns null
|
|
10001
|
+
* - If the delimiter is an empty string, returns an array of individual characters
|
|
10002
|
+
* - If the string and delimiter are both empty, returns an array with one empty string
|
|
10003
|
+
* - If the delimiter is not found, returns an array with the original string
|
|
10004
|
+
*
|
|
10005
|
+
* Examples:
|
|
10006
|
+
* - split("a,b,c", ",") \u2192 ["a", "b", "c"]
|
|
10007
|
+
* - split("hello", "") \u2192 ["h", "e", "l", "l", "o"]
|
|
10008
|
+
* - split("a-b-c", "-") \u2192 ["a", "b", "c"]
|
|
10009
|
+
* - split("word", "x") \u2192 ["word"]
|
|
10010
|
+
* - split("", ",") \u2192 [""]
|
|
10011
|
+
* - split("", "") \u2192 [""] (special case: empty string splits into array with one empty string)
|
|
10012
|
+
* - split("a,,b", ",") \u2192 ["a", "", "b"] (preserves empty segments)
|
|
10013
|
+
* - split(null, ",") \u2192 null
|
|
10014
|
+
* - split("hello", null) \u2192 null
|
|
10015
|
+
* - split("hello", 123) \u2192 null
|
|
10016
|
+
*
|
|
10017
|
+
* @param strVal - The string value to split
|
|
10018
|
+
* @param delimiterVal - The delimiter value to split on
|
|
10019
|
+
* @returns An array of strings, or null if inputs are invalid
|
|
10020
|
+
*/
|
|
10021
|
+
export function splitString(strVal: Value, delimiterVal: Value): Value {
|
|
10022
|
+
if (!strVal.isString() || !delimiterVal.isString()) return Value.Null();
|
|
10023
|
+
const str = strVal.asString();
|
|
10024
|
+
const delimiter = delimiterVal.asString();
|
|
10025
|
+
|
|
10026
|
+
const parts = str.split(delimiter);
|
|
10027
|
+
|
|
10028
|
+
// Convert string array to Value array
|
|
10029
|
+
const result = new Array<Value>(parts.length);
|
|
10030
|
+
for (let i = 0; i < parts.length; i++) {
|
|
10031
|
+
unchecked((result[i] = Value.String(unchecked(parts[i]))));
|
|
10032
|
+
}
|
|
10033
|
+
|
|
10034
|
+
return Value.Array(result);
|
|
10035
|
+
}
|
|
10036
|
+
|
|
10037
|
+
/**
|
|
10038
|
+
* Join array elements into a string with a separator.
|
|
10039
|
+
*
|
|
10040
|
+
* Returns a string that concatenates all elements of the array,
|
|
10041
|
+
* separated by the specified delimiter.
|
|
10042
|
+
*
|
|
10043
|
+
* Parameters:
|
|
10044
|
+
* - arr: The array to join
|
|
10045
|
+
* - delimiter: The delimiter to insert between elements
|
|
10046
|
+
*
|
|
10047
|
+
* Behavior:
|
|
10048
|
+
* - If the input is not an array, returns null
|
|
10049
|
+
* - If the delimiter is not a string, returns null
|
|
10050
|
+
* - If the array is empty, returns an empty string
|
|
10051
|
+
* - Each array element is converted to its string representation
|
|
10052
|
+
* - The delimiter is placed between each adjacent element, not at the start or end
|
|
10053
|
+
*
|
|
10054
|
+
* Examples:
|
|
10055
|
+
* - join(["a", "b", "c"], "-") \u2192 "a-b-c"
|
|
10056
|
+
* - join(["hello", "world"], " ") \u2192 "hello world"
|
|
10057
|
+
* - join(["a"], ",") \u2192 "a"
|
|
10058
|
+
* - join([], ",") \u2192 ""
|
|
10059
|
+
* - join([1, 2, 3], "-") \u2192 "1-2-3" (numbers converted to strings)
|
|
10060
|
+
* - join([true, false], " ") \u2192 "true false" (booleans converted to strings)
|
|
10061
|
+
* - join(["a", null, "b"], "-") \u2192 "a-null-b" (null converted to "null")
|
|
10062
|
+
* - join(null, ",") \u2192 null
|
|
10063
|
+
* - join(["a"], null) \u2192 null
|
|
10064
|
+
* - join(["a"], 123) \u2192 null
|
|
10065
|
+
*
|
|
10066
|
+
* @param arrVal - The array value to join
|
|
10067
|
+
* @param delimiterVal - The delimiter to insert between elements
|
|
10068
|
+
* @returns The concatenated string, or null if inputs are invalid
|
|
10069
|
+
*/
|
|
10070
|
+
export function joinArray(arrVal: Value, delimiterVal: Value): Value {
|
|
10071
|
+
if (!arrVal.isArray() || !delimiterVal.isString()) return Value.Null();
|
|
10072
|
+
const arr = arrVal.asArray();
|
|
10073
|
+
const delimiter = delimiterVal.asString();
|
|
10074
|
+
|
|
10075
|
+
// Handle empty array
|
|
10076
|
+
if (arr.length == 0) {
|
|
10077
|
+
return Value.String('');
|
|
10078
|
+
}
|
|
10079
|
+
|
|
10080
|
+
// Build the result string
|
|
10081
|
+
let result: string = '';
|
|
10082
|
+
for (let i = 0; i < arr.length; i++) {
|
|
10083
|
+
// Append delimiter if not first element
|
|
10084
|
+
if (i > 0) {
|
|
10085
|
+
result += delimiter;
|
|
10086
|
+
}
|
|
10087
|
+
// Convert value to string and append
|
|
10088
|
+
result += valueToString(unchecked(arr[i]));
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
return Value.String(result);
|
|
10092
|
+
}
|
|
10093
|
+
|
|
10094
|
+
/**
|
|
10095
|
+
* Convert a string value to a number.
|
|
10096
|
+
*
|
|
10097
|
+
* Parses a string containing a numeric value and returns it as a float.
|
|
10098
|
+
* Supports decimal numbers and negative numbers.
|
|
10099
|
+
*
|
|
10100
|
+
* Parameters:
|
|
10101
|
+
* - val: The value to convert to a number (must be a string)
|
|
10102
|
+
*
|
|
10103
|
+
* Behavior:
|
|
10104
|
+
* - If input is not a string, returns null
|
|
10105
|
+
* - If string cannot be parsed as a number, returns NaN
|
|
10106
|
+
* - Returns f64 (float) for all numeric values
|
|
10107
|
+
*
|
|
10108
|
+
* Examples:
|
|
10109
|
+
* - number("123") \u2192 123.0
|
|
10110
|
+
* - number("3.14") \u2192 3.14
|
|
10111
|
+
* - number("-42") \u2192 -42.0
|
|
10112
|
+
* - number("0.5") \u2192 0.5
|
|
10113
|
+
* - number("not a number") \u2192 NaN
|
|
10114
|
+
* - number(null) \u2192 null
|
|
10115
|
+
* - number(123) \u2192 null (not a string)
|
|
10116
|
+
*
|
|
10117
|
+
* @param val - The value to convert to a number
|
|
10118
|
+
* @returns The numeric value as a float, or null if input is not a string
|
|
10119
|
+
*/
|
|
10120
|
+
export function numberValue(val: Value): Value {
|
|
10121
|
+
if (val.isString()) {
|
|
10122
|
+
const str = val.asString();
|
|
10123
|
+
// Parse the string using the same logic as parseIntValue
|
|
10124
|
+
let result: i64 = 0;
|
|
10125
|
+
let negative = false;
|
|
10126
|
+
let i = 0;
|
|
10127
|
+
|
|
10128
|
+
// Handle leading whitespace
|
|
10129
|
+
while (i < str.length && str.charCodeAt(i) == 32) {
|
|
10130
|
+
i++;
|
|
10131
|
+
}
|
|
10132
|
+
|
|
10133
|
+
// Handle negative sign
|
|
10134
|
+
if (i < str.length && str.charCodeAt(i) == 45) {
|
|
10135
|
+
// '-'
|
|
10136
|
+
negative = true;
|
|
10137
|
+
i++;
|
|
10138
|
+
}
|
|
10139
|
+
|
|
10140
|
+
// Parse digits
|
|
10141
|
+
let hasDigits = false;
|
|
10142
|
+
while (i < str.length) {
|
|
10143
|
+
const c = str.charCodeAt(i);
|
|
10144
|
+
if (c >= 48 && c <= 57) {
|
|
10145
|
+
// '0'-'9'
|
|
10146
|
+
result = result * 10 + (c - 48);
|
|
10147
|
+
hasDigits = true;
|
|
10148
|
+
i++;
|
|
10149
|
+
} else {
|
|
10150
|
+
break;
|
|
10151
|
+
}
|
|
10152
|
+
}
|
|
10153
|
+
|
|
10154
|
+
if (!hasDigits) return Value.Null();
|
|
10155
|
+
|
|
10156
|
+
const finalResult = negative ? -result : result;
|
|
10157
|
+
return Value.Float(<f64>finalResult);
|
|
10158
|
+
} else if (val.isNumber()) {
|
|
10159
|
+
return val;
|
|
10160
|
+
} else if (val.isBool()) {
|
|
10161
|
+
return val.asBool() ? Value.Float(1.0) : Value.Float(0.0);
|
|
10162
|
+
}
|
|
10163
|
+
return Value.Null();
|
|
10164
|
+
}
|
|
10165
|
+
|
|
10166
|
+
/**
|
|
10167
|
+
* Convert any value to a string.
|
|
10168
|
+
*
|
|
10169
|
+
* Converts the input value to its string representation.
|
|
10170
|
+
*
|
|
10171
|
+
* Parameters:
|
|
10172
|
+
* - val: Any value to convert to string
|
|
10173
|
+
*
|
|
10174
|
+
* Behavior:
|
|
10175
|
+
* - Boolean: "true" or "false"
|
|
10176
|
+
* - Number: numeric string representation
|
|
10177
|
+
* - String: returned as-is
|
|
10178
|
+
* - Null: null (returns null, not the string "null")
|
|
10179
|
+
* - Array/Object: "[object]"
|
|
10180
|
+
*
|
|
10181
|
+
* Examples:
|
|
10182
|
+
* - string(42) \u2192 "42"
|
|
10183
|
+
* - string(3.14) \u2192 "3.14"
|
|
10184
|
+
* - string(true) \u2192 "true"
|
|
10185
|
+
* - string(false) \u2192 "false"
|
|
10186
|
+
* - string("hello") \u2192 "hello"
|
|
10187
|
+
* - string(null) \u2192 null
|
|
10188
|
+
*
|
|
10189
|
+
* @param val - The value to convert to a string
|
|
10190
|
+
* @returns The string representation, or null if input is null
|
|
10191
|
+
*/
|
|
10192
|
+
export function stringValue(val: Value): Value {
|
|
10193
|
+
if (val.isNull()) {
|
|
10194
|
+
return Value.Null();
|
|
10195
|
+
}
|
|
10196
|
+
if (val.isBool()) {
|
|
10197
|
+
return Value.String(val.asBool() ? 'true' : 'false');
|
|
10198
|
+
}
|
|
10199
|
+
if (val.isNumber()) {
|
|
10200
|
+
// Converting to string matches JavaScript String() coercion for consistency with JS engines.
|
|
10201
|
+
// Integers print without decimals (3 not 3.0), floats use AS toString() formatting.
|
|
10202
|
+
if (val.type == TYPE_INT) {
|
|
10203
|
+
return Value.String(val.intVal.toString());
|
|
10204
|
+
}
|
|
10205
|
+
const f = val.asFloat();
|
|
10206
|
+
// Check if it's a whole number by testing if remainder is 0
|
|
10207
|
+
if (f % 1.0 == 0.0) {
|
|
10208
|
+
return Value.String(i64(<i64>f).toString());
|
|
10209
|
+
}
|
|
10210
|
+
return Value.String(f.toString());
|
|
10211
|
+
}
|
|
10212
|
+
if (val.isString()) {
|
|
10213
|
+
return val;
|
|
10214
|
+
}
|
|
10215
|
+
// Arrays and objects fall back to a placeholder
|
|
10216
|
+
return Value.String('[object]');
|
|
10217
|
+
}
|
|
10218
|
+
|
|
10219
|
+
/**
|
|
10220
|
+
* Replace the first occurrence of a substring with another string.
|
|
10221
|
+
*
|
|
10222
|
+
* Returns a new string where the first occurrence of the search string
|
|
10223
|
+
* is replaced by the replacement string.
|
|
10224
|
+
*
|
|
10225
|
+
* Parameters:
|
|
10226
|
+
* - str: The string to modify
|
|
10227
|
+
* - search: The substring to search for
|
|
10228
|
+
* - replacement: The string to replace with
|
|
10229
|
+
*
|
|
10230
|
+
* Behavior:
|
|
10231
|
+
* - If the source string is not a string, returns null
|
|
10232
|
+
* - If the search value is not a string, returns null
|
|
10233
|
+
* - If the replacement value is not a string, returns null
|
|
10234
|
+
* - If the search string is empty, the replacement is inserted at the beginning
|
|
10235
|
+
* - If the search string is not found, returns the original string unchanged
|
|
10236
|
+
* - Only the first occurrence is replaced
|
|
10237
|
+
*
|
|
10238
|
+
* Examples:
|
|
10239
|
+
* - replace("hello", "l", "L") \u2192 "heLlo"
|
|
10240
|
+
* - replace("hello", "lo", "LO") \u2192 "heLLO"
|
|
10241
|
+
* - replace("hello", "x", "X") \u2192 "hello" (not found, unchanged)
|
|
10242
|
+
* - replace("hello hello", "l", "L") \u2192 "heLlo hello" (only first occurrence)
|
|
10243
|
+
* - replace("hello", "", "X") \u2192 "Xhello" (empty search, insert at start)
|
|
10244
|
+
* - replace("", "x", "X") \u2192 "" (empty source, no match)
|
|
10245
|
+
* - replace(null, "l", "L") \u2192 null (not a string)
|
|
10246
|
+
* - replace("hello", null, "L") \u2192 null (not a string)
|
|
10247
|
+
* - replace("hello", "l", null) \u2192 null (not a string)
|
|
10248
|
+
*
|
|
10249
|
+
* @param strVal - The string value to modify
|
|
10250
|
+
* @param searchVal - The substring value to search for
|
|
10251
|
+
* @param replacementVal - The replacement string value
|
|
10252
|
+
* @returns The modified string with first occurrence replaced, or null if inputs are invalid
|
|
10253
|
+
*/
|
|
10254
|
+
export function replaceString(strVal: Value, searchVal: Value, replacementVal: Value): Value {
|
|
10255
|
+
if (!strVal.isString() || !searchVal.isString() || !replacementVal.isString())
|
|
10256
|
+
return Value.Null();
|
|
10257
|
+
const str = strVal.asString();
|
|
10258
|
+
const search = searchVal.asString();
|
|
10259
|
+
const replacement = replacementVal.asString();
|
|
10260
|
+
|
|
10261
|
+
// AssemblyScript's replace() replaces all occurrences by default
|
|
10262
|
+
// We need to replace only the first occurrence, so we use indexOf and string concatenation
|
|
10263
|
+
const index = str.indexOf(search);
|
|
10264
|
+
|
|
10265
|
+
// Search string not found - return original string
|
|
10266
|
+
if (index < 0) {
|
|
10267
|
+
return Value.String(str);
|
|
10268
|
+
}
|
|
10269
|
+
|
|
10270
|
+
// Build result: prefix + replacement + suffix
|
|
10271
|
+
const prefix = str.substring(0, index);
|
|
10272
|
+
const suffix = str.substring(index + search.length);
|
|
10273
|
+
return Value.String(prefix + replacement + suffix);
|
|
10274
|
+
}
|
|
10275
|
+
|
|
10276
|
+
/**
|
|
10277
|
+
* Replace all occurrences of a substring with another string.
|
|
10278
|
+
*
|
|
10279
|
+
* Returns a new string where all occurrences of the search string
|
|
10280
|
+
* are replaced by the replacement string.
|
|
10281
|
+
*
|
|
10282
|
+
* Parameters:
|
|
10283
|
+
* - str: The string to modify
|
|
10284
|
+
* - search: The substring to search for
|
|
10285
|
+
* - replacement: The string to replace with
|
|
10286
|
+
*
|
|
10287
|
+
* Behavior:
|
|
10288
|
+
* - If the source string is not a string, returns null
|
|
10289
|
+
* - If the search value is not a string, returns null
|
|
10290
|
+
* - If the replacement value is not a string, returns null
|
|
10291
|
+
* - If the search string is empty, the replacement is inserted between each character
|
|
10292
|
+
* - If the search string is not found, returns the original string unchanged
|
|
10293
|
+
* - All occurrences are replaced
|
|
10294
|
+
*
|
|
10295
|
+
* Examples:
|
|
10296
|
+
* - replaceAll("hello", "l", "L") \u2192 "heLLo"
|
|
10297
|
+
* - replaceAll("hello hello", "l", "L") \u2192 "heLLo heLLo"
|
|
10298
|
+
* - replaceAll("hello", "lo", "LO") \u2192 "heLLO"
|
|
10299
|
+
* - replaceAll("hello", "x", "X") \u2192 "hello" (not found, unchanged)
|
|
10300
|
+
* - replaceAll("hello", "", "X") \u2192 "XhXeXlXlXoX" (empty search, insert between chars)
|
|
10301
|
+
* - replaceAll("", "x", "X") \u2192 "" (empty source, no match)
|
|
10302
|
+
* - replaceAll(null, "l", "L") \u2192 null (not a string)
|
|
10303
|
+
* - replaceAll("hello", null, "L") \u2192 null (not a string)
|
|
10304
|
+
* - replaceAll("hello", "l", null) \u2192 null (not a string)
|
|
10305
|
+
*
|
|
10306
|
+
* @param strVal - The string value to modify
|
|
10307
|
+
* @param searchVal - The substring value to search for
|
|
10308
|
+
* @param replacementVal - The replacement string value
|
|
10309
|
+
* @returns The modified string with all occurrences replaced, or null if inputs are invalid
|
|
10310
|
+
*/
|
|
10311
|
+
export function replaceAllString(strVal: Value, searchVal: Value, replacementVal: Value): Value {
|
|
10312
|
+
if (!strVal.isString() || !searchVal.isString() || !replacementVal.isString())
|
|
10313
|
+
return Value.Null();
|
|
10314
|
+
let str = strVal.asString();
|
|
10315
|
+
const search = searchVal.asString();
|
|
10316
|
+
const replacement = replacementVal.asString();
|
|
10317
|
+
|
|
10318
|
+
// Handle empty search string - insert replacement between each character
|
|
10319
|
+
if (search.length == 0) {
|
|
10320
|
+
let result = replacement;
|
|
10321
|
+
for (let i = 0; i < str.length; i++) {
|
|
10322
|
+
result += str.charAt(i) + replacement;
|
|
10323
|
+
}
|
|
10324
|
+
return Value.String(result);
|
|
10325
|
+
}
|
|
10326
|
+
|
|
10327
|
+
// Use split and join for O(n) complexity instead of quadratic substring approach
|
|
10328
|
+
const parts = str.split(search);
|
|
10329
|
+
return Value.String(parts.join(replacement));
|
|
10330
|
+
}
|
|
10331
|
+
|
|
10332
|
+
// === Value Type Conversion ===
|
|
10333
|
+
|
|
10334
|
+
/**
|
|
10335
|
+
* Convert a Value to its string representation.
|
|
10336
|
+
*
|
|
10337
|
+
* This follows JavaScript's String() conversion rules:
|
|
10338
|
+
* - Number: converted using JS toString()
|
|
10339
|
+
* - Boolean: "true" or "false"
|
|
10340
|
+
* - String: returned as-is
|
|
10341
|
+
* - Null: "null"
|
|
10342
|
+
*
|
|
10343
|
+
* @param val - The value to convert
|
|
10344
|
+
* @returns The string representation
|
|
10345
|
+
*/
|
|
10346
|
+
function valueToString(val: Value): string {
|
|
10347
|
+
if (val.isNull()) {
|
|
10348
|
+
return 'null';
|
|
10349
|
+
}
|
|
10350
|
+
if (val.isBool()) {
|
|
10351
|
+
return val.asBool() ? 'true' : 'false';
|
|
10352
|
+
}
|
|
10353
|
+
if (val.isNumber()) {
|
|
10354
|
+
// Converting to string matches JavaScript String() coercion for consistency with JS engines.
|
|
10355
|
+
// Integers print without decimals (3 not 3.0), floats use AS toString() formatting.
|
|
10356
|
+
if (val.type == TYPE_INT) {
|
|
10357
|
+
return val.intVal.toString();
|
|
10358
|
+
}
|
|
10359
|
+
const f = val.asFloat();
|
|
10360
|
+
// Check if it's a whole number by testing if remainder is 0
|
|
10361
|
+
if (f % 1.0 == 0.0) {
|
|
10362
|
+
return i64(<i64>f).toString();
|
|
10363
|
+
}
|
|
10364
|
+
return f.toString();
|
|
10365
|
+
}
|
|
10366
|
+
if (val.isString()) {
|
|
10367
|
+
return val.asString();
|
|
10368
|
+
}
|
|
10369
|
+
// Arrays and objects fall back to a placeholder
|
|
10370
|
+
return '[object]';
|
|
10371
|
+
}
|
|
10372
|
+
|
|
10373
|
+
// === Comparison Helpers ===
|
|
10374
|
+
|
|
10375
|
+
export function valuesEqual(a: Value, b: Value): bool {
|
|
10376
|
+
if (a.type != b.type) {
|
|
10377
|
+
// Allow int/float comparison
|
|
10378
|
+
if (a.isNumber() && b.isNumber()) {
|
|
10379
|
+
return a.asFloat() == b.asFloat();
|
|
10380
|
+
}
|
|
10381
|
+
return false;
|
|
10382
|
+
}
|
|
10383
|
+
if (a.isBool()) return a.boolVal == b.boolVal;
|
|
10384
|
+
if (a.type == TYPE_INT) return a.intVal == b.intVal;
|
|
10385
|
+
if (a.type == TYPE_FLOAT) return a.floatVal == b.floatVal;
|
|
10386
|
+
if (a.isString()) return a.asString() == b.asString();
|
|
10387
|
+
if (a.isNull()) return true; // null == null
|
|
10388
|
+
return false; // Arrays/objects: reference equality not supported
|
|
10389
|
+
}
|
|
10390
|
+
|
|
10391
|
+
/**
|
|
10392
|
+
* Compare two values for ordering.
|
|
10393
|
+
*
|
|
10394
|
+
* Returns:
|
|
10395
|
+
* - Negative number if a < b
|
|
10396
|
+
* - Zero if a == b
|
|
10397
|
+
* - Positive number if a > b
|
|
10398
|
+
*
|
|
10399
|
+
* Comparison rules:
|
|
10400
|
+
* - Strings: Lexicographic comparison using UTF-16 code units (case-sensitive)
|
|
10401
|
+
* - Numbers: Numeric comparison (int and float are comparable)
|
|
10402
|
+
* - Booleans: false < true
|
|
10403
|
+
* - Null: Treated as less than all other values
|
|
10404
|
+
* - Mixed types: Comparisons between incompatible types return 0 (not recommended)
|
|
10405
|
+
*
|
|
10406
|
+
* Examples:
|
|
10407
|
+
* - compareValues(String("apple"), String("banana")) \u2192 negative
|
|
10408
|
+
* - compareValues(String("zebra"), String("apple")) \u2192 positive
|
|
10409
|
+
* - compareValues(String("abc"), String("abc")) \u2192 0
|
|
10410
|
+
* - compareValues(Float(3.14), Float(2.71)) \u2192 positive
|
|
10411
|
+
* - compareValues(Bool(false), Bool(true)) \u2192 negative
|
|
10412
|
+
*
|
|
10413
|
+
* @param a - The left operand to compare
|
|
10414
|
+
* @param b - The right operand to compare
|
|
10415
|
+
* @returns Comparison result (-1, 0, or 1)
|
|
10416
|
+
*/
|
|
10417
|
+
export function compareValues(a: Value, b: Value): i32 {
|
|
10418
|
+
// Handle null - null is less than all other values
|
|
10419
|
+
if (a.isNull()) return b.isNull() ? 0 : -1;
|
|
10420
|
+
if (b.isNull()) return 1;
|
|
10421
|
+
|
|
10422
|
+
// Allow int/float comparison for numbers
|
|
10423
|
+
if (a.isNumber() && b.isNumber()) {
|
|
10424
|
+
const aNum = a.asFloat();
|
|
10425
|
+
const bNum = b.asFloat();
|
|
10426
|
+
if (aNum < bNum) return -1;
|
|
10427
|
+
if (aNum > bNum) return 1;
|
|
10428
|
+
return 0;
|
|
10429
|
+
}
|
|
10430
|
+
|
|
10431
|
+
// String comparison - lexicographic, case-sensitive
|
|
10432
|
+
if (a.isString() && b.isString()) {
|
|
10433
|
+
const aStr = a.asString();
|
|
10434
|
+
const bStr = b.asString();
|
|
10435
|
+
if (aStr < bStr) return -1;
|
|
10436
|
+
if (aStr > bStr) return 1;
|
|
10437
|
+
return 0;
|
|
10438
|
+
}
|
|
10439
|
+
|
|
10440
|
+
// Boolean comparison
|
|
10441
|
+
if (a.isBool() && b.isBool()) {
|
|
10442
|
+
const aBool = a.asBool();
|
|
10443
|
+
const bBool = b.asBool();
|
|
10444
|
+
if (!aBool && bBool) return -1;
|
|
10445
|
+
if (aBool && !bBool) return 1;
|
|
10446
|
+
return 0;
|
|
10447
|
+
}
|
|
10448
|
+
|
|
10449
|
+
// Mixed types or unsupported types - treat as equal (but probably shouldn't happen)
|
|
10450
|
+
return 0;
|
|
10451
|
+
}
|
|
10452
|
+
|
|
10453
|
+
// === Arithmetic Operators ===
|
|
10454
|
+
|
|
10455
|
+
/**
|
|
10456
|
+
* Add two values, supporting both numeric addition and string concatenation.
|
|
10457
|
+
*
|
|
10458
|
+
* Type coercion rules:
|
|
10459
|
+
* - If either operand is a string, both are converted to strings and concatenated
|
|
10460
|
+
* - Otherwise, both operands are treated as numbers and added together
|
|
10461
|
+
*
|
|
10462
|
+
* Examples:
|
|
10463
|
+
* - "hello" + " " + "world" \u2192 "hello world"
|
|
10464
|
+
* - "count: " + 5 \u2192 "count: 5"
|
|
10465
|
+
* - 1 + 2 \u2192 3
|
|
10466
|
+
* - true + false \u2192 1 (true=1, false=0, numeric addition)
|
|
10467
|
+
* - null + 5 \u2192 5 (null=0, numeric addition)
|
|
10468
|
+
* - "prefix" + null \u2192 "prefixnull" (string concatenation)
|
|
10469
|
+
*
|
|
10470
|
+
* @param a - The left operand
|
|
10471
|
+
* @param b - The right operand
|
|
10472
|
+
* @returns The sum or concatenation result
|
|
10473
|
+
*/
|
|
10474
|
+
export function addValues(a: Value, b: Value): Value {
|
|
10475
|
+
// String concatenation takes priority over numeric addition
|
|
10476
|
+
// If either operand is a string, concatenate both as strings
|
|
10477
|
+
if (a.isString() || b.isString()) {
|
|
10478
|
+
const leftStr = a.isString() ? a.asString() : valueToString(a);
|
|
10479
|
+
const rightStr = b.isString() ? b.asString() : valueToString(b);
|
|
10480
|
+
return Value.String(leftStr + rightStr);
|
|
10481
|
+
}
|
|
10482
|
+
|
|
10483
|
+
// Numeric addition
|
|
10484
|
+
const aNum = a.isNumber() ? a.asFloat() : a.isBool() ? (a.asBool() ? 1.0 : 0.0) : 0.0;
|
|
10485
|
+
const bNum = b.isNumber() ? b.asFloat() : b.isBool() ? (b.asBool() ? 1.0 : 0.0) : 0.0;
|
|
10486
|
+
return Value.Float(aNum + bNum);
|
|
10487
|
+
}
|
|
10488
|
+
|
|
10489
|
+
export function subtractValues(a: Value, b: Value): Value {
|
|
10490
|
+
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
10491
|
+
const aVal = a.asFloat();
|
|
10492
|
+
const bVal = b.asFloat();
|
|
10493
|
+
return Value.Float(aVal - bVal);
|
|
10494
|
+
}
|
|
10495
|
+
|
|
10496
|
+
export function multiplyValues(a: Value, b: Value): Value {
|
|
10497
|
+
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
10498
|
+
const aVal = a.asFloat();
|
|
10499
|
+
const bVal = b.asFloat();
|
|
10500
|
+
return Value.Float(aVal * bVal);
|
|
10501
|
+
}
|
|
10502
|
+
|
|
10503
|
+
export function divideValues(a: Value, b: Value): Value {
|
|
10504
|
+
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
10505
|
+
const aVal = a.asFloat();
|
|
10506
|
+
const bVal = b.asFloat();
|
|
10507
|
+
if (bVal == 0) return Value.Null(); // Division by zero
|
|
10508
|
+
return Value.Float(aVal / bVal);
|
|
10509
|
+
}
|
|
10510
|
+
|
|
10511
|
+
export function moduloValues(a: Value, b: Value): Value {
|
|
10512
|
+
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
10513
|
+
const aVal = a.asFloat();
|
|
10514
|
+
const bVal = b.asFloat();
|
|
10515
|
+
if (bVal == 0) return Value.Null(); // Division by zero
|
|
10516
|
+
// Use fmod for floating point modulo
|
|
10517
|
+
const result = aVal - Math.trunc(aVal / bVal) * bVal;
|
|
10518
|
+
return Value.Float(result);
|
|
10519
|
+
}
|
|
10520
|
+
|
|
10521
|
+
export function powerValues(a: Value, b: Value): Value {
|
|
10522
|
+
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
10523
|
+
const aVal = a.asFloat();
|
|
10524
|
+
const bVal = b.asFloat();
|
|
10525
|
+
const result = Math.pow(aVal, bVal);
|
|
10526
|
+
return Value.Float(result);
|
|
10527
|
+
}
|
|
10528
|
+
|
|
10529
|
+
export function negateValue(a: Value): Value {
|
|
10530
|
+
if (!a.isNumber()) return Value.Null();
|
|
10531
|
+
const n = a.asFloat();
|
|
10532
|
+
return Value.Float(-n);
|
|
10533
|
+
}
|
|
10534
|
+
|
|
10535
|
+
/**
|
|
10536
|
+
* Get a property from an object Value.
|
|
10537
|
+
*
|
|
10538
|
+
* @param obj - The Value to get the property from (should be an object)
|
|
10539
|
+
* @param property - The property name to get
|
|
10540
|
+
* @returns The Value of the property, or Null if not found or obj is null/not an object
|
|
10541
|
+
*/
|
|
10542
|
+
export function getProperty(obj: Value, property: string): Value {
|
|
10543
|
+
if (obj.isNull()) return Value.Null();
|
|
10544
|
+
if (!obj.isObject()) return Value.Null();
|
|
10545
|
+
|
|
10546
|
+
const map = obj.asObject();
|
|
10547
|
+
if (map.has(property)) {
|
|
10548
|
+
return map.get(property);
|
|
10549
|
+
}
|
|
10550
|
+
return Value.Null();
|
|
10551
|
+
}
|
|
10552
|
+
|
|
10553
|
+
/**
|
|
10554
|
+
* Get an element from an array at the specified index.
|
|
10555
|
+
*
|
|
10556
|
+
* @param arr - The Value to get the element from (should be an array)
|
|
10557
|
+
* @param idx - The index to get (must be a number Value)
|
|
10558
|
+
* @returns The Value at the index, or Null if out of bounds or arr is null/not an array
|
|
10559
|
+
*/
|
|
10560
|
+
export function getIndex(arr: Value, idx: Value): Value {
|
|
10561
|
+
if (arr.isNull()) return Value.Null();
|
|
10562
|
+
if (idx.isNull()) return Value.Null();
|
|
10563
|
+
|
|
10564
|
+
// Handle object access with string keys (e.g., baseCosts["FastTrack"])
|
|
10565
|
+
if (arr.isObject()) {
|
|
10566
|
+
if (idx.isString()) {
|
|
10567
|
+
const obj = arr.asObject();
|
|
10568
|
+
const key = idx.asString();
|
|
10569
|
+
if (obj.has(key)) {
|
|
10570
|
+
return obj.get(key);
|
|
10571
|
+
}
|
|
10572
|
+
return Value.Null();
|
|
10573
|
+
}
|
|
10574
|
+
// If idx is a number, convert to string for object key lookup
|
|
10575
|
+
// This handles cases like obj[0] where obj is {"0": value}
|
|
10576
|
+
const keyStr = idx.asString();
|
|
10577
|
+
const obj = arr.asObject();
|
|
10578
|
+
if (obj.has(keyStr)) {
|
|
10579
|
+
return obj.get(keyStr);
|
|
10580
|
+
}
|
|
10581
|
+
return Value.Null();
|
|
10582
|
+
}
|
|
10583
|
+
|
|
10584
|
+
if (!arr.isArray()) return Value.Null();
|
|
10585
|
+
|
|
10586
|
+
const array = arr.asArray();
|
|
10587
|
+
const index = idx.asFloat();
|
|
10588
|
+
|
|
10589
|
+
// Handle negative indices (from end of array)
|
|
10590
|
+
let actualIndex = <i32>index;
|
|
10591
|
+
if (actualIndex < 0) {
|
|
10592
|
+
actualIndex = <i32>(array.length + actualIndex);
|
|
10593
|
+
}
|
|
10594
|
+
|
|
10595
|
+
// Check bounds
|
|
10596
|
+
if (actualIndex < 0 || actualIndex >= array.length) {
|
|
10597
|
+
return Value.Null();
|
|
10598
|
+
}
|
|
10599
|
+
|
|
10600
|
+
return array[actualIndex];
|
|
10601
|
+
}
|
|
10602
|
+
|
|
10603
|
+
/**
|
|
10604
|
+
* Create an interval object for range checking.
|
|
10605
|
+
*
|
|
10606
|
+
* @param start - Start value of the interval
|
|
10607
|
+
* @param end - End value of the interval
|
|
10608
|
+
* @param startInclusive - Whether the start is inclusive (true) or exclusive (false)
|
|
10609
|
+
* @param endInclusive - Whether the end is inclusive (true) or exclusive (false)
|
|
10610
|
+
* @returns An object representing the interval
|
|
10611
|
+
*/
|
|
10612
|
+
export function createInterval(
|
|
10613
|
+
start: Value,
|
|
10614
|
+
end: Value,
|
|
10615
|
+
startInclusive: bool,
|
|
10616
|
+
endInclusive: bool,
|
|
10617
|
+
): Value {
|
|
10618
|
+
const intervalMap = new Map<string, Value>();
|
|
10619
|
+
intervalMap.set('__isInterval', Value.Bool(true));
|
|
10620
|
+
intervalMap.set('start', start);
|
|
10621
|
+
intervalMap.set('end', end);
|
|
10622
|
+
intervalMap.set('startInclusive', Value.Bool(startInclusive));
|
|
10623
|
+
intervalMap.set('endInclusive', Value.Bool(endInclusive));
|
|
10624
|
+
return Value.Object(intervalMap);
|
|
10625
|
+
}
|
|
10626
|
+
|
|
10627
|
+
/**
|
|
10628
|
+
* Check if a value is within an interval.
|
|
10629
|
+
*
|
|
10630
|
+
* @param needle - The value to check
|
|
10631
|
+
* @param interval - The interval object created by createInterval
|
|
10632
|
+
* @returns true if the value is within the interval, false otherwise
|
|
10633
|
+
*/
|
|
10634
|
+
function valueInInterval(needle: Value, interval: Value): bool {
|
|
10635
|
+
if (!interval.isObject()) return false;
|
|
10636
|
+
|
|
10637
|
+
const intervalObj = interval.asObject();
|
|
10638
|
+
|
|
10639
|
+
// Check if this is actually an interval object
|
|
10640
|
+
if (!intervalObj.has('__isInterval')) return false;
|
|
10641
|
+
|
|
10642
|
+
const start = intervalObj.get('start');
|
|
10643
|
+
const end = intervalObj.get('end');
|
|
10644
|
+
const startInclusive = intervalObj.get('startInclusive').asBool();
|
|
10645
|
+
const endInclusive = intervalObj.get('endInclusive').asBool();
|
|
10646
|
+
|
|
10647
|
+
const cmpStart = compareValues(needle, start);
|
|
10648
|
+
const cmpEnd = compareValues(needle, end);
|
|
10649
|
+
|
|
10650
|
+
// Check start boundary
|
|
10651
|
+
let passesStart: bool;
|
|
10652
|
+
if (startInclusive) {
|
|
10653
|
+
passesStart = cmpStart >= 0;
|
|
10654
|
+
} else {
|
|
10655
|
+
passesStart = cmpStart > 0;
|
|
10656
|
+
}
|
|
10657
|
+
|
|
10658
|
+
// Check end boundary
|
|
10659
|
+
let passesEnd: bool;
|
|
10660
|
+
if (endInclusive) {
|
|
10661
|
+
passesEnd = cmpEnd <= 0;
|
|
10662
|
+
} else {
|
|
10663
|
+
passesEnd = cmpEnd < 0;
|
|
10664
|
+
}
|
|
10665
|
+
|
|
10666
|
+
return passesStart && passesEnd;
|
|
10667
|
+
}
|
|
10668
|
+
|
|
10669
|
+
/**
|
|
10670
|
+
* Check if a value is in an array, a substring is in a string, or a value is in an interval.
|
|
10671
|
+
* For arrays: checks if the exact value exists in the array
|
|
10672
|
+
* For strings: checks if the needle string is a substring of the haystack
|
|
10673
|
+
* For intervals: checks if the value is within the range
|
|
10674
|
+
*
|
|
10675
|
+
* @param needle - The Value or string to search for
|
|
10676
|
+
* @param haystack - The Value to search in (should be an array, string, or interval)
|
|
10677
|
+
* @returns true if found, false otherwise
|
|
10678
|
+
*/
|
|
10679
|
+
export function valueIn(needle: Value, haystack: Value): Value {
|
|
10680
|
+
if (needle.isNull() || haystack.isNull()) return Value.Bool(false);
|
|
10681
|
+
|
|
10682
|
+
// Check if haystack is an interval
|
|
10683
|
+
if (haystack.isObject()) {
|
|
10684
|
+
const obj = haystack.asObject();
|
|
10685
|
+
if (obj.has('__isInterval')) {
|
|
10686
|
+
return Value.Bool(valueInInterval(needle, haystack));
|
|
10687
|
+
}
|
|
10688
|
+
}
|
|
10689
|
+
|
|
10690
|
+
// Check if haystack is an array
|
|
10691
|
+
if (haystack.isArray()) {
|
|
10692
|
+
const array = haystack.asArray();
|
|
10693
|
+
for (let i = 0; i < array.length; i++) {
|
|
10694
|
+
if (valuesEqual(needle, unchecked(array[i]))) {
|
|
10695
|
+
return Value.Bool(true);
|
|
10696
|
+
}
|
|
10697
|
+
}
|
|
10698
|
+
return Value.Bool(false);
|
|
10699
|
+
}
|
|
10700
|
+
|
|
10701
|
+
// Check if haystack is a string
|
|
10702
|
+
if (haystack.isString() && needle.isString()) {
|
|
10703
|
+
const hayStr = haystack.asString();
|
|
10704
|
+
const needStr = needle.asString();
|
|
10705
|
+
if (hayStr.indexOf(needStr) >= 0) {
|
|
10706
|
+
return Value.Bool(true);
|
|
10707
|
+
}
|
|
10708
|
+
return Value.Bool(false);
|
|
10709
|
+
}
|
|
10710
|
+
|
|
10711
|
+
return Value.Bool(false);
|
|
10712
|
+
}
|
|
10713
|
+
`;
|
|
10714
|
+
var RUNTIME_TABLES = "// assembly/runtime/tables.ts (AssemblyScript)\nimport { Value } from './values';\nimport { Context } from './context';\nimport { valuesEqual } from './expressions';\n\n/**\n * Condition for decision table row evaluation.\n * Uses Value tagged union consistently.\n */\nexport class Condition {\n field: string;\n op: string;\n value: Value;\n\n constructor(field: string, op: string, value: Value) {\n this.field = field;\n this.op = op;\n this.value = value;\n }\n\n evaluate(ctx: Context): bool {\n const actual = ctx.get(this.field);\n const expected = this.value;\n\n // Handle null cases\n if (actual.isNull()) {\n if (this.op == '==' && expected.isNull()) return true;\n if (this.op == '!=' && !expected.isNull()) return true;\n return false;\n }\n\n // Numeric comparisons\n if (actual.isNumber() && expected.isNumber()) {\n const a = actual.asFloat();\n const e = expected.asFloat();\n\n if (this.op == '==') return a == e;\n if (this.op == '!=') return a != e;\n if (this.op == '<') return a < e;\n if (this.op == '<=') return a <= e;\n if (this.op == '>') return a > e;\n if (this.op == '>=') return a >= e;\n }\n\n // String comparisons\n if (actual.isString() && expected.isString()) {\n const a = actual.asString();\n const e = expected.asString();\n\n if (this.op == '==') return a == e;\n if (this.op == '!=') return a != e;\n // Lexicographic comparison for strings\n if (this.op == '<') return a < e;\n if (this.op == '<=') return a <= e;\n if (this.op == '>') return a > e;\n if (this.op == '>=') return a >= e;\n }\n\n // Boolean equality\n if (actual.isBool() && expected.isBool()) {\n if (this.op == '==') return actual.boolVal == expected.boolVal;\n if (this.op == '!=') return actual.boolVal != expected.boolVal;\n }\n\n // Generic equality via valuesEqual\n if (this.op == '==') return valuesEqual(actual, expected);\n if (this.op == '!=') return !valuesEqual(actual, expected);\n\n return false;\n }\n}\n\n/**\n * Check if a value is within an interval (range).\n * Supports open/closed boundaries: [a..b], (a..b), [a..b), (a..b]\n */\nexport function inInterval(\n val: Value,\n start: Value,\n end: Value,\n startInclusive: bool,\n endInclusive: bool,\n): bool {\n if (!val.isNumber() || !start.isNumber() || !end.isNumber()) {\n return false;\n }\n\n const v = val.asFloat();\n const s = start.asFloat();\n const e = end.asFloat();\n\n const aboveStart = startInclusive ? v >= s : v > s;\n const belowEnd = endInclusive ? v <= e : v < e;\n\n return aboveStart && belowEnd;\n}\n";
|
|
10715
|
+
var RUNTIME_MEMORY = "// assembly/runtime/memory.ts (AssemblyScript)\n// Memory layout and management utilities for WASM linear memory\n\nimport { writeString } from './strings';\n\n// ============================================================================\n// Memory Layout Constants\n// ============================================================================\n\n// Reserved space for null pointer trap (first 4 bytes)\n// Accessing address 0 should trap, catching null/nullish pointer bugs\nexport const NULL_POINTER_TRAP_SIZE: usize = 4;\n\n// Fixed memory region offsets\nexport const INPUT_BUFFER_START: usize = NULL_POINTER_TRAP_SIZE; // 0x0004\n\n// Schema-specific field layouts require different buffer sizes. Computing sizes at\n// compile time based on actual schema ensures optimal memory usage per decision.\nexport let INPUT_BUFFER_SIZE: usize = 0;\n\n// Variable-length input data starts after the fixed input buffer\nexport let INPUT_VAR_START: usize = 0;\n\n// Output buffer follows input variable data\nexport let OUTPUT_START: usize = 0;\n\n// Computed at compilation time based on input/output schema field sizes\nexport let OUTPUT_BUFFER_SIZE: usize = 0;\n\n// Variable-length output data starts after the fixed output buffer\nexport let OUTPUT_VAR_START: usize = 0;\n\n// Runtime heap for intermediate values (temporary arrays, string concat buffers, etc.)\nexport let HEAP_START: usize = 0;\n\n// ============================================================================\n// Header Constants\n// ============================================================================\n\n// Schema hash size in bytes\nexport const SCHEMA_HASH_SIZE: usize = 8;\n\n// String descriptor format: [offset (4 bytes)] [length (4 bytes)]\nexport const STRING_DESC_SIZE: usize = 8;\n\n// Array descriptor format: [offset (4 bytes)] [length (4 bytes)]\nexport const ARRAY_DESC_SIZE: usize = 8;\n\n// ============================================================================\n// Memory Allocation\n// ============================================================================\n\n// Decision evaluation is single-pass (no GC needed), and most allocations are temporary\n// (freed after result is marshaled). Linear bump allocator is fast and simple.\nlet heapPointer: usize = 0;\n\n/**\n * Initialize memory layout.\n *\n * This function must be called once at startup with the computed sizes\n * for the input and output buffers based on the schema.\n *\n * @param inputFixedSize - Size of fixed input fields in bytes\n * @param outputFixedSize - Size of fixed output fields in bytes\n */\nexport function initializeMemoryLayout(inputFixedSize: usize, outputFixedSize: usize): void {\n INPUT_BUFFER_SIZE = inputFixedSize;\n INPUT_VAR_START = INPUT_BUFFER_START + inputFixedSize;\n\n OUTPUT_BUFFER_SIZE = outputFixedSize;\n OUTPUT_START = INPUT_VAR_START; // Output follows input variable data\n OUTPUT_VAR_START = OUTPUT_START + outputFixedSize;\n\n HEAP_START = OUTPUT_VAR_START; // Heap follows output variable data\n heapPointer = HEAP_START; // Reset heap pointer\n}\n\n/**\n * Get the current memory layout info.\n *\n * @returns Object with all memory region offsets and sizes\n */\nexport function getMemoryLayout(): MemoryLayout {\n return new MemoryLayout(\n NULL_POINTER_TRAP_SIZE,\n INPUT_BUFFER_START,\n INPUT_BUFFER_SIZE,\n INPUT_VAR_START,\n OUTPUT_START,\n OUTPUT_BUFFER_SIZE,\n OUTPUT_VAR_START,\n HEAP_START,\n );\n}\n\n/**\n * Memory layout information structure.\n */\nexport class MemoryLayout {\n nullPointerTrapSize: usize;\n inputBufferStart: usize;\n inputBufferSize: usize;\n inputVarStart: usize;\n outputStart: usize;\n outputBufferSize: usize;\n outputVarStart: usize;\n heapStart: usize;\n\n constructor(\n nullPointerTrapSize: usize,\n inputBufferStart: usize,\n inputBufferSize: usize,\n inputVarStart: usize,\n outputStart: usize,\n outputBufferSize: usize,\n outputVarStart: usize,\n heapStart: usize,\n ) {\n this.nullPointerTrapSize = nullPointerTrapSize;\n this.inputBufferStart = inputBufferStart;\n this.inputBufferSize = inputBufferSize;\n this.inputVarStart = inputVarStart;\n this.outputStart = outputStart;\n this.outputBufferSize = outputBufferSize;\n this.outputVarStart = outputVarStart;\n this.heapStart = heapStart;\n }\n}\n\n// ============================================================================\n// Simple Linear Heap Allocator\n// ============================================================================\n\n/**\n * Allocate memory from the runtime heap.\n *\n * A simple linear allocator - memory is never freed during a single evaluation.\n * Suitable for short-lived operations like decision table evaluation.\n *\n * Memory is never freed during a single evaluation because:\n * (1) evaluations are short-lived (milliseconds),\n * (2) heap is reset between evaluations, and\n * (3) avoiding free list management simplifies allocator and improves performance.\n *\n * @param size - Number of bytes to allocate (must be > 0)\n * @returns Pointer to allocated memory\n * @throws Calls abort() on OUT_OF_MEMORY - this is a critical error that halts execution\n */\nexport function heapAlloc(size: usize): usize {\n if (size == 0) return 0;\n\n // Get current memory size in bytes (1 page = 64KB = 65536 bytes)\n const memorySize: usize = (<usize>memory.size()) << 16;\n\n // Align the heap pointer to 8-byte boundary\n const alignedPtr = alignPtr(heapPointer, 8);\n\n // Check for overflow when computing aligned pointer\n // If alignedPtr wrapped around (became smaller than heapPointer), we've overflowed\n if (alignedPtr < heapPointer) {\n SetLastError(RuntimeErrorCode.OUT_OF_MEMORY);\n return 0;\n }\n\n // Check for overflow when computing new heap pointer\n // If newHeapPointer would wrap around, we've overflowed\n const maxAllowedSize = memorySize - alignedPtr;\n if (size > maxAllowedSize) {\n SetLastError(RuntimeErrorCode.OUT_OF_MEMORY);\n return 0;\n }\n\n const newHeapPointer = alignedPtr + size;\n\n // Bounds check: ensure new heap pointer doesn't exceed memory\n if (newHeapPointer > memorySize) {\n SetLastError(RuntimeErrorCode.OUT_OF_MEMORY);\n return 0;\n }\n\n // All checks passed, update heap pointer and return allocation\n heapPointer = newHeapPointer;\n return alignedPtr;\n}\n\n/**\n * Get the current heap pointer (next allocation will start here).\n *\n * @returns Current heap pointer address\n */\nexport function getHeapPointer(): usize {\n return heapPointer;\n}\n\n/**\n * Reset the heap pointer to the start of the heap region.\n *\n * This allows reusing heap memory for multiple evaluations.\n */\nexport function resetHeap(): void {\n heapPointer = HEAP_START;\n}\n\n/**\n * Save the current heap pointer state.\n *\n * @returns Current heap pointer address\n */\nexport function saveHeapPointer(): usize {\n return heapPointer;\n}\n\n/**\n * Restore the heap pointer to a previously saved state.\n *\n * @param saved - Saved heap pointer address from saveHeapPointer()\n */\nexport function restoreHeapPointer(saved: usize): usize {\n const old = heapPointer;\n heapPointer = saved;\n return old;\n}\n\n// ============================================================================\n// Pointer Utilities\n// ============================================================================\n\n/**\n * Align a pointer to the specified alignment boundary.\n *\n * @param ptr - Pointer to align\n * @param alignment - Alignment boundary (must be a power of 2)\n * @returns Aligned pointer\n */\nexport function alignPtr(ptr: usize, alignment: usize): usize {\n return (ptr + alignment - 1) & ~(alignment - 1);\n}\n\n/**\n * Check if a pointer is null (0).\n *\n * @param ptr - Pointer to check\n * @returns true if pointer is 0, false otherwise\n */\nexport function isNullPtr(ptr: usize): bool {\n return ptr == 0;\n}\n\n/**\n * Check if a pointer is valid (non-null and aligned).\n *\n * @param ptr - Pointer to check\n * @param alignment - Required alignment (default: 4 bytes)\n * @returns true if pointer is valid, false otherwise\n */\nexport function isValidPtr(ptr: usize, alignment: usize = 4): bool {\n if (ptr == 0) return false;\n return (ptr & (alignment - 1)) == 0;\n}\n\n// ============================================================================\n// Buffer Access Helpers\n// ============================================================================\n\n/**\n * Get the pointer to a schema hash field.\n *\n * @param basePtr - Base pointer of the buffer\n * @returns Pointer to the schema hash field\n */\nexport function getSchemaHashPtr(basePtr: usize): usize {\n return basePtr;\n}\n\n/**\n * Read a schema hash from a buffer.\n *\n * @param basePtr - Base pointer of the buffer\n * @returns Schema hash as u64\n */\nexport function readSchemaHash(basePtr: usize): u64 {\n return load<u64>(basePtr);\n}\n\n/**\n * Write a schema hash to a buffer.\n *\n * @param basePtr - Base pointer of the buffer\n * @param hash - Schema hash to write\n */\nexport function writeSchemaHash(basePtr: usize, hash: u64): void {\n store<u64>(basePtr, hash);\n}\n\n/**\n * Get the pointer to a field in a fixed-size buffer.\n *\n * @param bufferPtr - Base pointer of the buffer\n * @param fieldOffset - Offset of the field from the buffer start\n * @returns Pointer to the field\n */\nexport function getFieldPtr(bufferPtr: usize, fieldOffset: usize): usize {\n return bufferPtr + fieldOffset;\n}\n\n// ============================================================================\n// String Descriptor Helpers\n// ============================================================================\n\n/**\n * Get the offset and length components of a string descriptor.\n *\n * String descriptor format: [offset (4 bytes)] [length (4 bytes)]\n *\n * @param descPtr - Pointer to the string descriptor\n * @returns Object with offset and length\n */\nexport function readStringDesc(descPtr: usize): StringDesc {\n return new StringDesc(load<u32>(descPtr), load<u32>(descPtr + 4));\n}\n\n/**\n * Write a string descriptor.\n *\n * @param descPtr - Pointer to the string descriptor\n * @param offset - Offset to the string data\n * @param length - Length of the string data\n */\nexport function writeStringDesc(descPtr: usize, offset: u32, length: u32): void {\n store<u32>(descPtr, offset);\n store<u32>(descPtr + 4, length);\n}\n\n/**\n * String descriptor structure.\n */\nexport class StringDesc {\n offset: u32;\n length: u32;\n\n constructor(offset: u32, length: u32) {\n this.offset = offset;\n this.length = length;\n }\n\n isEmpty(): bool {\n return this.length == 0;\n }\n}\n\n// ============================================================================\n// Array Descriptor Helpers\n// ============================================================================\n\n/**\n * Get the offset and length components of an array descriptor.\n *\n * Array descriptor format: [offset (4 bytes)] [length (4 bytes)]\n *\n * @param descPtr - Pointer to the array descriptor\n * @returns Object with offset and length\n */\nexport function readArrayDesc(descPtr: usize): ArrayDesc {\n return new ArrayDesc(load<u32>(descPtr), load<u32>(descPtr + 4));\n}\n\n/**\n * Write an array descriptor.\n *\n * @param descPtr - Pointer to the array descriptor\n * @param offset - Offset to the array data\n * @param length - Number of elements in the array\n */\nexport function writeArrayDesc(descPtr: usize, offset: u32, length: u32): void {\n store<u32>(descPtr, offset);\n store<u32>(descPtr + 4, length);\n}\n\n/**\n * Array descriptor structure.\n */\nexport class ArrayDesc {\n offset: u32;\n length: u32;\n\n constructor(offset: u32, length: u32) {\n this.offset = offset;\n this.length = length;\n }\n\n isEmpty(): bool {\n return this.length == 0;\n }\n}\n\n// ============================================================================\n// Runtime Error Handling\n// ============================================================================\n\n// Error codes for runtime failures\nexport enum RuntimeErrorCode {\n NONE = 0,\n SCHEMA_MISMATCH = 1,\n NULL_POINTER = 2,\n INVALID_ACCESS = 3,\n EVALUATION_ERROR = 4,\n OUT_OF_MEMORY = 5,\n}\n\n// Last error that occurred during evaluation\nlet lastErrorCode: RuntimeErrorCode = RuntimeErrorCode.NONE;\n\n// Optional error message (allocated on heap for variable-length messages)\nlet errorMessagePtr: usize = 0;\n\n/**\n * Check if an error code represents a critical error that should abort execution.\n *\n * Critical errors are unrecoverable conditions where continuing execution would\n * likely lead to memory corruption, crashes, or incorrect results. These errors\n * call abort() to immediately halt the WASM module.\n *\n * Critical errors:\n * - OUT_OF_MEMORY: Memory allocation failed; continuing could corrupt state\n * - NULL_POINTER: Null pointer dereference; memory safety violation\n * - INVALID_ACCESS: Invalid memory access; memory safety violation\n *\n * Non-critical errors (set flag, caller must check hasError()):\n * - SCHEMA_MISMATCH: Input doesn't match schema; recoverable\n * - EVALUATION_ERROR: Logic error in decision evaluation; recoverable\n *\n * @param code - The error code to check\n * @returns true if the error is critical and should abort\n */\nexport function isCriticalError(code: RuntimeErrorCode): bool {\n return (\n code === RuntimeErrorCode.OUT_OF_MEMORY ||\n code === RuntimeErrorCode.NULL_POINTER ||\n code === RuntimeErrorCode.INVALID_ACCESS\n );\n}\n\n/**\n * Get the last error code that occurred during evaluation.\n *\n * @returns The last error code\n */\nexport function getLastError(): RuntimeErrorCode {\n return lastErrorCode;\n}\n\n/**\n * Set the last error code and optional message.\n *\n * For critical errors (OUT_OF_MEMORY, NULL_POINTER, INVALID_ACCESS), this function\n * calls abort() to immediately halt execution. Critical errors indicate unrecoverable\n * conditions where continuing would lead to memory corruption or undefined behavior.\n *\n * For non-critical errors (SCHEMA_MISMATCH, EVALUATION_ERROR), callers MUST check\n * hasError() after operations that can fail and handle the error appropriately.\n * Failure to check error state may result in incorrect evaluation results.\n *\n * @param code - The error code\n * @param message - Optional error message (allocated on heap)\n */\nexport function SetLastError(code: RuntimeErrorCode, message: usize = 0): void {\n lastErrorCode = code;\n errorMessagePtr = message;\n\n // Critical errors abort immediately to prevent corrupted state propagation\n if (isCriticalError(code)) {\n abort();\n }\n}\n\n/**\n * Clear the last error (reset to NONE).\n */\nexport function clearLastError(): void {\n lastErrorCode = RuntimeErrorCode.NONE;\n errorMessagePtr = 0;\n}\n\n/**\n * Set an error with a string message.\n *\n * This is a convenience function that combines SetLastError() with writeString().\n * The error message is allocated on the heap and will be freed by the next error.\n *\n * For critical errors (OUT_OF_MEMORY, NULL_POINTER, INVALID_ACCESS), this function\n * will call abort() after setting the error, immediately halting execution.\n *\n * For non-critical errors (SCHEMA_MISMATCH, EVALUATION_ERROR), callers MUST check\n * hasError() after operations that can fail.\n *\n * @param code - The error code\n * @param message - The error message string\n */\nexport function SetLastErrorWithMessage(code: RuntimeErrorCode, message: string): void {\n const messagePtr = writeString(message);\n SetLastError(code, messagePtr);\n}\n\n/**\n * Get the pointer to the error message.\n *\n * @returns Pointer to error message (0 if no message)\n */\nexport function getErrorMessagePtr(): usize {\n return errorMessagePtr;\n}\n\n/**\n * Check if an error occurred.\n *\n * Callers MUST check this function after operations that can set non-critical errors\n * (SCHEMA_MISMATCH, EVALUATION_ERROR). Critical errors (OUT_OF_MEMORY, NULL_POINTER,\n * INVALID_ACCESS) abort immediately and never reach this check.\n *\n * Example usage:\n * ```assemblyscript\n * let result = someOperationThatCanFail();\n * if (hasError()) {\n * // Handle error - return early, propagate error, etc.\n * return Value.Null();\n * }\n * // Continue with valid result\n * ```\n *\n * @returns true if a non-critical error occurred, false otherwise\n */\nexport function hasError(): bool {\n return lastErrorCode !== RuntimeErrorCode.NONE;\n}\n\n// ============================================================================\n// Memory Validation Helpers\n// ============================================================================\n\n/**\n * Check if a pointer is within the valid memory range.\n *\n * This is a basic check that can help catch out-of-bounds accesses.\n * Note: In production, you may want to disable these checks for performance.\n *\n * @param ptr - Pointer to check\n * @param size - Size of the region to check\n * @return true if pointer is within valid range, false otherwise\n */\nexport function isValidMemoryRange(ptr: usize, size: usize): bool {\n if (ptr == 0) return false;\n\n // Get current memory size (AssemblyScript runtime provides this)\n const memorySize: usize = memory.size() << 16; // 1 page = 64KB\n\n return ptr + size <= memorySize;\n}\n\n/**\n * Check if a pointer is within the input buffer region.\n *\n * @param ptr - Pointer to check\n * @return true if pointer is within input buffer, false otherwise\n */\nexport function isWithinInputBuffer(ptr: usize): bool {\n return ptr >= INPUT_BUFFER_START && ptr < INPUT_VAR_START;\n}\n\n/**\n * Check if a pointer is within the input variable data region.\n *\n * @param ptr - Pointer to check\n * @return true if pointer is within input variable data, false otherwise\n */\nexport function isWithinInputVar(ptr: usize): bool {\n return ptr >= INPUT_VAR_START && ptr < OUTPUT_START;\n}\n\n/**\n * Check if a pointer is within the output buffer region.\n *\n * @param ptr - Pointer to check\n * @return true if pointer is within output buffer, false otherwise\n */\nexport function isWithinOutputBuffer(ptr: usize): bool {\n return ptr >= OUTPUT_START && ptr < OUTPUT_VAR_START;\n}\n\n/**\n * Check if a pointer is within the output variable data region.\n *\n * @param ptr - Pointer to check\n * @return true if pointer is within output variable data, false otherwise\n */\nexport function isWithinOutputVar(ptr: usize): bool {\n return ptr >= OUTPUT_VAR_START && ptr < HEAP_START;\n}\n\n/**\n * Check if a pointer is within the heap region.\n *\n * @param ptr - Pointer to check\n * @return true if pointer is within heap, false otherwise\n */\nexport function isWithinHeap(ptr: usize): bool {\n return ptr >= HEAP_START;\n}\n";
|
|
10716
|
+
var RUNTIME_STRINGS = "// assembly/runtime/strings.ts (AssemblyScript)\n// UTF-16 string handling for WASM linear memory\n\nimport { heapAlloc } from './memory';\n\n// ============================================================================\n// String Memory Format\n// ============================================================================\n\n/**\n * String memory layout: [length: u32][utf16 chars...]\n *\n * We use a length-prefixed format; this allows O(1) length checks and efficient substring\n * operations without scanning for null terminators. Also matches AssemblyScript's native\n * string representation. The first 4 bytes contain the length (number of UTF-16 code units).\n * Following that are the UTF-16 code units, each 2 bytes.\n */\n\n// ============================================================================\n// String Constants\n// ============================================================================\n\n/**\n * Size of the length field header in bytes.\n */\nexport const STRING_LENGTH_SIZE: usize = 4;\n\n/**\n * Size of each UTF-16 code unit in bytes.\n */\nexport const UTF16_CODE_UNIT_SIZE: usize = 2;\n\n/**\n * Minimum string length to use bulk memory operations.\n * For strings shorter than this, character-by-character writes are used\n * as the setup overhead dominates. For longer strings, bulk operations\n * provide significant performance benefits.\n */\nconst MIN_BULK_COPY_STRING_LENGTH: usize = 32;\n\n// ============================================================================\n// String Writing\n// ============================================================================\n\n/**\n * Write a string to memory and return its pointer.\n *\n * Memory is allocated from the runtime heap using heapAlloc().\n * The memory is aligned to 4 bytes for efficient access.\n *\n * @param s - The string to write to memory\n * @returns Pointer to the allocated string data, or 0 if allocation failed\n */\nexport function writeString(s: string): usize {\n const len: usize = <usize>s.length;\n\n // Calculate total size: length field (4 bytes) + UTF-16 characters (2 bytes each)\n const totalSize = STRING_LENGTH_SIZE + len * UTF16_CODE_UNIT_SIZE;\n\n // Four-byte alignment ensures proper alignment for u32 loads/stores, avoiding unaligned access\n // penalties on some architectures. WASM requires aligned memory access for multi-byte types.\n // Allocate memory from heap\n const ptr = heapAlloc(totalSize);\n\n // Return 0 if allocation failed\n if (ptr == 0) {\n return 0;\n }\n\n // Write length (number of UTF-16 code units)\n store<u32>(ptr, len);\n\n // Handle empty string case (len == 0)\n if (len == 0) {\n return ptr;\n }\n\n // For long strings, use bulk memory copy for better performance.\n // AssemblyScript strings are internally UTF-16, matching our memory format.\n if (len >= MIN_BULK_COPY_STRING_LENGTH) {\n // Get pointer to the string's internal UTF-16 data\n const srcPtr = changetype<usize>(s);\n // Copy the UTF-16 data directly\n memory.copy(ptr + STRING_LENGTH_SIZE, srcPtr, len * UTF16_CODE_UNIT_SIZE);\n } else {\n // For short strings, character-by-character writes avoid bulk operation overhead\n for (let i: usize = 0; i < len; i++) {\n const codeUnit: u16 = unchecked(<u16>s.charCodeAt(<i32>i));\n store<u16>(ptr + STRING_LENGTH_SIZE + i * UTF16_CODE_UNIT_SIZE, codeUnit);\n }\n }\n\n return ptr;\n}\n\n// ============================================================================\n// String Reading\n// ============================================================================\n\n/**\n * Read a string from memory.\n *\n * @param ptr - Pointer to the string data in memory\n * @returns The read string, or empty string if pointer is null\n */\nexport function readString(ptr: usize): string {\n // Return empty string for null pointer\n if (ptr == 0) {\n return '';\n }\n\n // Read length (number of UTF-16 code units)\n const len: usize = <usize>load<u32>(ptr);\n\n // Handle empty string case\n if (len == 0) {\n return '';\n }\n\n // Use bulk memory read for O(n) performance instead of O(n\xB2) string concatenation.\n // String.UTF16.decodeUnsafe reads UTF-16 data directly from memory in a single allocation,\n // avoiding the repeated string allocations that occur with character-by-character concatenation.\n return String.UTF16.decodeUnsafe(ptr + STRING_LENGTH_SIZE, len * UTF16_CODE_UNIT_SIZE);\n}\n\n// ============================================================================\n// String Utility Functions\n// ============================================================================\n\n/**\n * Calculate the size in bytes needed to store a string in memory.\n *\n * @param len - The length of the string (number of UTF-16 code units)\n * @returns The total size in bytes including the length header\n */\nexport function getStringSize(len: usize): usize {\n return STRING_LENGTH_SIZE + len * UTF16_CODE_UNIT_SIZE;\n}\n\n/**\n * Calculate the length of a string from its pointer in memory.\n *\n * @param ptr - Pointer to the string data in memory\n * @returns The length of the string (number of UTF-16 code units), or 0 if null\n */\nexport function getStringLength(ptr: usize): usize {\n if (ptr == 0) {\n return 0;\n }\n return load<u32>(ptr);\n}\n\n/**\n * Check if a string pointer points to an empty string.\n *\n * @param ptr - Pointer to the string data in memory\n * @returns true if the string is empty or pointer is null, false otherwise\n */\nexport function isStringEmpty(ptr: usize): bool {\n if (ptr == 0) {\n return true;\n }\n return load<u32>(ptr) == 0;\n}\n\n/**\n * Compare two strings in memory.\n *\n * @param ptr1 - Pointer to the first string\n * @param ptr2 - Pointer to the second string\n * @returns true if the strings are equal, false otherwise\n */\nexport function stringsEqual(ptr1: usize, ptr2: usize): bool {\n // If both are null, they're equal\n if (ptr1 == 0 && ptr2 == 0) {\n return true;\n }\n\n // If only one is null, they're not equal\n if (ptr1 == 0 || ptr2 == 0) {\n return false;\n }\n\n // Compare lengths\n const len1 = load<u32>(ptr1);\n const len2 = load<u32>(ptr2);\n\n if (len1 != len2) {\n return false;\n }\n\n // Compare each UTF-16 code unit\n for (let i: usize = 0; i < <usize>len1; i++) {\n const offset = STRING_LENGTH_SIZE + i * UTF16_CODE_UNIT_SIZE;\n const char1 = load<u16>(ptr1 + offset);\n const char2 = load<u16>(ptr2 + offset);\n if (char1 != char2) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Calculate a simple hash of a string in memory.\n *\n * This can be used for quick comparisons or deduplication.\n * Uses a simple polynomial rolling hash algorithm.\n *\n * @param ptr - Pointer to the string data in memory\n * @returns A hash value for the string (0 if null)\n */\nexport function hashString(ptr: usize): u32 {\n if (ptr == 0) {\n return 0;\n }\n\n const len: usize = <usize>load<u32>(ptr);\n let hash: u32 = 5381;\n\n for (let i: usize = 0; i < len; i++) {\n const charCode = load<u16>(ptr + STRING_LENGTH_SIZE + i * UTF16_CODE_UNIT_SIZE);\n hash = unchecked((hash << 5) + hash + unchecked(charCode)); // hash * 33 + charCode\n }\n\n return hash;\n}\n\n/**\n * Copy a string within memory.\n *\n * Creates a new copy of the string at a new location in the heap.\n *\n * @param srcPtr - Pointer to the source string\n * @returns Pointer to the newly allocated copy, or 0 if allocation failed\n */\nexport function copyString(srcPtr: usize): usize {\n if (srcPtr == 0) {\n return 0;\n }\n\n // Get the length of the source string\n const len: usize = <usize>load<u32>(srcPtr);\n\n // Calculate total size\n const totalSize = STRING_LENGTH_SIZE + len * UTF16_CODE_UNIT_SIZE;\n\n // Allocate new memory\n const dstPtr = heapAlloc(totalSize);\n\n if (dstPtr == 0) {\n return 0;\n }\n\n // Copy the entire string data (length header + UTF-16 code units)\n memory.copy(dstPtr, srcPtr, totalSize);\n\n return dstPtr;\n}\n";
|
|
10717
|
+
var RUNTIME_ARRAYS = "// assembly/runtime/arrays.ts (AssemblyScript)\n// Array handling for WASM linear memory\n\nimport { heapAlloc } from './memory';\nimport { writeString, readString } from './strings';\nimport {\n Value,\n TYPE_NULL,\n TYPE_BOOL,\n TYPE_INT,\n TYPE_FLOAT,\n TYPE_STRING,\n TYPE_ARRAY,\n TYPE_OBJECT,\n} from './values';\n\n// ============================================================================\n// Array Constants\n// ============================================================================\n\n/**\n * Size of the length field header in bytes for arrays.\n */\nexport const ARRAY_LENGTH_SIZE: usize = 4;\n\n/**\n * Size of each F64 element in bytes.\n */\nexport const F64_ELEMENT_SIZE: usize = 8;\n\n/**\n * Size of each element pointer (usize) in bytes for generic arrays.\n */\nexport const ELEMENT_PTR_SIZE: usize = 4;\n\n/**\n * Size of the type tag field in bytes for serialized Values.\n */\nexport const VALUE_TYPE_SIZE: usize = 4;\n\n/**\n * Size of the bool field in bytes for serialized Values.\n */\nexport const VALUE_BOOL_SIZE: usize = 1;\n\n/**\n * Size of the int field in bytes for serialized Values.\n */\nexport const VALUE_INT_SIZE: usize = 8;\n\n/**\n * Size of the float field in bytes for serialized Values.\n */\nexport const VALUE_FLOAT_SIZE: usize = 8;\n\n/**\n * Size of the pointer field in bytes for serialized Values (string, array, object).\n */\nexport const VALUE_PTR_SIZE: usize = 4;\n\n// ============================================================================\n// F64 Array Handling\n// ============================================================================\n\n/**\n * Write a numeric array (Array<f64>) to memory.\n *\n * The array is stored in the format: [length: u32][f64 elements...]\n *\n * Memory is allocated from the runtime heap using heapAlloc().\n * The memory is aligned to 8 bytes for efficient access.\n *\n * @param arr - The array to write to memory\n * @returns Pointer to the allocated array data, or 0 if allocation failed\n */\nexport function writeF64Array(arr: Array<f64>): usize {\n const len = <u32>arr.length;\n\n // Calculate total size: length field (4 bytes) + f64 elements (8 bytes each)\n const totalSize = ARRAY_LENGTH_SIZE + <usize>len * F64_ELEMENT_SIZE;\n\n // Allocate memory from heap\n const ptr = heapAlloc(totalSize);\n\n // Return 0 if allocation failed\n if (ptr == 0) {\n return 0;\n }\n\n // Write length\n store<u32>(ptr, len);\n\n // Write each f64 element\n for (let i: u32 = 0; i < len; i++) {\n store<f64>(ptr + ARRAY_LENGTH_SIZE + <usize>i * F64_ELEMENT_SIZE, arr[i]);\n }\n\n return ptr;\n}\n\n/**\n * Read a numeric array (Array<f64>) from memory.\n *\n * The array must be stored in the format: [length: u32][f64 elements...]\n *\n * @param ptr - Pointer to the array data in memory\n * @returns The read array, or empty array if pointer is null\n */\nexport function readF64Array(ptr: usize): Array<f64> {\n // Return empty array for null pointer\n if (ptr == 0) {\n return new Array<f64>();\n }\n\n // Read length\n const len = load<u32>(ptr);\n\n // Create array and populate it\n const result = new Array<f64>(len);\n for (let i: u32 = 0; i < len; i++) {\n result[i] = load<f64>(ptr + ARRAY_LENGTH_SIZE + <usize>i * F64_ELEMENT_SIZE);\n }\n\n return result;\n}\n\n/**\n * Calculate the size in bytes needed to store an F64 array in memory.\n *\n * @param len - The length of the array\n * @returns The total size in bytes including the length header\n */\nexport function getF64ArraySize(len: usize): usize {\n return ARRAY_LENGTH_SIZE + len * F64_ELEMENT_SIZE;\n}\n\n// ============================================================================\n// Value Serialization\n// ============================================================================\n\n/**\n * Write a Value to memory and return its pointer.\n *\n * For complex types (string/array/object), storing the actual data inline would make\n * Value structs variable-sized and unpredictable. Storing pointers provides fixed-size\n * Values (predictable offsets) while allowing arbitrary complex data on the heap.\n *\n * The Value is stored in the format: [type: u32][value_data...]\n *\n * For primitive types (bool, int, float), value_data is the actual value.\n * For complex types (string, array, object), value_data is a pointer to the data.\n *\n * Memory is allocated from the runtime heap using heapAlloc().\n *\n * @param v - The Value to write to memory\n * @returns Pointer to the allocated Value data, or 0 if allocation failed\n */\nexport function writeValue(v: Value): usize {\n const type = v.type;\n\n if (type == TYPE_NULL) {\n // For null, just write the type tag\n const ptr = heapAlloc(VALUE_TYPE_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n return ptr;\n } else if (type == TYPE_BOOL) {\n // Format: [type: u32][bool: u8]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_BOOL_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n store<u8>(ptr + VALUE_TYPE_SIZE, v.boolVal ? 1 : 0);\n return ptr;\n } else if (type == TYPE_INT) {\n // Format: [type: u32][int: i64]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_INT_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n store<i64>(ptr + VALUE_TYPE_SIZE, v.intVal);\n return ptr;\n } else if (type == TYPE_FLOAT) {\n // Format: [type: u32][float: f64]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_FLOAT_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n store<f64>(ptr + VALUE_TYPE_SIZE, v.floatVal);\n return ptr;\n } else if (type == TYPE_STRING) {\n // Format: [type: u32][stringPtr: u32]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_PTR_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n const stringVal = v.stringVal != null ? v.stringVal! : '';\n const stringPtr = writeString(stringVal);\n store<u32>(ptr + VALUE_TYPE_SIZE, stringPtr);\n return ptr;\n } else if (type == TYPE_ARRAY) {\n // Format: [type: u32][arrayPtr: u32]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_PTR_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n const arrayVal = v.arrayVal != null ? v.arrayVal! : new Array<Value>();\n const arrayPtr = writeValueArray(arrayVal);\n store<u32>(ptr + VALUE_TYPE_SIZE, arrayPtr);\n return ptr;\n } else if (type == TYPE_OBJECT) {\n // Format: [type: u32][objectPtr: u32]\n const ptr = heapAlloc(VALUE_TYPE_SIZE + VALUE_PTR_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, type);\n const objectVal = v.objectVal != null ? v.objectVal! : new Map<string, Value>();\n const objectPtr = writeValueMap(objectVal);\n store<u32>(ptr + VALUE_TYPE_SIZE, objectPtr);\n return ptr;\n }\n\n return 0; // Unknown type\n}\n\n/**\n * Read a Value from memory.\n *\n * The Value must be stored in the format: [type: u32][value_data...]\n *\n * @param ptr - Pointer to the Value data in memory\n * @returns The read Value, or null if pointer is invalid\n */\nexport function readValue(ptr: usize): Value {\n // Return null for null pointer\n if (ptr == 0) {\n return Value.Null();\n }\n\n // Read type\n const type = load<u32>(ptr);\n\n if (type == TYPE_NULL) {\n return Value.Null();\n } else if (type == TYPE_BOOL) {\n const boolVal = load<u8>(ptr + VALUE_TYPE_SIZE) != 0;\n return Value.Bool(boolVal);\n } else if (type == TYPE_INT) {\n const intVal = load<i64>(ptr + VALUE_TYPE_SIZE);\n return Value.Int(intVal);\n } else if (type == TYPE_FLOAT) {\n const floatVal = load<f64>(ptr + VALUE_TYPE_SIZE);\n return Value.Float(floatVal);\n } else if (type == TYPE_STRING) {\n const stringPtr = load<u32>(ptr + VALUE_TYPE_SIZE);\n const stringVal = readString(stringPtr);\n return Value.String(stringVal);\n } else if (type == TYPE_ARRAY) {\n const arrayPtr = load<u32>(ptr + VALUE_TYPE_SIZE);\n const arrayVal = readValueArray(arrayPtr);\n return Value.Array(arrayVal);\n } else if (type == TYPE_OBJECT) {\n const objectPtr = load<u32>(ptr + VALUE_TYPE_SIZE);\n const objectVal = readValueMap(objectPtr);\n return Value.Object(objectVal);\n }\n\n return Value.Null(); // Unknown type\n}\n\n// ============================================================================\n// Value Array Handling\n// ============================================================================\n\n// Format marker for flat numeric arrays (high bit set in length field)\nconst FLAT_ARRAY_MARKER: u32 = 0x80000000;\n\n/**\n * Detect if an array contains only floats/ints (homogeneous numeric array).\n *\n * @param arr - The array to check\n * @returns true if all elements are TYPE_FLOAT or TYPE_INT, false otherwise\n */\nfunction isHomogeneousNumericArray(arr: Array<Value>): bool {\n const len = arr.length;\n if (len == 0) return false;\n\n for (let i = 0; i < len; i++) {\n const type = arr[i].type;\n if (type != TYPE_FLOAT && type != TYPE_INT) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Write a homogeneous numeric array in flat format.\n *\n * Format: [length | FLAT_ARRAY_MARKER: u32][f64 elements...]\n *\n * The high bit of the length field is set to indicate flat format.\n * This specialized format avoids individual Value allocations and type tags,\n * reducing memory overhead and fragmentation significantly.\n *\n * @param arr - The array to write (must be homogeneous numeric)\n * @returns Pointer to the allocated array data, or 0 if allocation failed\n */\nfunction writeHomogeneousNumericArray(arr: Array<Value>): usize {\n const len = <u32>arr.length;\n\n // Calculate total size: length with marker (4) + f64 elements (8 each)\n const totalSize = ARRAY_LENGTH_SIZE + <usize>len * F64_ELEMENT_SIZE;\n\n // Single allocation for entire array\n const ptr = heapAlloc(totalSize);\n if (ptr == 0) return 0;\n\n // Write length with high bit set to indicate flat format\n store<u32>(ptr, len | FLAT_ARRAY_MARKER);\n\n // Write all numeric values as f64 directly (no Value wrapping)\n for (let i: u32 = 0; i < len; i++) {\n const val = arr[i];\n const numVal = val.type == TYPE_FLOAT ? val.floatVal : <f64>val.intVal;\n store<f64>(ptr + ARRAY_LENGTH_SIZE + <usize>i * F64_ELEMENT_SIZE, numVal);\n }\n\n return ptr;\n}\n\n/**\n * Read a homogeneous numeric array from flat format.\n *\n * Format: [length | FLAT_ARRAY_MARKER: u32][f64 elements...]\n *\n * @param ptr - Pointer to the array data in memory\n * @param len - The actual length (without the marker bit)\n * @returns The read array of Value objects\n */\nfunction readHomogeneousNumericArray(ptr: usize, len: u32): Array<Value> {\n const result = new Array<Value>(len);\n\n // Read all f64 values and wrap them as Value.Float\n for (let i: u32 = 0; i < len; i++) {\n const numVal = load<f64>(ptr + ARRAY_LENGTH_SIZE + <usize>i * F64_ELEMENT_SIZE);\n result[i] = Value.Float(numVal);\n }\n\n return result;\n}\n\n/**\n * Write a Value array to memory.\n *\n * The array is stored in one of two formats:\n *\n * 1. Flat numeric format (for homogeneous numeric arrays):\n * [length | FLAT_ARRAY_MARKER: u32][f64 elements...]\n *\n * 2. Generic format (for mixed-type arrays):\n * [length: u32][Value pointers...]\n *\n * Homogeneous numeric arrays are detected and serialized using a flat format\n * that eliminates per-element allocations and type tags, significantly reducing\n * memory overhead (from ~16+ bytes per element to 8 bytes) and fragmentation.\n * The flat format is indicated by setting the high bit of the length field.\n *\n * Memory is allocated from the runtime heap using heapAlloc().\n *\n * @param arr - The array to write to memory\n * @returns Pointer to the allocated array data, or 0 if allocation failed\n */\nexport function writeValueArray(arr: Array<Value>): usize {\n const len = <u32>arr.length;\n\n // Empty array (generic format)\n if (len == 0) {\n const ptr = heapAlloc(ARRAY_LENGTH_SIZE);\n if (ptr == 0) return 0;\n store<u32>(ptr, 0);\n return ptr;\n }\n\n // Optimization: detect homogeneous numeric arrays and use flat format\n if (isHomogeneousNumericArray(arr)) {\n return writeHomogeneousNumericArray(arr);\n }\n\n // Generic path: mixed-type array\n // Format: [length: u32][Value pointers...]\n const totalSize = ARRAY_LENGTH_SIZE + <usize>len * ELEMENT_PTR_SIZE;\n\n const ptr = heapAlloc(totalSize);\n if (ptr == 0) return 0;\n\n store<u32>(ptr, len);\n\n // Write each Value pointer\n for (let i: u32 = 0; i < len; i++) {\n const valuePtr = writeValue(arr[i]);\n store<u32>(ptr + ARRAY_LENGTH_SIZE + <usize>i * ELEMENT_PTR_SIZE, valuePtr);\n }\n\n return ptr;\n}\n\n/**\n * Read a Value array from memory.\n *\n * Handles both flat numeric format and generic format:\n *\n * 1. Flat numeric format: [length | FLAT_ARRAY_MARKER: u32][f64 elements...]\n * 2. Generic format: [length: u32][Value pointers...]\n *\n * The format is detected by checking if the high bit of the length is set.\n *\n * @param ptr - Pointer to the array data in memory\n * @returns The read array, or empty array if pointer is null\n */\nexport function readValueArray(ptr: usize): Array<Value> {\n // Return empty array for null pointer\n if (ptr == 0) {\n return new Array<Value>();\n }\n\n // Read length (with potential marker bit)\n const lengthField = load<u32>(ptr);\n\n // Check if this is a flat numeric array (high bit set)\n if ((lengthField & FLAT_ARRAY_MARKER) != 0) {\n // Flat numeric format - extract actual length\n const len = lengthField & ~FLAT_ARRAY_MARKER;\n return readHomogeneousNumericArray(ptr, len);\n }\n\n // Generic format - length is used as-is\n const len = lengthField;\n\n // Empty array\n if (len == 0) {\n return new Array<Value>();\n }\n\n // Read array of Value pointers\n const result = new Array<Value>(len);\n for (let i: u32 = 0; i < len; i++) {\n const valuePtr = load<u32>(ptr + ARRAY_LENGTH_SIZE + <usize>i * ELEMENT_PTR_SIZE);\n result[i] = readValue(valuePtr);\n }\n\n return result;\n}\n\n/**\n * Calculate the size in bytes needed to store a Value array in memory.\n *\n * Note: This calculates the size for the generic format.\n * For flat numeric arrays, the actual size may be different.\n *\n * @param len - The length of the array\n * @returns The total size in bytes including the length header (but not the individual Values)\n */\nexport function getValueArraySize(len: usize): usize {\n return ARRAY_LENGTH_SIZE + len * ELEMENT_PTR_SIZE;\n}\n\n// ============================================================================\n// Value Map (Object) Handling\n// ============================================================================\n\n/**\n * Write a Value map to memory.\n *\n * The map is stored in the format: [length: u32][keyPtr1: u32][valuePtr1: u32][keyPtr2: u32][valuePtr2: u32]...\n *\n * Keys are always strings and written to memory separately using writeString().\n * Values are serialized using writeValue() separately.\n *\n * Memory is allocated from the runtime heap using heapAlloc().\n *\n * JavaScript object property order is insertion-ordered (ES2015+), so we must preserve\n * order when serializing to match JS semantics and ensure deterministic output.\n *\n * @param map - The map to write to memory\n * @returns Pointer to the allocated map data, or 0 if allocation failed\n */\nexport function writeValueMap(map: Map<string, Value>): usize {\n const len = <u32>map.size;\n\n // Calculate total size: length field (4 bytes) + key/value pairs (8 bytes each)\n const totalSize = ARRAY_LENGTH_SIZE + <usize>len * 2 * ELEMENT_PTR_SIZE;\n\n // Allocate memory from heap\n const ptr = heapAlloc(totalSize);\n\n // Return 0 if allocation failed\n if (ptr == 0) {\n return 0;\n }\n\n // Write length\n store<u32>(ptr, len);\n\n // Get keys and write each key/value pair\n const keys = map.keys();\n for (let i: u32 = 0; i < <u32>keys.length; i++) {\n const key = keys[i];\n const offset = ptr + ARRAY_LENGTH_SIZE + <usize>i * 2 * ELEMENT_PTR_SIZE;\n\n // Write key pointer\n const keyPtr = writeString(key);\n store<u32>(offset, keyPtr);\n\n // Write value pointer\n const valuePtr = writeValue(map.get(key));\n store<u32>(offset + ELEMENT_PTR_SIZE, valuePtr);\n }\n\n return ptr;\n}\n\n/**\n * Read a Value map from memory.\n *\n * The map must be stored in the format: [length: u32][keyPtr1: u32][valuePtr1: u32][keyPtr2: u32][valuePtr2: u32]...\n *\n * @param ptr - Pointer to the map data in memory\n * @returns The read map, or empty map if pointer is null\n */\nexport function readValueMap(ptr: usize): Map<string, Value> {\n // Return empty map for null pointer\n if (ptr == 0) {\n return new Map<string, Value>();\n }\n\n // Read length\n const len = load<u32>(ptr);\n\n // Create map and populate it\n const result = new Map<string, Value>();\n for (let i: u32 = 0; i < len; i++) {\n const keyPtr = load<u32>(ptr + ARRAY_LENGTH_SIZE + <usize>i * 2 * ELEMENT_PTR_SIZE);\n const valuePtr = load<u32>(\n ptr + ARRAY_LENGTH_SIZE + <usize>i * 2 * ELEMENT_PTR_SIZE + ELEMENT_PTR_SIZE,\n );\n\n const key = readString(keyPtr);\n const value = readValue(valuePtr);\n\n result.set(key, value);\n }\n\n return result;\n}\n\n// ============================================================================\n// Array Utility Functions\n// ============================================================================\n\n/**\n * Calculate the length of an array from its pointer in memory.\n *\n * @param ptr - Pointer to the array data in memory\n * @returns The length of the array, or 0 if null\n */\nexport function getArrayLength(ptr: usize): usize {\n if (ptr == 0) {\n return 0;\n }\n return load<u32>(ptr);\n}\n\n/**\n * Check if an array pointer points to an empty array.\n *\n * @param ptr - Pointer to the array data in memory\n * @returns true if the array is empty or pointer is null, false otherwise\n */\nexport function isArrayEmpty(ptr: usize): bool {\n if (ptr == 0) {\n return true;\n }\n return load<u32>(ptr) == 0;\n}\n";
|
|
8520
10718
|
|
|
8521
10719
|
// src/compiler/cache.ts
|
|
8522
10720
|
var packageVersion = "";
|
|
8523
10721
|
function getPackageVersion() {
|
|
8524
10722
|
if (packageVersion === "") {
|
|
8525
10723
|
try {
|
|
8526
|
-
const packagePath = join(
|
|
8527
|
-
const pkg = JSON.parse(
|
|
10724
|
+
const packagePath = join(dirname(__dirname), "..", "package.json");
|
|
10725
|
+
const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
|
|
8528
10726
|
packageVersion = pkg.version || "unknown";
|
|
8529
10727
|
} catch {
|
|
8530
10728
|
packageVersion = "unknown";
|
|
@@ -8581,7 +10779,7 @@ function readCacheFromDisk(cacheKey) {
|
|
|
8581
10779
|
return null;
|
|
8582
10780
|
}
|
|
8583
10781
|
try {
|
|
8584
|
-
const content =
|
|
10782
|
+
const content = readFileSync(cachePath, "utf-8");
|
|
8585
10783
|
const entry = JSON.parse(content);
|
|
8586
10784
|
if (entry.version !== getPackageVersion()) {
|
|
8587
10785
|
unlinkSync(cachePath);
|
|
@@ -8795,8 +10993,8 @@ function createASCFileSystemCallbacks(virtualFS) {
|
|
|
8795
10993
|
const key = baseDir ? `${baseDir}/${name}` : name;
|
|
8796
10994
|
virtualFS.set(key, data2);
|
|
8797
10995
|
},
|
|
8798
|
-
listFiles(
|
|
8799
|
-
const prefix = baseDir ? `${baseDir}/${
|
|
10996
|
+
listFiles(dirname2, baseDir) {
|
|
10997
|
+
const prefix = baseDir ? `${baseDir}/${dirname2}` : dirname2;
|
|
8800
10998
|
return [...virtualFS.keys()].filter((f) => f.startsWith(prefix));
|
|
8801
10999
|
}
|
|
8802
11000
|
};
|
|
@@ -10428,7 +12626,7 @@ function canonicalizeSchema(schema) {
|
|
|
10428
12626
|
}
|
|
10429
12627
|
|
|
10430
12628
|
// src/compiler/index.ts
|
|
10431
|
-
import { readFileSync as
|
|
12629
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
10432
12630
|
import path2 from "path";
|
|
10433
12631
|
|
|
10434
12632
|
// src/compiler/ast-types.ts
|
|
@@ -12120,7 +14318,7 @@ function createFilesystemLoader(testDataRoot) {
|
|
|
12120
14318
|
for (const basePath of possiblePaths) {
|
|
12121
14319
|
try {
|
|
12122
14320
|
const filePath = path2.join(basePath, key);
|
|
12123
|
-
const content =
|
|
14321
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
12124
14322
|
return parseJDM(content);
|
|
12125
14323
|
} catch (error) {
|
|
12126
14324
|
if (isFileNotFoundError(error)) {
|