@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
|
@@ -1,1811 +0,0 @@
|
|
|
1
|
-
// assembly/runtime/expressions.ts (AssemblyScript)
|
|
2
|
-
import { Value, TYPE_ARRAY, TYPE_STRING, TYPE_FLOAT, TYPE_INT, TYPE_BOOL } from './values';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Helper function to create an argument array.
|
|
6
|
-
* AssemblyScript doesn't support true variadic functions or rest parameters, so we provide
|
|
7
|
-
* fixed-arity helpers (makeArgs1, makeArgs2, makeArgs3) for common function arities.
|
|
8
|
-
*
|
|
9
|
-
* These use thread-local reusable arrays to avoid allocations in hot paths.
|
|
10
|
-
* Since WASM evaluation is single-threaded, reusing arrays is safe.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Reusable argument arrays - lazily initialized
|
|
14
|
-
let _args1: Array<Value> | null = null;
|
|
15
|
-
let _args2: Array<Value> | null = null;
|
|
16
|
-
let _args3: Array<Value> | null = null;
|
|
17
|
-
|
|
18
|
-
export function makeArgs(a: Value, b: Value, c: Value = Value.Null()): Array<Value> {
|
|
19
|
-
if (_args3 === null) {
|
|
20
|
-
_args3 = new Array<Value>(3);
|
|
21
|
-
}
|
|
22
|
-
unchecked((_args3![0] = a));
|
|
23
|
-
unchecked((_args3![1] = b));
|
|
24
|
-
unchecked((_args3![2] = c));
|
|
25
|
-
return _args3!;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function makeArgs1(a: Value): Array<Value> {
|
|
29
|
-
if (_args1 === null) {
|
|
30
|
-
_args1 = new Array<Value>(1);
|
|
31
|
-
}
|
|
32
|
-
unchecked((_args1![0] = a));
|
|
33
|
-
return _args1!;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function makeArgs2(a: Value, b: Value): Array<Value> {
|
|
37
|
-
if (_args2 === null) {
|
|
38
|
-
_args2 = new Array<Value>(2);
|
|
39
|
-
}
|
|
40
|
-
unchecked((_args2![0] = a));
|
|
41
|
-
unchecked((_args2![1] = b));
|
|
42
|
-
return _args2!;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function makeArgs3(a: Value, b: Value, c: Value): Array<Value> {
|
|
46
|
-
if (_args3 === null) {
|
|
47
|
-
_args3 = new Array<Value>(3);
|
|
48
|
-
}
|
|
49
|
-
unchecked((_args3![0] = a));
|
|
50
|
-
unchecked((_args3![1] = b));
|
|
51
|
-
unchecked((_args3![2] = c));
|
|
52
|
-
return _args3!;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Type alias for function handlers
|
|
56
|
-
type FunctionHandler = (args: Array<Value>) => Value;
|
|
57
|
-
|
|
58
|
-
// Global function registry - Map provides O(1) lookup instead of O(n) if-else chain
|
|
59
|
-
const functionRegistry = new Map<string, FunctionHandler>();
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Initialize the function registry with all built-in functions.
|
|
63
|
-
* This is called once at module initialization.
|
|
64
|
-
*/
|
|
65
|
-
function initializeFunctionRegistry(): void {
|
|
66
|
-
// Array functions
|
|
67
|
-
functionRegistry.set('sum', (args: Array<Value>): Value => sumArray(args[0]));
|
|
68
|
-
functionRegistry.set('avg', (args: Array<Value>): Value => avgArray(args[0]));
|
|
69
|
-
functionRegistry.set('min', (args: Array<Value>): Value => minArray(args[0]));
|
|
70
|
-
functionRegistry.set('max', (args: Array<Value>): Value => maxArray(args[0]));
|
|
71
|
-
functionRegistry.set('sort', (args: Array<Value>): Value => sortArray(args[0]));
|
|
72
|
-
|
|
73
|
-
// Flatten has two aliases
|
|
74
|
-
const flatHandler = (args: Array<Value>): Value => {
|
|
75
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
76
|
-
return flatArray(args[0], arg1);
|
|
77
|
-
};
|
|
78
|
-
functionRegistry.set('flat', flatHandler);
|
|
79
|
-
functionRegistry.set('flatten', flatHandler);
|
|
80
|
-
|
|
81
|
-
// Count/length has two aliases
|
|
82
|
-
const lengthHandler = (args: Array<Value>): Value => lengthOf(args[0]);
|
|
83
|
-
functionRegistry.set('count', lengthHandler);
|
|
84
|
-
functionRegistry.set('len', lengthHandler);
|
|
85
|
-
|
|
86
|
-
functionRegistry.set('contains', (args: Array<Value>): Value => {
|
|
87
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
88
|
-
return containsValue(args[0], arg1);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Type conversion functions
|
|
92
|
-
functionRegistry.set('number', (args: Array<Value>): Value => numberValue(args[0]));
|
|
93
|
-
functionRegistry.set('string', (args: Array<Value>): Value => stringValue(args[0]));
|
|
94
|
-
|
|
95
|
-
// Math functions
|
|
96
|
-
functionRegistry.set('abs', (args: Array<Value>): Value => absValue(args[0]));
|
|
97
|
-
functionRegistry.set('floor', (args: Array<Value>): Value => floorValue(args[0]));
|
|
98
|
-
functionRegistry.set('ceil', (args: Array<Value>): Value => ceilValue(args[0]));
|
|
99
|
-
functionRegistry.set('round', (args: Array<Value>): Value => roundValue(args[0]));
|
|
100
|
-
|
|
101
|
-
// String functions
|
|
102
|
-
functionRegistry.set('upper', (args: Array<Value>): Value => upperString(args[0]));
|
|
103
|
-
functionRegistry.set('lower', (args: Array<Value>): Value => lowerString(args[0]));
|
|
104
|
-
functionRegistry.set('trim', (args: Array<Value>): Value => trimString(args[0]));
|
|
105
|
-
|
|
106
|
-
functionRegistry.set('substring', (args: Array<Value>): Value => {
|
|
107
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
108
|
-
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
109
|
-
return substringString(args[0], arg1, arg2);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
functionRegistry.set('indexOf', (args: Array<Value>): Value => {
|
|
113
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
114
|
-
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
115
|
-
return indexOfString(args[0], arg1, arg2.isNull() ? Value.Int(<i64>0) : arg2);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
functionRegistry.set('startsWith', (args: Array<Value>): Value => {
|
|
119
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
120
|
-
return startsWithString(args[0], arg1);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
functionRegistry.set('endsWith', (args: Array<Value>): Value => {
|
|
124
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
125
|
-
return endsWithString(args[0], arg1);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
functionRegistry.set('split', (args: Array<Value>): Value => {
|
|
129
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
130
|
-
return splitString(args[0], arg1);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
functionRegistry.set('join', (args: Array<Value>): Value => {
|
|
134
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
135
|
-
return joinArray(args[0], arg1);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
functionRegistry.set('replace', (args: Array<Value>): Value => {
|
|
139
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
140
|
-
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
141
|
-
return replaceString(args[0], arg1, arg2);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
functionRegistry.set('replaceAll', (args: Array<Value>): Value => {
|
|
145
|
-
const arg1 = args.length >= 2 ? args[1] : Value.Null();
|
|
146
|
-
const arg2 = args.length >= 3 ? args[2] : Value.Null();
|
|
147
|
-
return replaceAllString(args[0], arg1, arg2);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// Date/time functions
|
|
151
|
-
functionRegistry.set('date', (args: Array<Value>): Value => parseDate(args[0]));
|
|
152
|
-
|
|
153
|
-
// now() returns 0 because WASM doesn't have direct access to system time in a sandboxed way.
|
|
154
|
-
// To get the current timestamp, use date("now") instead, which uses Date.now().
|
|
155
|
-
functionRegistry.set('now', (args: Array<Value>): Value => Value.Float(0));
|
|
156
|
-
|
|
157
|
-
functionRegistry.set('time', (args: Array<Value>): Value => parseTime(args[0]));
|
|
158
|
-
functionRegistry.set('duration', (args: Array<Value>): Value => parseDuration(args[0]));
|
|
159
|
-
|
|
160
|
-
// Object functions
|
|
161
|
-
functionRegistry.set('values', (args: Array<Value>): Value => objectValues(args[0]));
|
|
162
|
-
functionRegistry.set('keys', (args: Array<Value>): Value => objectKeys(args[0]));
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Initialize the registry immediately
|
|
166
|
-
initializeFunctionRegistry();
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Evaluate built-in functions.
|
|
170
|
-
* All inputs and outputs use the Value tagged union.
|
|
171
|
-
*
|
|
172
|
-
* Uses a Map-based registry for O(1) function lookup instead of O(n) if-else chain.
|
|
173
|
-
*/
|
|
174
|
-
export function evaluateFunction(name: string, args: Array<Value>): Value {
|
|
175
|
-
// Safety check for args array
|
|
176
|
-
if (args == null || args.length == 0) return Value.Null();
|
|
177
|
-
|
|
178
|
-
// Look up handler in the registry
|
|
179
|
-
if (functionRegistry.has(name)) {
|
|
180
|
-
const handler = functionRegistry.get(name);
|
|
181
|
-
return handler(args);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Unknown function - return null
|
|
185
|
-
return Value.Null();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// === Object Functions ===
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get all values from an object as an array.
|
|
192
|
-
* If the input is not an object, returns an empty array.
|
|
193
|
-
*/
|
|
194
|
-
export function objectValues(val: Value): Value {
|
|
195
|
-
if (!val.isObject()) return Value.Array(new Array<Value>(0));
|
|
196
|
-
const obj = val.asObject();
|
|
197
|
-
const keys = obj.keys();
|
|
198
|
-
const result = new Array<Value>(keys.length);
|
|
199
|
-
for (let i = 0; i < keys.length; i++) {
|
|
200
|
-
unchecked((result[i] = obj.get(unchecked(keys[i]))));
|
|
201
|
-
}
|
|
202
|
-
return Value.Array(result);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Get all keys from an object as an array of strings.
|
|
207
|
-
* If the input is not an object, returns an empty array.
|
|
208
|
-
*/
|
|
209
|
-
export function objectKeys(val: Value): Value {
|
|
210
|
-
if (!val.isObject()) return Value.Array(new Array<Value>(0));
|
|
211
|
-
const obj = val.asObject();
|
|
212
|
-
const keys = obj.keys();
|
|
213
|
-
const result = new Array<Value>(keys.length);
|
|
214
|
-
for (let i = 0; i < keys.length; i++) {
|
|
215
|
-
unchecked((result[i] = Value.String(unchecked(keys[i]))));
|
|
216
|
-
}
|
|
217
|
-
return Value.Array(result);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// === Array Functions ===
|
|
221
|
-
|
|
222
|
-
export function sumArray(val: Value): Value {
|
|
223
|
-
if (!val.isArray()) return Value.Float(0.0);
|
|
224
|
-
const arr = val.asArray();
|
|
225
|
-
let total: f64 = 0.0;
|
|
226
|
-
for (let i = 0; i < arr.length; i++) {
|
|
227
|
-
total += unchecked(arr[i]).asFloat();
|
|
228
|
-
}
|
|
229
|
-
return Value.Float(total);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
export function minArray(val: Value): Value {
|
|
233
|
-
if (!val.isArray()) return Value.Null();
|
|
234
|
-
const arr = val.asArray();
|
|
235
|
-
if (arr.length == 0) return Value.Null();
|
|
236
|
-
let min = arr[0].asFloat();
|
|
237
|
-
for (let i = 1; i < arr.length; i++) {
|
|
238
|
-
const v = unchecked(arr[i]).asFloat();
|
|
239
|
-
if (v < min) min = v;
|
|
240
|
-
}
|
|
241
|
-
return Value.Float(min);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
export function maxArray(val: Value): Value {
|
|
245
|
-
if (!val.isArray()) return Value.Null();
|
|
246
|
-
const arr = val.asArray();
|
|
247
|
-
if (arr.length == 0) return Value.Null();
|
|
248
|
-
let max = arr[0].asFloat();
|
|
249
|
-
for (let i = 1; i < arr.length; i++) {
|
|
250
|
-
const v = unchecked(arr[i]).asFloat();
|
|
251
|
-
if (v > max) max = v;
|
|
252
|
-
}
|
|
253
|
-
return Value.Float(max);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function avgArray(val: Value): Value {
|
|
257
|
-
if (!val.isArray()) return Value.Null();
|
|
258
|
-
const arr = val.asArray();
|
|
259
|
-
if (arr.length == 0) return Value.Null();
|
|
260
|
-
let total: f64 = 0.0;
|
|
261
|
-
for (let i = 0; i < arr.length; i++) {
|
|
262
|
-
total += unchecked(arr[i]).asFloat();
|
|
263
|
-
}
|
|
264
|
-
return Value.Float(total / <f64>arr.length);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Sort an array in ascending order.
|
|
269
|
-
*
|
|
270
|
-
* Supports sorting numbers and strings in ascending order.
|
|
271
|
-
* Creates and returns a new sorted array without modifying the original.
|
|
272
|
-
*
|
|
273
|
-
* Parameters:
|
|
274
|
-
* - val: The array value to sort
|
|
275
|
-
*
|
|
276
|
-
* Behavior:
|
|
277
|
-
* - Numbers: sorted in ascending numeric order
|
|
278
|
-
* - Strings: sorted in lexicographic (ASCII) order
|
|
279
|
-
* - Empty arrays: returns a new empty array
|
|
280
|
-
* - Non-array inputs: returns null
|
|
281
|
-
*
|
|
282
|
-
* Examples:
|
|
283
|
-
* - sortArray([3, 1, 2]) -> [1, 2, 3]
|
|
284
|
-
* - sortArray(["c", "a", "b"]) -> ["a", "b", "c"]
|
|
285
|
-
* - sortArray([]) -> []
|
|
286
|
-
*
|
|
287
|
-
* @param val - The array value to sort
|
|
288
|
-
* @returns A new sorted array, or null for invalid input
|
|
289
|
-
*/
|
|
290
|
-
export function sortArray(val: Value): Value {
|
|
291
|
-
if (!val.isArray()) return Value.Null();
|
|
292
|
-
const arr = val.asArray();
|
|
293
|
-
|
|
294
|
-
// Create a copy of the array to avoid mutating the original
|
|
295
|
-
const sorted = new Array<Value>(arr.length);
|
|
296
|
-
for (let i = 0; i < arr.length; i++) {
|
|
297
|
-
unchecked((sorted[i] = unchecked(arr[i])));
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Sort the copy using the compareValues function
|
|
301
|
-
sorted.sort((a: Value, b: Value): i32 => compareValues(a, b));
|
|
302
|
-
|
|
303
|
-
return Value.Array(sorted);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Flatten nested arrays into a single-level array.
|
|
308
|
-
*
|
|
309
|
-
* Parameters:
|
|
310
|
-
* - val: The array value to flatten
|
|
311
|
-
* - depthVal: Optional depth (default: 1). Specifies how many levels deep to flatten.
|
|
312
|
-
*
|
|
313
|
-
* Behavior:
|
|
314
|
-
* - If depth is 0, returns a shallow copy of the array
|
|
315
|
-
* - Flattens arrays recursively up to the specified depth
|
|
316
|
-
* - Empty arrays are returned as empty arrays
|
|
317
|
-
* - Non-array inputs return null
|
|
318
|
-
*
|
|
319
|
-
* Examples:
|
|
320
|
-
* - flatArray([[1, 2], [3, 4]]) -> [1, 2, 3, 4] (depth 1)
|
|
321
|
-
* - flatArray([1, [2, [3]]]) -> [1, 2, [3]] (depth 1)
|
|
322
|
-
* - flatArray([1, [2, [3]]], 2) -> [1, 2, 3] (depth 2)
|
|
323
|
-
* - flatArray([1, 2, 3]) -> [1, 2, 3] (no nesting, shallow copy)
|
|
324
|
-
* - flatArray([]) -> [] (empty array)
|
|
325
|
-
* - flatArray([[1], [2]], 0) -> [[1], [2]] (depth 0, no flattening)
|
|
326
|
-
*
|
|
327
|
-
* @param val - The array value to flatten
|
|
328
|
-
* @param depthVal - Optional depth for flattening (default: 1)
|
|
329
|
-
* @returns A new flattened array, or null for invalid input
|
|
330
|
-
*/
|
|
331
|
-
export function flatArray(val: Value, depthVal: Value = Value.Int(<i64>1)): Value {
|
|
332
|
-
if (!val.isArray()) return Value.Null();
|
|
333
|
-
const arr = val.asArray();
|
|
334
|
-
|
|
335
|
-
// Parse depth parameter matches JavaScript Array.flat() default behavior, which
|
|
336
|
-
// flattens one level deep unless specified otherwise. Most common use case.
|
|
337
|
-
let depth: i32 = 1;
|
|
338
|
-
if (!depthVal.isNull() && depthVal.isNumber()) {
|
|
339
|
-
depth = <i32>depthVal.asFloat(); // Use asFloat() since numbers may come as floats
|
|
340
|
-
// Clamp depth to non-negative values
|
|
341
|
-
if (depth < 0) depth = 1;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Use a simple iterative approach that maintains order
|
|
345
|
-
let result = new Array<Value>();
|
|
346
|
-
flattenRecursive(arr, depth, result);
|
|
347
|
-
return Value.Array(result);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Helper function for flatArray
|
|
351
|
-
function flattenRecursive(arr: Array<Value>, currentDepth: i32, result: Array<Value>): void {
|
|
352
|
-
for (let i = 0; i < arr.length; i++) {
|
|
353
|
-
const element = unchecked(arr[i]);
|
|
354
|
-
if (element.isArray() && currentDepth > 0) {
|
|
355
|
-
// Recursively flatten nested array
|
|
356
|
-
flattenRecursive(element.asArray(), currentDepth - 1, result);
|
|
357
|
-
} else {
|
|
358
|
-
// Add element to results
|
|
359
|
-
result.push(element);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export function lengthOf(val: Value): Value {
|
|
365
|
-
if (val.isArray()) {
|
|
366
|
-
return Value.Int(<i64>val.asArray().length);
|
|
367
|
-
}
|
|
368
|
-
if (val.isString()) {
|
|
369
|
-
return Value.Int(<i64>val.asString().length);
|
|
370
|
-
}
|
|
371
|
-
return Value.Int(0);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Check if a value contains a subvalue.
|
|
376
|
-
*
|
|
377
|
-
* For strings: checks if the string contains a given substring.
|
|
378
|
-
* For arrays: checks if the array contains the given value.
|
|
379
|
-
*
|
|
380
|
-
* Parameters:
|
|
381
|
-
* - container: The string or array to search in
|
|
382
|
-
* - needle: The substring or value to search for
|
|
383
|
-
*
|
|
384
|
-
* Behavior:
|
|
385
|
-
* - If container is a string, checks if it contains the substring
|
|
386
|
-
* - If container is an array, checks if any element equals the needle
|
|
387
|
-
* - For strings, returns null if either argument is not a string
|
|
388
|
-
* - For arrays, always returns a boolean
|
|
389
|
-
*
|
|
390
|
-
* String Examples:
|
|
391
|
-
* - contains("hello world", "world") → true
|
|
392
|
-
* - contains("hello world", "xyz") → false
|
|
393
|
-
* - contains("abc", "abc") → true
|
|
394
|
-
* - contains("hello", "") → true (empty string is always contained)
|
|
395
|
-
* - contains("", "") → true
|
|
396
|
-
* - contains("", "x") → false
|
|
397
|
-
* - contains("Hello", "hello") → false (case-sensitive)
|
|
398
|
-
* - contains("hello", 123) → null (needle not a string)
|
|
399
|
-
* - contains(123, "hello") → null (container not a string)
|
|
400
|
-
*
|
|
401
|
-
* Array Examples:
|
|
402
|
-
* - contains([1, 2, 3], 2) → true
|
|
403
|
-
* - contains([1, 2, 3], 4) → false
|
|
404
|
-
* - contains(["a"], "a") → true
|
|
405
|
-
* - contains(123, 1) → false
|
|
406
|
-
*
|
|
407
|
-
* @param container - The string or array to search in
|
|
408
|
-
* @param needle - The substring or value to search for
|
|
409
|
-
* @returns true if contained, false if not, or null for invalid string arguments
|
|
410
|
-
*/
|
|
411
|
-
export function containsValue(container: Value, needle: Value): Value {
|
|
412
|
-
// Handle string containment
|
|
413
|
-
if (container.isString()) {
|
|
414
|
-
if (!needle.isString()) return Value.Null();
|
|
415
|
-
const str = container.asString();
|
|
416
|
-
const search = needle.asString();
|
|
417
|
-
// Use indexOf to check containment (returns -1 if not found)
|
|
418
|
-
const index = str.indexOf(search);
|
|
419
|
-
return Value.Bool(index >= 0);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Handle array containment (original behavior)
|
|
423
|
-
if (!container.isArray()) return Value.Bool(false);
|
|
424
|
-
const arr = container.asArray();
|
|
425
|
-
for (let i = 0; i < arr.length; i++) {
|
|
426
|
-
if (valuesEqual(unchecked(arr[i]), needle)) return Value.Bool(true);
|
|
427
|
-
}
|
|
428
|
-
return Value.Bool(false);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// === Date/Time Functions ===
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Parse a date string into a numeric timestamp.
|
|
435
|
-
*
|
|
436
|
-
* Supports ISO 8601 date format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS[Z|+/-HH:MM]
|
|
437
|
-
* Returns Unix timestamp in seconds since epoch (1970-01-01 00:00:00 UTC).
|
|
438
|
-
* This matches zen-engine's behavior and allows date arithmetic operations.
|
|
439
|
-
*
|
|
440
|
-
* Examples:
|
|
441
|
-
* - date("2025-03-20T10:30:00Z") → 1742466600 (seconds since epoch)
|
|
442
|
-
* - date("1970-01-01") → 0
|
|
443
|
-
* - date("1970-01-01T00:00:00Z") → 0
|
|
444
|
-
*
|
|
445
|
-
* @param val - A string value containing a date
|
|
446
|
-
* @returns A float Value representing seconds since epoch, or Null for invalid input
|
|
447
|
-
*/
|
|
448
|
-
export function parseDate(val: Value): Value {
|
|
449
|
-
if (!val.isString()) return Value.Null();
|
|
450
|
-
|
|
451
|
-
const str = val.asString();
|
|
452
|
-
|
|
453
|
-
// Handle special 'now' keyword - returns Unix timestamp in seconds
|
|
454
|
-
// Using Date.now() which returns milliseconds, convert to seconds
|
|
455
|
-
if (str == 'now') {
|
|
456
|
-
return Value.Float(<f64>Date.now() / 1000.0);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Fast path for simple YYYY-MM-DD format (10 characters)
|
|
460
|
-
// This avoids string allocations from split() and substring() for common cases
|
|
461
|
-
if (str.length == 10) {
|
|
462
|
-
// Check dash positions at indices 4 and 7
|
|
463
|
-
const dashPos1 = unchecked(<i32>str.charCodeAt(4));
|
|
464
|
-
const dashPos2 = unchecked(<i32>str.charCodeAt(7));
|
|
465
|
-
if (dashPos1 == 45 && dashPos2 == 45) {
|
|
466
|
-
// Hyphen is ASCII 45, digits start at ASCII 48
|
|
467
|
-
// Parse year, month, day directly without split()
|
|
468
|
-
const year =
|
|
469
|
-
unchecked(<i32>str.charCodeAt(0) - 48) * 1000 +
|
|
470
|
-
unchecked(<i32>str.charCodeAt(1) - 48) * 100 +
|
|
471
|
-
unchecked(<i32>str.charCodeAt(2) - 48) * 10 +
|
|
472
|
-
unchecked(<i32>str.charCodeAt(3) - 48);
|
|
473
|
-
const month =
|
|
474
|
-
unchecked(<i32>str.charCodeAt(5) - 48) * 10 + unchecked(<i32>str.charCodeAt(6) - 48);
|
|
475
|
-
const day =
|
|
476
|
-
unchecked(<i32>str.charCodeAt(8) - 48) * 10 + unchecked(<i32>str.charCodeAt(9) - 48);
|
|
477
|
-
|
|
478
|
-
// Validate ranges
|
|
479
|
-
if (year >= 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31) {
|
|
480
|
-
const daysSinceEpoch = calculateDaysSinceEpoch(year, month, day);
|
|
481
|
-
return Value.Float(<f64>daysSinceEpoch * 86400.0);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Parse ISO 8601 date format: YYYY-MM-DD
|
|
487
|
-
// Also handle datetime format: YYYY-MM-DDTHH:MM:SS[Z|+/-HH:MM]
|
|
488
|
-
let dateStr = str;
|
|
489
|
-
let timeStr = '';
|
|
490
|
-
let tzOffsetSeconds: i32 = 0; // Timezone offset in seconds (positive = ahead of UTC)
|
|
491
|
-
|
|
492
|
-
const tIndex = str.indexOf('T');
|
|
493
|
-
if (tIndex >= 0) {
|
|
494
|
-
dateStr = str.substring(0, tIndex);
|
|
495
|
-
let timePart = str.substring(tIndex + 1);
|
|
496
|
-
|
|
497
|
-
// Handle timezone: Z, +HH:MM, -HH:MM
|
|
498
|
-
const zIndex = timePart.indexOf('Z');
|
|
499
|
-
if (zIndex >= 0) {
|
|
500
|
-
timeStr = timePart.substring(0, zIndex);
|
|
501
|
-
tzOffsetSeconds = 0; // UTC
|
|
502
|
-
} else {
|
|
503
|
-
const plusIndex = timePart.indexOf('+');
|
|
504
|
-
const minusIndex = timePart.indexOf('-');
|
|
505
|
-
// Find timezone offset (look for +/- after the time part, not in middle of time)
|
|
506
|
-
let tzStartIndex = -1;
|
|
507
|
-
if (plusIndex >= 5) {
|
|
508
|
-
// At least HH:MM before timezone
|
|
509
|
-
tzStartIndex = plusIndex;
|
|
510
|
-
} else if (minusIndex >= 5) {
|
|
511
|
-
tzStartIndex = minusIndex;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (tzStartIndex >= 0) {
|
|
515
|
-
timeStr = timePart.substring(0, tzStartIndex);
|
|
516
|
-
const tzStr = timePart.substring(tzStartIndex);
|
|
517
|
-
// Parse timezone offset (+/-HH:MM or +/-HH)
|
|
518
|
-
const isNegative = tzStr.charAt(0) == '-';
|
|
519
|
-
const tzParts = tzStr.substring(1).split(':');
|
|
520
|
-
if (tzParts.length >= 1) {
|
|
521
|
-
const tzHours = parseIntValue(tzParts[0]);
|
|
522
|
-
const tzMinutes = tzParts.length >= 2 ? parseIntValue(tzParts[1]) : 0;
|
|
523
|
-
if (tzHours >= 0 && tzMinutes >= 0) {
|
|
524
|
-
tzOffsetSeconds = tzHours * 3600 + tzMinutes * 60;
|
|
525
|
-
if (isNegative) tzOffsetSeconds = -tzOffsetSeconds;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
} else {
|
|
529
|
-
timeStr = timePart;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Split date by dash
|
|
535
|
-
const parts = dateStr.split('-');
|
|
536
|
-
if (parts.length < 3) return Value.Null();
|
|
537
|
-
|
|
538
|
-
// Parse year, month, day
|
|
539
|
-
const year = parseIntValue(parts[0]);
|
|
540
|
-
const month = parseIntValue(parts[1]);
|
|
541
|
-
const day = parseIntValue(parts[2]);
|
|
542
|
-
|
|
543
|
-
if (year < 0 || month < 1 || month > 12 || day < 1 || day > 31) {
|
|
544
|
-
return Value.Null();
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Calculate days since epoch
|
|
548
|
-
const daysSinceEpoch = calculateDaysSinceEpoch(year, month, day);
|
|
549
|
-
|
|
550
|
-
// Parse time if present (HH:MM:SS or HH:MM)
|
|
551
|
-
let hours: i32 = 0;
|
|
552
|
-
let minutes: i32 = 0;
|
|
553
|
-
let seconds: i32 = 0;
|
|
554
|
-
|
|
555
|
-
if (timeStr.length > 0) {
|
|
556
|
-
const timeParts = timeStr.split(':');
|
|
557
|
-
if (timeParts.length >= 2) {
|
|
558
|
-
hours = parseIntValue(timeParts[0]);
|
|
559
|
-
minutes = parseIntValue(timeParts[1]);
|
|
560
|
-
if (timeParts.length >= 3) {
|
|
561
|
-
// Handle fractional seconds (e.g., "10:30:00.123")
|
|
562
|
-
const secStr = timeParts[2];
|
|
563
|
-
const dotIndex = secStr.indexOf('.');
|
|
564
|
-
if (dotIndex >= 0) {
|
|
565
|
-
seconds = parseIntValue(secStr.substring(0, dotIndex));
|
|
566
|
-
} else {
|
|
567
|
-
seconds = parseIntValue(secStr);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
|
|
572
|
-
// Invalid time - use 0
|
|
573
|
-
hours = 0;
|
|
574
|
-
minutes = 0;
|
|
575
|
-
seconds = 0;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// Calculate total seconds since epoch
|
|
580
|
-
// daysSinceEpoch * 86400 + hours * 3600 + minutes * 60 + seconds - tzOffsetSeconds
|
|
581
|
-
// We subtract tzOffsetSeconds to convert local time to UTC
|
|
582
|
-
const totalSeconds: f64 =
|
|
583
|
-
<f64>daysSinceEpoch * 86400.0 +
|
|
584
|
-
<f64>hours * 3600.0 +
|
|
585
|
-
<f64>minutes * 60.0 +
|
|
586
|
-
<f64>seconds -
|
|
587
|
-
<f64>tzOffsetSeconds;
|
|
588
|
-
|
|
589
|
-
return Value.Float(totalSeconds);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* Parse a time string into a numeric value.
|
|
594
|
-
*
|
|
595
|
-
* Supports format: HH:MM:SS or HH:MM
|
|
596
|
-
* Returns the number of seconds since midnight.
|
|
597
|
-
*
|
|
598
|
-
* @param val - A string value containing a time
|
|
599
|
-
* @returns A float Value representing seconds since midnight
|
|
600
|
-
*/
|
|
601
|
-
export function parseTime(val: Value): Value {
|
|
602
|
-
if (!val.isString()) return Value.Null();
|
|
603
|
-
|
|
604
|
-
const str = val.asString();
|
|
605
|
-
const parts = str.split(':');
|
|
606
|
-
|
|
607
|
-
if (parts.length < 2) return Value.Null();
|
|
608
|
-
|
|
609
|
-
const hours = parseIntValue(parts[0]);
|
|
610
|
-
const minutes = parseIntValue(parts[1]);
|
|
611
|
-
const seconds = parts.length >= 3 ? parseIntValue(parts[2]) : 0;
|
|
612
|
-
|
|
613
|
-
if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59 || seconds < 0 || seconds > 59) {
|
|
614
|
-
return Value.Null();
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const totalSeconds = hours * 3600 + minutes * 60 + seconds;
|
|
618
|
-
return Value.Float(<f64>totalSeconds);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Parse a duration string into a numeric value.
|
|
623
|
-
*
|
|
624
|
-
* Supports ISO 8601 duration format: P[n]Y[n]M[n]DT[n]H[n]M[n]S
|
|
625
|
-
* Also supports simple formats like "30d", "24h", "60m", "3600s"
|
|
626
|
-
*
|
|
627
|
-
* @param val - A string value containing a duration
|
|
628
|
-
* @returns A float Value representing duration in seconds
|
|
629
|
-
*/
|
|
630
|
-
export function parseDuration(val: Value): Value {
|
|
631
|
-
if (!val.isString()) return Value.Null();
|
|
632
|
-
|
|
633
|
-
const str = val.asString();
|
|
634
|
-
|
|
635
|
-
// Handle simple formats: "30d", "24h", "60m", "3600s"
|
|
636
|
-
if (str.length > 1) {
|
|
637
|
-
const unit = str.charAt(str.length - 1);
|
|
638
|
-
const numStr = str.substring(0, str.length - 1);
|
|
639
|
-
const num = parseFloatValue(numStr);
|
|
640
|
-
|
|
641
|
-
if (num >= 0) {
|
|
642
|
-
if (unit == 's') return Value.Float(num);
|
|
643
|
-
if (unit == 'm') return Value.Float(num * 60);
|
|
644
|
-
if (unit == 'h') return Value.Float(num * 3600);
|
|
645
|
-
if (unit == 'd') return Value.Float(num * 86400);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// Full ISO 8601 duration format (P[n]Y[n]M[n]DT[n]H[n]M[n]S) not yet supported
|
|
650
|
-
return Value.Null();
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
/**
|
|
654
|
-
* Parse a string to an integer value.
|
|
655
|
-
*/
|
|
656
|
-
function parseIntValue(str: string): i32 {
|
|
657
|
-
let result: i32 = 0;
|
|
658
|
-
let negative = false;
|
|
659
|
-
let i = 0;
|
|
660
|
-
|
|
661
|
-
// Handle leading whitespace
|
|
662
|
-
while (i < str.length && str.charCodeAt(i) == 32) {
|
|
663
|
-
i++;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Handle negative sign
|
|
667
|
-
if (i < str.length && str.charCodeAt(i) == 45) {
|
|
668
|
-
// '-'
|
|
669
|
-
negative = true;
|
|
670
|
-
i++;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Parse digits
|
|
674
|
-
let hasDigits = false;
|
|
675
|
-
while (i < str.length) {
|
|
676
|
-
const c = str.charCodeAt(i);
|
|
677
|
-
if (c >= 48 && c <= 57) {
|
|
678
|
-
// '0'-'9'
|
|
679
|
-
result = result * 10 + (c - 48);
|
|
680
|
-
hasDigits = true;
|
|
681
|
-
i++;
|
|
682
|
-
} else {
|
|
683
|
-
break;
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// AssemblyScript doesn't have optional types or Result<T,E>, so we use -1 as sentinel
|
|
688
|
-
// value for parse errors. Callers check for negative values to detect failures.
|
|
689
|
-
if (!hasDigits) return -1;
|
|
690
|
-
|
|
691
|
-
return negative ? -result : result;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Parse a string to a float value.
|
|
696
|
-
* Returns NaN for invalid input.
|
|
697
|
-
*/
|
|
698
|
-
function parseFloatValue(str: string): f64 {
|
|
699
|
-
return parseFloat(str);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/**
|
|
703
|
-
* Calculate days since Unix epoch (1970-01-01).
|
|
704
|
-
* Uses a simplified algorithm that handles leap years.
|
|
705
|
-
*/
|
|
706
|
-
function calculateDaysSinceEpoch(year: i32, month: i32, day: i32): i32 {
|
|
707
|
-
// Days in each month (non-leap year)
|
|
708
|
-
const daysInMonth: i32[] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
709
|
-
|
|
710
|
-
// Calculate days from years
|
|
711
|
-
let days: i32 = 0;
|
|
712
|
-
|
|
713
|
-
// Calculate years from 1970 to target year
|
|
714
|
-
if (year >= 1970) {
|
|
715
|
-
for (let y = 1970; y < year; y++) {
|
|
716
|
-
days += isLeapYear(y) ? 366 : 365;
|
|
717
|
-
}
|
|
718
|
-
} else {
|
|
719
|
-
for (let y = year; y < 1970; y++) {
|
|
720
|
-
days -= isLeapYear(y) ? 366 : 365;
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// Add days from months
|
|
725
|
-
for (let m = 1; m < month; m++) {
|
|
726
|
-
days += unchecked(daysInMonth[m - 1]);
|
|
727
|
-
// Add leap day for February in leap years
|
|
728
|
-
if (m == 2 && isLeapYear(year)) {
|
|
729
|
-
days += 1;
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Add days
|
|
734
|
-
days += day - 1; // day 1 is the first day, so subtract 1
|
|
735
|
-
|
|
736
|
-
return days;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Check if a year is a leap year.
|
|
741
|
-
*/
|
|
742
|
-
function isLeapYear(year: i32): bool {
|
|
743
|
-
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// === Math Functions ===
|
|
747
|
-
|
|
748
|
-
export function absValue(val: Value): Value {
|
|
749
|
-
if (val.isNumber()) {
|
|
750
|
-
const n = val.asFloat();
|
|
751
|
-
return Value.Float(n < 0 ? -n : n);
|
|
752
|
-
}
|
|
753
|
-
return Value.Null();
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
export function floorValue(val: Value): Value {
|
|
757
|
-
if (val.isNumber()) {
|
|
758
|
-
return Value.Float(Math.floor(val.asFloat()));
|
|
759
|
-
}
|
|
760
|
-
return Value.Null();
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
export function ceilValue(val: Value): Value {
|
|
764
|
-
if (val.isNumber()) {
|
|
765
|
-
return Value.Float(Math.ceil(val.asFloat()));
|
|
766
|
-
}
|
|
767
|
-
return Value.Null();
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
export function roundValue(val: Value): Value {
|
|
771
|
-
if (val.isNumber()) {
|
|
772
|
-
return Value.Float(Math.round(val.asFloat()));
|
|
773
|
-
}
|
|
774
|
-
return Value.Null();
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
// === String Functions ===
|
|
778
|
-
|
|
779
|
-
/**
|
|
780
|
-
* Convert a string to uppercase.
|
|
781
|
-
*
|
|
782
|
-
* Non-alphabetic characters (numbers, symbols, spaces) remain unchanged.
|
|
783
|
-
* If the input is not a string, returns null.
|
|
784
|
-
*
|
|
785
|
-
* Examples:
|
|
786
|
-
* - upper("hello") → "HELLO"
|
|
787
|
-
* - upper("Hello World") → "HELLO WORLD"
|
|
788
|
-
* - upper("123abc") → "123ABC"
|
|
789
|
-
* - upper("!@#") → "!@#"
|
|
790
|
-
* - upper(123) → null
|
|
791
|
-
* - upper(null) → null
|
|
792
|
-
*
|
|
793
|
-
* @param val - The value to convert to uppercase
|
|
794
|
-
* @returns The uppercase string, or null if input is not a string
|
|
795
|
-
*/
|
|
796
|
-
export function upperString(val: Value): Value {
|
|
797
|
-
if (!val.isString()) return Value.Null();
|
|
798
|
-
const str = val.asString();
|
|
799
|
-
return Value.String(str.toUpperCase());
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* Convert a string to lowercase.
|
|
804
|
-
*
|
|
805
|
-
* Non-alphabetic characters (numbers, symbols, spaces) remain unchanged.
|
|
806
|
-
* If the input is not a string, returns null.
|
|
807
|
-
*
|
|
808
|
-
* Examples:
|
|
809
|
-
* - lower("HELLO") → "hello"
|
|
810
|
-
* - lower("Hello World") → "hello world"
|
|
811
|
-
* - lower("123ABC") → "123abc"
|
|
812
|
-
* - lower("!@#") → "!@#"
|
|
813
|
-
* - lower(123) → null
|
|
814
|
-
* - lower(null) → null
|
|
815
|
-
*
|
|
816
|
-
* @param val - The value to convert to lowercase
|
|
817
|
-
* @returns The lowercase string, or null if input is not a string
|
|
818
|
-
*/
|
|
819
|
-
export function lowerString(val: Value): Value {
|
|
820
|
-
if (!val.isString()) return Value.Null();
|
|
821
|
-
const str = val.asString();
|
|
822
|
-
return Value.String(str.toLowerCase());
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Remove leading and trailing whitespace from a string.
|
|
827
|
-
*
|
|
828
|
-
* Whitespace includes spaces, tabs, newlines, and other whitespace characters.
|
|
829
|
-
* If the input is not a string, returns null.
|
|
830
|
-
*
|
|
831
|
-
* Examples:
|
|
832
|
-
* - trim(" hello ") → "hello"
|
|
833
|
-
* - trim("\t\nhello\n\t") → "hello"
|
|
834
|
-
* - trim("hello") → "hello" (no change)
|
|
835
|
-
* - trim(" hello world ") → "hello world"
|
|
836
|
-
* - trim("") → "" (empty string)
|
|
837
|
-
* - trim(" ") → "" (only whitespace)
|
|
838
|
-
* - trim(123) → null
|
|
839
|
-
* - trim(null) → null
|
|
840
|
-
*
|
|
841
|
-
* @param val - The value to trim whitespace from
|
|
842
|
-
* @returns The trimmed string, or null if input is not a string
|
|
843
|
-
*/
|
|
844
|
-
export function trimString(val: Value): Value {
|
|
845
|
-
if (!val.isString()) return Value.Null();
|
|
846
|
-
const str = val.asString();
|
|
847
|
-
return Value.String(str.trim());
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Extract a portion of a string.
|
|
852
|
-
*
|
|
853
|
-
* Returns a substring starting at the specified index and optionally
|
|
854
|
-
* ending at the specified index (or length).
|
|
855
|
-
*
|
|
856
|
-
* Parameters:
|
|
857
|
-
* - str: The input string
|
|
858
|
-
* - start: The starting index (0-based, inclusive)
|
|
859
|
-
* - endOrLength: Optional end index (exclusive) or length. If omitted,
|
|
860
|
-
* extracts from start to the end of the string.
|
|
861
|
-
*
|
|
862
|
-
* Behavior:
|
|
863
|
-
* - If start is negative, it's treated as an offset from the end of the string
|
|
864
|
-
* - If start < 0, it's clamped to 0 (empty prefix)
|
|
865
|
-
* - If start >= str.length, returns empty string
|
|
866
|
-
* - If endOrLength is omitted or null, extracts to end of string
|
|
867
|
-
* - If endOrLength is negative, it's treated as an offset from the end
|
|
868
|
-
* - If endOrLength > str.length, it's clamped to str.length
|
|
869
|
-
* - If endOrLength <= start, returns empty string
|
|
870
|
-
*
|
|
871
|
-
* Examples:
|
|
872
|
-
* - substring("hello", 0, 2) → "he"
|
|
873
|
-
* - substring("hello", 1, 3) → "el"
|
|
874
|
-
* - substring("hello", 2) → "llo"
|
|
875
|
-
* - substring("hello", 0) → "hello"
|
|
876
|
-
* - substring("hello", 0, 5) → "hello"
|
|
877
|
-
* - substring("hello", 0, 10) → "hello" (clamped to end)
|
|
878
|
-
* - substring("hello", 3, 5) → "lo"
|
|
879
|
-
* - substring("hello", -3) → "llo" (from end)
|
|
880
|
-
* - substring("hello", 0, -2) → "hel" (from end)
|
|
881
|
-
* - substring("hello", 10) → "" (start beyond length)
|
|
882
|
-
* - substring("", 0, 5) → "" (empty string)
|
|
883
|
-
* - substring(123, 0, 2) → null (not a string)
|
|
884
|
-
* - substring(null, 0, 2) → null (not a string)
|
|
885
|
-
*
|
|
886
|
-
* @param strVal - The string value to extract from
|
|
887
|
-
* @param startVal - The starting index
|
|
888
|
-
* @param endOrLengthVal - Optional end index (exclusive) or length
|
|
889
|
-
* @returns The substring, or null if input is not a string
|
|
890
|
-
*/
|
|
891
|
-
export function substringString(strVal: Value, startVal: Value, endOrLengthVal: Value): Value {
|
|
892
|
-
if (!strVal.isString()) return Value.Null();
|
|
893
|
-
const str = strVal.asString();
|
|
894
|
-
const len = str.length;
|
|
895
|
-
|
|
896
|
-
if (len == 0) return Value.String('');
|
|
897
|
-
|
|
898
|
-
// Parse start index - use asFloat() to handle both Int and Float types
|
|
899
|
-
let start: i32 = 0;
|
|
900
|
-
if (startVal.isNumber()) {
|
|
901
|
-
start = <i32>startVal.asFloat();
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Handle negative start (offset from end)
|
|
905
|
-
if (start < 0) {
|
|
906
|
-
start = len + start;
|
|
907
|
-
if (start < 0) start = 0; // Clamp to 0
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
// Handle start beyond length
|
|
911
|
-
if (start >= len) {
|
|
912
|
-
return Value.String('');
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
// Parse end index if provided
|
|
916
|
-
let hasEnd = !endOrLengthVal.isNull();
|
|
917
|
-
let end: i32 = len; // Default to end of string
|
|
918
|
-
|
|
919
|
-
if (hasEnd && endOrLengthVal.isNumber()) {
|
|
920
|
-
end = <i32>endOrLengthVal.asFloat();
|
|
921
|
-
|
|
922
|
-
// Handle negative end (offset from end)
|
|
923
|
-
if (end < 0) {
|
|
924
|
-
end = len + end;
|
|
925
|
-
if (end < 0) end = 0;
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Clamp end to string length
|
|
929
|
-
if (end > len) end = len;
|
|
930
|
-
|
|
931
|
-
// Ensure end >= start
|
|
932
|
-
if (end < start) end = start;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Extract substring
|
|
936
|
-
return Value.String(str.substring(start, end));
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
/**
|
|
940
|
-
* Find the index of the first occurrence of a substring.
|
|
941
|
-
*
|
|
942
|
-
* Returns the index of the first occurrence of the search string,
|
|
943
|
-
* starting from the specified position. Returns -1 if not found.
|
|
944
|
-
*
|
|
945
|
-
* Parameters:
|
|
946
|
-
* - str: The string to search in
|
|
947
|
-
* - searchVal: The substring to search for
|
|
948
|
-
* - startVal: Optional starting position (default: 0)
|
|
949
|
-
*
|
|
950
|
-
* Behavior:
|
|
951
|
-
* - If the source string is not a string, returns null
|
|
952
|
-
* - If the search value is not a string, returns null
|
|
953
|
-
* - If start is negative or omitted, defaults to 0
|
|
954
|
-
* - If start > str.length, returns -1 (not found)
|
|
955
|
-
* - If search string is empty, returns the start position
|
|
956
|
-
* - Returns -1 if the substring is not found
|
|
957
|
-
*
|
|
958
|
-
* Examples:
|
|
959
|
-
* - indexOf("hello", "l") → 2
|
|
960
|
-
* - indexOf("hello", "x") → -1
|
|
961
|
-
* - indexOf("hello", "l", 3) → 3
|
|
962
|
-
* - indexOf("hello", "ll") → 2
|
|
963
|
-
* - indexOf("hello", "lo", 0) → 3
|
|
964
|
-
* - indexOf("hello", "") → 0 (empty search string)
|
|
965
|
-
* - indexOf("hello", "o", 5) → -1 (start at end)
|
|
966
|
-
* - indexOf("hello", "o", 10) → -1 (start beyond length)
|
|
967
|
-
* - indexOf("", "x") → -1 (empty source)
|
|
968
|
-
* - indexOf("", "") → 0 (both empty)
|
|
969
|
-
* - indexOf(123, "1") → null (not a string)
|
|
970
|
-
*
|
|
971
|
-
* @param strVal - The string value to search in
|
|
972
|
-
* @param searchVal - The substring value to search for
|
|
973
|
-
* @param startVal - Optional starting position
|
|
974
|
-
* @returns The index of the first occurrence, or -1 if not found, or null if inputs are not strings
|
|
975
|
-
*/
|
|
976
|
-
export function indexOfString(strVal: Value, searchVal: Value, startVal: Value): Value {
|
|
977
|
-
if (!strVal.isString() || !searchVal.isString()) return Value.Null();
|
|
978
|
-
const str = strVal.asString();
|
|
979
|
-
const search = searchVal.asString();
|
|
980
|
-
|
|
981
|
-
// Parse start index
|
|
982
|
-
let start: i32 = 0;
|
|
983
|
-
if (!startVal.isNull() && startVal.isNumber()) {
|
|
984
|
-
start = <i32>startVal.asFloat();
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// Handle negative start
|
|
988
|
-
if (start < 0) {
|
|
989
|
-
start = 0;
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// Special case: empty search string (matches JavaScript behavior)
|
|
993
|
-
// When searching for empty string, return the start position clamped to string length
|
|
994
|
-
if (search.length == 0) {
|
|
995
|
-
const len = <i32>str.length;
|
|
996
|
-
if (start >= len) {
|
|
997
|
-
return Value.Int(<i64>len);
|
|
998
|
-
}
|
|
999
|
-
return Value.Int(<i64>start);
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
|
-
// AssemblyScript's indexOf returns -1 for not found
|
|
1003
|
-
const index = str.indexOf(search, start);
|
|
1004
|
-
return Value.Int(<i64>index);
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
/**
|
|
1008
|
-
* Check if a string starts with a given prefix.
|
|
1009
|
-
*
|
|
1010
|
-
* Returns true if the string begins with the specified prefix, false otherwise.
|
|
1011
|
-
*
|
|
1012
|
-
* Parameters:
|
|
1013
|
-
* - str: The string to check
|
|
1014
|
-
* - prefix: The prefix to look for
|
|
1015
|
-
*
|
|
1016
|
-
* Behavior:
|
|
1017
|
-
* - If either input is not a string, returns null
|
|
1018
|
-
* - If the prefix is an empty string, returns true
|
|
1019
|
-
* - Case-sensitive comparison
|
|
1020
|
-
*
|
|
1021
|
-
* Examples:
|
|
1022
|
-
* - startsWith("hello", "he") → true
|
|
1023
|
-
* - startsWith("hello", "lo") → false
|
|
1024
|
-
* - startsWith("hello", "hello") → true
|
|
1025
|
-
* - startsWith("hello", "hello world") → false (prefix longer than string)
|
|
1026
|
-
* - startsWith("", "") → true (empty strings)
|
|
1027
|
-
* - startsWith("hello", "") → true (empty prefix)
|
|
1028
|
-
* - startsWith("", "he") → false (prefix longer than empty string)
|
|
1029
|
-
* - startsWith("Hello", "he") → false (case-sensitive)
|
|
1030
|
-
* - startsWith(123, "1") → null (not a string)
|
|
1031
|
-
* - startsWith(null, "he") → null (not a string)
|
|
1032
|
-
*
|
|
1033
|
-
* @param strVal - The string value to check
|
|
1034
|
-
* @param prefixVal - The prefix value to look for
|
|
1035
|
-
* @returns true if the string starts with the prefix, false otherwise, or null if inputs are not strings
|
|
1036
|
-
*/
|
|
1037
|
-
export function startsWithString(strVal: Value, prefixVal: Value): Value {
|
|
1038
|
-
if (!strVal.isString() || !prefixVal.isString()) return Value.Null();
|
|
1039
|
-
const str = strVal.asString();
|
|
1040
|
-
const prefix = prefixVal.asString();
|
|
1041
|
-
|
|
1042
|
-
// AssemblyScript's startsWith returns a bool
|
|
1043
|
-
const result = str.startsWith(prefix);
|
|
1044
|
-
return Value.Bool(result);
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
/**
|
|
1048
|
-
* Check if a string ends with a given suffix.
|
|
1049
|
-
*
|
|
1050
|
-
* Returns true if the string ends with the specified suffix, false otherwise.
|
|
1051
|
-
*
|
|
1052
|
-
* Parameters:
|
|
1053
|
-
* - str: The string to check
|
|
1054
|
-
* - suffix: The suffix to look for
|
|
1055
|
-
*
|
|
1056
|
-
* Behavior:
|
|
1057
|
-
* - If either input is not a string, returns null
|
|
1058
|
-
* - If the suffix is an empty string, returns true
|
|
1059
|
-
* - Case-sensitive comparison
|
|
1060
|
-
*
|
|
1061
|
-
* Examples:
|
|
1062
|
-
* - endsWith("hello", "lo") → true
|
|
1063
|
-
* - endsWith("hello", "he") → false
|
|
1064
|
-
* - endsWith("hello", "hello") → true
|
|
1065
|
-
* - endsWith("hello", "hello world") → false (suffix longer than string)
|
|
1066
|
-
* - endsWith("", "") → true (empty strings)
|
|
1067
|
-
* - endsWith("hello", "") → true (empty suffix)
|
|
1068
|
-
* - endsWith("", "lo") → false (suffix longer than empty string)
|
|
1069
|
-
* - endsWith("Hello", "LO") → false (case-sensitive)
|
|
1070
|
-
* - endsWith(123, "1") → null (not a string)
|
|
1071
|
-
* - endsWith(null, "lo") → null (not a string)
|
|
1072
|
-
*
|
|
1073
|
-
* @param strVal - The string value to check
|
|
1074
|
-
* @param suffixVal - The suffix value to look for
|
|
1075
|
-
* @returns true if the string ends with the suffix, false otherwise, or null if inputs are not strings
|
|
1076
|
-
*/
|
|
1077
|
-
export function endsWithString(strVal: Value, suffixVal: Value): Value {
|
|
1078
|
-
if (!strVal.isString() || !suffixVal.isString()) return Value.Null();
|
|
1079
|
-
const str = strVal.asString();
|
|
1080
|
-
const suffix = suffixVal.asString();
|
|
1081
|
-
|
|
1082
|
-
// AssemblyScript's endsWith returns a bool
|
|
1083
|
-
const result = str.endsWith(suffix);
|
|
1084
|
-
return Value.Bool(result);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
/**
|
|
1088
|
-
* Split a string into an array based on a delimiter.
|
|
1089
|
-
*
|
|
1090
|
-
* Returns an array of substrings by dividing the input string at each
|
|
1091
|
-
* occurrence of the specified delimiter.
|
|
1092
|
-
*
|
|
1093
|
-
* Parameters:
|
|
1094
|
-
* - str: The string to split
|
|
1095
|
-
* - delimiter: The delimiter to split on
|
|
1096
|
-
*
|
|
1097
|
-
* Behavior:
|
|
1098
|
-
* - If the input string is not a string, returns null
|
|
1099
|
-
* - If the delimiter is not a string, returns null
|
|
1100
|
-
* - If the delimiter is an empty string, returns an array of individual characters
|
|
1101
|
-
* - If the string and delimiter are both empty, returns an array with one empty string
|
|
1102
|
-
* - If the delimiter is not found, returns an array with the original string
|
|
1103
|
-
*
|
|
1104
|
-
* Examples:
|
|
1105
|
-
* - split("a,b,c", ",") → ["a", "b", "c"]
|
|
1106
|
-
* - split("hello", "") → ["h", "e", "l", "l", "o"]
|
|
1107
|
-
* - split("a-b-c", "-") → ["a", "b", "c"]
|
|
1108
|
-
* - split("word", "x") → ["word"]
|
|
1109
|
-
* - split("", ",") → [""]
|
|
1110
|
-
* - split("", "") → [""] (special case: empty string splits into array with one empty string)
|
|
1111
|
-
* - split("a,,b", ",") → ["a", "", "b"] (preserves empty segments)
|
|
1112
|
-
* - split(null, ",") → null
|
|
1113
|
-
* - split("hello", null) → null
|
|
1114
|
-
* - split("hello", 123) → null
|
|
1115
|
-
*
|
|
1116
|
-
* @param strVal - The string value to split
|
|
1117
|
-
* @param delimiterVal - The delimiter value to split on
|
|
1118
|
-
* @returns An array of strings, or null if inputs are invalid
|
|
1119
|
-
*/
|
|
1120
|
-
export function splitString(strVal: Value, delimiterVal: Value): Value {
|
|
1121
|
-
if (!strVal.isString() || !delimiterVal.isString()) return Value.Null();
|
|
1122
|
-
const str = strVal.asString();
|
|
1123
|
-
const delimiter = delimiterVal.asString();
|
|
1124
|
-
|
|
1125
|
-
const parts = str.split(delimiter);
|
|
1126
|
-
|
|
1127
|
-
// Convert string array to Value array
|
|
1128
|
-
const result = new Array<Value>(parts.length);
|
|
1129
|
-
for (let i = 0; i < parts.length; i++) {
|
|
1130
|
-
unchecked((result[i] = Value.String(unchecked(parts[i]))));
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
return Value.Array(result);
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
/**
|
|
1137
|
-
* Join array elements into a string with a separator.
|
|
1138
|
-
*
|
|
1139
|
-
* Returns a string that concatenates all elements of the array,
|
|
1140
|
-
* separated by the specified delimiter.
|
|
1141
|
-
*
|
|
1142
|
-
* Parameters:
|
|
1143
|
-
* - arr: The array to join
|
|
1144
|
-
* - delimiter: The delimiter to insert between elements
|
|
1145
|
-
*
|
|
1146
|
-
* Behavior:
|
|
1147
|
-
* - If the input is not an array, returns null
|
|
1148
|
-
* - If the delimiter is not a string, returns null
|
|
1149
|
-
* - If the array is empty, returns an empty string
|
|
1150
|
-
* - Each array element is converted to its string representation
|
|
1151
|
-
* - The delimiter is placed between each adjacent element, not at the start or end
|
|
1152
|
-
*
|
|
1153
|
-
* Examples:
|
|
1154
|
-
* - join(["a", "b", "c"], "-") → "a-b-c"
|
|
1155
|
-
* - join(["hello", "world"], " ") → "hello world"
|
|
1156
|
-
* - join(["a"], ",") → "a"
|
|
1157
|
-
* - join([], ",") → ""
|
|
1158
|
-
* - join([1, 2, 3], "-") → "1-2-3" (numbers converted to strings)
|
|
1159
|
-
* - join([true, false], " ") → "true false" (booleans converted to strings)
|
|
1160
|
-
* - join(["a", null, "b"], "-") → "a-null-b" (null converted to "null")
|
|
1161
|
-
* - join(null, ",") → null
|
|
1162
|
-
* - join(["a"], null) → null
|
|
1163
|
-
* - join(["a"], 123) → null
|
|
1164
|
-
*
|
|
1165
|
-
* @param arrVal - The array value to join
|
|
1166
|
-
* @param delimiterVal - The delimiter to insert between elements
|
|
1167
|
-
* @returns The concatenated string, or null if inputs are invalid
|
|
1168
|
-
*/
|
|
1169
|
-
export function joinArray(arrVal: Value, delimiterVal: Value): Value {
|
|
1170
|
-
if (!arrVal.isArray() || !delimiterVal.isString()) return Value.Null();
|
|
1171
|
-
const arr = arrVal.asArray();
|
|
1172
|
-
const delimiter = delimiterVal.asString();
|
|
1173
|
-
|
|
1174
|
-
// Handle empty array
|
|
1175
|
-
if (arr.length == 0) {
|
|
1176
|
-
return Value.String('');
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
// Build the result string
|
|
1180
|
-
let result: string = '';
|
|
1181
|
-
for (let i = 0; i < arr.length; i++) {
|
|
1182
|
-
// Append delimiter if not first element
|
|
1183
|
-
if (i > 0) {
|
|
1184
|
-
result += delimiter;
|
|
1185
|
-
}
|
|
1186
|
-
// Convert value to string and append
|
|
1187
|
-
result += valueToString(unchecked(arr[i]));
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
return Value.String(result);
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
/**
|
|
1194
|
-
* Convert a string value to a number.
|
|
1195
|
-
*
|
|
1196
|
-
* Parses a string containing a numeric value and returns it as a float.
|
|
1197
|
-
* Supports decimal numbers and negative numbers.
|
|
1198
|
-
*
|
|
1199
|
-
* Parameters:
|
|
1200
|
-
* - val: The value to convert to a number (must be a string)
|
|
1201
|
-
*
|
|
1202
|
-
* Behavior:
|
|
1203
|
-
* - If input is not a string, returns null
|
|
1204
|
-
* - If string cannot be parsed as a number, returns NaN
|
|
1205
|
-
* - Returns f64 (float) for all numeric values
|
|
1206
|
-
*
|
|
1207
|
-
* Examples:
|
|
1208
|
-
* - number("123") → 123.0
|
|
1209
|
-
* - number("3.14") → 3.14
|
|
1210
|
-
* - number("-42") → -42.0
|
|
1211
|
-
* - number("0.5") → 0.5
|
|
1212
|
-
* - number("not a number") → NaN
|
|
1213
|
-
* - number(null) → null
|
|
1214
|
-
* - number(123) → null (not a string)
|
|
1215
|
-
*
|
|
1216
|
-
* @param val - The value to convert to a number
|
|
1217
|
-
* @returns The numeric value as a float, or null if input is not a string
|
|
1218
|
-
*/
|
|
1219
|
-
export function numberValue(val: Value): Value {
|
|
1220
|
-
if (val.isString()) {
|
|
1221
|
-
const str = val.asString();
|
|
1222
|
-
// Parse the string using the same logic as parseIntValue
|
|
1223
|
-
let result: i64 = 0;
|
|
1224
|
-
let negative = false;
|
|
1225
|
-
let i = 0;
|
|
1226
|
-
|
|
1227
|
-
// Handle leading whitespace
|
|
1228
|
-
while (i < str.length && str.charCodeAt(i) == 32) {
|
|
1229
|
-
i++;
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// Handle negative sign
|
|
1233
|
-
if (i < str.length && str.charCodeAt(i) == 45) {
|
|
1234
|
-
// '-'
|
|
1235
|
-
negative = true;
|
|
1236
|
-
i++;
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// Parse digits
|
|
1240
|
-
let hasDigits = false;
|
|
1241
|
-
while (i < str.length) {
|
|
1242
|
-
const c = str.charCodeAt(i);
|
|
1243
|
-
if (c >= 48 && c <= 57) {
|
|
1244
|
-
// '0'-'9'
|
|
1245
|
-
result = result * 10 + (c - 48);
|
|
1246
|
-
hasDigits = true;
|
|
1247
|
-
i++;
|
|
1248
|
-
} else {
|
|
1249
|
-
break;
|
|
1250
|
-
}
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
if (!hasDigits) return Value.Null();
|
|
1254
|
-
|
|
1255
|
-
const finalResult = negative ? -result : result;
|
|
1256
|
-
return Value.Float(<f64>finalResult);
|
|
1257
|
-
} else if (val.isNumber()) {
|
|
1258
|
-
return val;
|
|
1259
|
-
} else if (val.isBool()) {
|
|
1260
|
-
return val.asBool() ? Value.Float(1.0) : Value.Float(0.0);
|
|
1261
|
-
}
|
|
1262
|
-
return Value.Null();
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
/**
|
|
1266
|
-
* Convert any value to a string.
|
|
1267
|
-
*
|
|
1268
|
-
* Converts the input value to its string representation.
|
|
1269
|
-
*
|
|
1270
|
-
* Parameters:
|
|
1271
|
-
* - val: Any value to convert to string
|
|
1272
|
-
*
|
|
1273
|
-
* Behavior:
|
|
1274
|
-
* - Boolean: "true" or "false"
|
|
1275
|
-
* - Number: numeric string representation
|
|
1276
|
-
* - String: returned as-is
|
|
1277
|
-
* - Null: null (returns null, not the string "null")
|
|
1278
|
-
* - Array/Object: "[object]"
|
|
1279
|
-
*
|
|
1280
|
-
* Examples:
|
|
1281
|
-
* - string(42) → "42"
|
|
1282
|
-
* - string(3.14) → "3.14"
|
|
1283
|
-
* - string(true) → "true"
|
|
1284
|
-
* - string(false) → "false"
|
|
1285
|
-
* - string("hello") → "hello"
|
|
1286
|
-
* - string(null) → null
|
|
1287
|
-
*
|
|
1288
|
-
* @param val - The value to convert to a string
|
|
1289
|
-
* @returns The string representation, or null if input is null
|
|
1290
|
-
*/
|
|
1291
|
-
export function stringValue(val: Value): Value {
|
|
1292
|
-
if (val.isNull()) {
|
|
1293
|
-
return Value.Null();
|
|
1294
|
-
}
|
|
1295
|
-
if (val.isBool()) {
|
|
1296
|
-
return Value.String(val.asBool() ? 'true' : 'false');
|
|
1297
|
-
}
|
|
1298
|
-
if (val.isNumber()) {
|
|
1299
|
-
// Converting to string matches JavaScript String() coercion for consistency with JS engines.
|
|
1300
|
-
// Integers print without decimals (3 not 3.0), floats use AS toString() formatting.
|
|
1301
|
-
if (val.type == TYPE_INT) {
|
|
1302
|
-
return Value.String(val.intVal.toString());
|
|
1303
|
-
}
|
|
1304
|
-
const f = val.asFloat();
|
|
1305
|
-
// Check if it's a whole number by testing if remainder is 0
|
|
1306
|
-
if (f % 1.0 == 0.0) {
|
|
1307
|
-
return Value.String(i64(<i64>f).toString());
|
|
1308
|
-
}
|
|
1309
|
-
return Value.String(f.toString());
|
|
1310
|
-
}
|
|
1311
|
-
if (val.isString()) {
|
|
1312
|
-
return val;
|
|
1313
|
-
}
|
|
1314
|
-
// Arrays and objects fall back to a placeholder
|
|
1315
|
-
return Value.String('[object]');
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* Replace the first occurrence of a substring with another string.
|
|
1320
|
-
*
|
|
1321
|
-
* Returns a new string where the first occurrence of the search string
|
|
1322
|
-
* is replaced by the replacement string.
|
|
1323
|
-
*
|
|
1324
|
-
* Parameters:
|
|
1325
|
-
* - str: The string to modify
|
|
1326
|
-
* - search: The substring to search for
|
|
1327
|
-
* - replacement: The string to replace with
|
|
1328
|
-
*
|
|
1329
|
-
* Behavior:
|
|
1330
|
-
* - If the source string is not a string, returns null
|
|
1331
|
-
* - If the search value is not a string, returns null
|
|
1332
|
-
* - If the replacement value is not a string, returns null
|
|
1333
|
-
* - If the search string is empty, the replacement is inserted at the beginning
|
|
1334
|
-
* - If the search string is not found, returns the original string unchanged
|
|
1335
|
-
* - Only the first occurrence is replaced
|
|
1336
|
-
*
|
|
1337
|
-
* Examples:
|
|
1338
|
-
* - replace("hello", "l", "L") → "heLlo"
|
|
1339
|
-
* - replace("hello", "lo", "LO") → "heLLO"
|
|
1340
|
-
* - replace("hello", "x", "X") → "hello" (not found, unchanged)
|
|
1341
|
-
* - replace("hello hello", "l", "L") → "heLlo hello" (only first occurrence)
|
|
1342
|
-
* - replace("hello", "", "X") → "Xhello" (empty search, insert at start)
|
|
1343
|
-
* - replace("", "x", "X") → "" (empty source, no match)
|
|
1344
|
-
* - replace(null, "l", "L") → null (not a string)
|
|
1345
|
-
* - replace("hello", null, "L") → null (not a string)
|
|
1346
|
-
* - replace("hello", "l", null) → null (not a string)
|
|
1347
|
-
*
|
|
1348
|
-
* @param strVal - The string value to modify
|
|
1349
|
-
* @param searchVal - The substring value to search for
|
|
1350
|
-
* @param replacementVal - The replacement string value
|
|
1351
|
-
* @returns The modified string with first occurrence replaced, or null if inputs are invalid
|
|
1352
|
-
*/
|
|
1353
|
-
export function replaceString(strVal: Value, searchVal: Value, replacementVal: Value): Value {
|
|
1354
|
-
if (!strVal.isString() || !searchVal.isString() || !replacementVal.isString())
|
|
1355
|
-
return Value.Null();
|
|
1356
|
-
const str = strVal.asString();
|
|
1357
|
-
const search = searchVal.asString();
|
|
1358
|
-
const replacement = replacementVal.asString();
|
|
1359
|
-
|
|
1360
|
-
// AssemblyScript's replace() replaces all occurrences by default
|
|
1361
|
-
// We need to replace only the first occurrence, so we use indexOf and string concatenation
|
|
1362
|
-
const index = str.indexOf(search);
|
|
1363
|
-
|
|
1364
|
-
// Search string not found - return original string
|
|
1365
|
-
if (index < 0) {
|
|
1366
|
-
return Value.String(str);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// Build result: prefix + replacement + suffix
|
|
1370
|
-
const prefix = str.substring(0, index);
|
|
1371
|
-
const suffix = str.substring(index + search.length);
|
|
1372
|
-
return Value.String(prefix + replacement + suffix);
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
/**
|
|
1376
|
-
* Replace all occurrences of a substring with another string.
|
|
1377
|
-
*
|
|
1378
|
-
* Returns a new string where all occurrences of the search string
|
|
1379
|
-
* are replaced by the replacement string.
|
|
1380
|
-
*
|
|
1381
|
-
* Parameters:
|
|
1382
|
-
* - str: The string to modify
|
|
1383
|
-
* - search: The substring to search for
|
|
1384
|
-
* - replacement: The string to replace with
|
|
1385
|
-
*
|
|
1386
|
-
* Behavior:
|
|
1387
|
-
* - If the source string is not a string, returns null
|
|
1388
|
-
* - If the search value is not a string, returns null
|
|
1389
|
-
* - If the replacement value is not a string, returns null
|
|
1390
|
-
* - If the search string is empty, the replacement is inserted between each character
|
|
1391
|
-
* - If the search string is not found, returns the original string unchanged
|
|
1392
|
-
* - All occurrences are replaced
|
|
1393
|
-
*
|
|
1394
|
-
* Examples:
|
|
1395
|
-
* - replaceAll("hello", "l", "L") → "heLLo"
|
|
1396
|
-
* - replaceAll("hello hello", "l", "L") → "heLLo heLLo"
|
|
1397
|
-
* - replaceAll("hello", "lo", "LO") → "heLLO"
|
|
1398
|
-
* - replaceAll("hello", "x", "X") → "hello" (not found, unchanged)
|
|
1399
|
-
* - replaceAll("hello", "", "X") → "XhXeXlXlXoX" (empty search, insert between chars)
|
|
1400
|
-
* - replaceAll("", "x", "X") → "" (empty source, no match)
|
|
1401
|
-
* - replaceAll(null, "l", "L") → null (not a string)
|
|
1402
|
-
* - replaceAll("hello", null, "L") → null (not a string)
|
|
1403
|
-
* - replaceAll("hello", "l", null) → null (not a string)
|
|
1404
|
-
*
|
|
1405
|
-
* @param strVal - The string value to modify
|
|
1406
|
-
* @param searchVal - The substring value to search for
|
|
1407
|
-
* @param replacementVal - The replacement string value
|
|
1408
|
-
* @returns The modified string with all occurrences replaced, or null if inputs are invalid
|
|
1409
|
-
*/
|
|
1410
|
-
export function replaceAllString(strVal: Value, searchVal: Value, replacementVal: Value): Value {
|
|
1411
|
-
if (!strVal.isString() || !searchVal.isString() || !replacementVal.isString())
|
|
1412
|
-
return Value.Null();
|
|
1413
|
-
let str = strVal.asString();
|
|
1414
|
-
const search = searchVal.asString();
|
|
1415
|
-
const replacement = replacementVal.asString();
|
|
1416
|
-
|
|
1417
|
-
// Handle empty search string - insert replacement between each character
|
|
1418
|
-
if (search.length == 0) {
|
|
1419
|
-
let result = replacement;
|
|
1420
|
-
for (let i = 0; i < str.length; i++) {
|
|
1421
|
-
result += str.charAt(i) + replacement;
|
|
1422
|
-
}
|
|
1423
|
-
return Value.String(result);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// Use split and join for O(n) complexity instead of quadratic substring approach
|
|
1427
|
-
const parts = str.split(search);
|
|
1428
|
-
return Value.String(parts.join(replacement));
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// === Value Type Conversion ===
|
|
1432
|
-
|
|
1433
|
-
/**
|
|
1434
|
-
* Convert a Value to its string representation.
|
|
1435
|
-
*
|
|
1436
|
-
* This follows JavaScript's String() conversion rules:
|
|
1437
|
-
* - Number: converted using JS toString()
|
|
1438
|
-
* - Boolean: "true" or "false"
|
|
1439
|
-
* - String: returned as-is
|
|
1440
|
-
* - Null: "null"
|
|
1441
|
-
*
|
|
1442
|
-
* @param val - The value to convert
|
|
1443
|
-
* @returns The string representation
|
|
1444
|
-
*/
|
|
1445
|
-
function valueToString(val: Value): string {
|
|
1446
|
-
if (val.isNull()) {
|
|
1447
|
-
return 'null';
|
|
1448
|
-
}
|
|
1449
|
-
if (val.isBool()) {
|
|
1450
|
-
return val.asBool() ? 'true' : 'false';
|
|
1451
|
-
}
|
|
1452
|
-
if (val.isNumber()) {
|
|
1453
|
-
// Converting to string matches JavaScript String() coercion for consistency with JS engines.
|
|
1454
|
-
// Integers print without decimals (3 not 3.0), floats use AS toString() formatting.
|
|
1455
|
-
if (val.type == TYPE_INT) {
|
|
1456
|
-
return val.intVal.toString();
|
|
1457
|
-
}
|
|
1458
|
-
const f = val.asFloat();
|
|
1459
|
-
// Check if it's a whole number by testing if remainder is 0
|
|
1460
|
-
if (f % 1.0 == 0.0) {
|
|
1461
|
-
return i64(<i64>f).toString();
|
|
1462
|
-
}
|
|
1463
|
-
return f.toString();
|
|
1464
|
-
}
|
|
1465
|
-
if (val.isString()) {
|
|
1466
|
-
return val.asString();
|
|
1467
|
-
}
|
|
1468
|
-
// Arrays and objects fall back to a placeholder
|
|
1469
|
-
return '[object]';
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
// === Comparison Helpers ===
|
|
1473
|
-
|
|
1474
|
-
export function valuesEqual(a: Value, b: Value): bool {
|
|
1475
|
-
if (a.type != b.type) {
|
|
1476
|
-
// Allow int/float comparison
|
|
1477
|
-
if (a.isNumber() && b.isNumber()) {
|
|
1478
|
-
return a.asFloat() == b.asFloat();
|
|
1479
|
-
}
|
|
1480
|
-
return false;
|
|
1481
|
-
}
|
|
1482
|
-
if (a.isBool()) return a.boolVal == b.boolVal;
|
|
1483
|
-
if (a.type == TYPE_INT) return a.intVal == b.intVal;
|
|
1484
|
-
if (a.type == TYPE_FLOAT) return a.floatVal == b.floatVal;
|
|
1485
|
-
if (a.isString()) return a.asString() == b.asString();
|
|
1486
|
-
if (a.isNull()) return true; // null == null
|
|
1487
|
-
return false; // Arrays/objects: reference equality not supported
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
/**
|
|
1491
|
-
* Compare two values for ordering.
|
|
1492
|
-
*
|
|
1493
|
-
* Returns:
|
|
1494
|
-
* - Negative number if a < b
|
|
1495
|
-
* - Zero if a == b
|
|
1496
|
-
* - Positive number if a > b
|
|
1497
|
-
*
|
|
1498
|
-
* Comparison rules:
|
|
1499
|
-
* - Strings: Lexicographic comparison using UTF-16 code units (case-sensitive)
|
|
1500
|
-
* - Numbers: Numeric comparison (int and float are comparable)
|
|
1501
|
-
* - Booleans: false < true
|
|
1502
|
-
* - Null: Treated as less than all other values
|
|
1503
|
-
* - Mixed types: Comparisons between incompatible types return 0 (not recommended)
|
|
1504
|
-
*
|
|
1505
|
-
* Examples:
|
|
1506
|
-
* - compareValues(String("apple"), String("banana")) → negative
|
|
1507
|
-
* - compareValues(String("zebra"), String("apple")) → positive
|
|
1508
|
-
* - compareValues(String("abc"), String("abc")) → 0
|
|
1509
|
-
* - compareValues(Float(3.14), Float(2.71)) → positive
|
|
1510
|
-
* - compareValues(Bool(false), Bool(true)) → negative
|
|
1511
|
-
*
|
|
1512
|
-
* @param a - The left operand to compare
|
|
1513
|
-
* @param b - The right operand to compare
|
|
1514
|
-
* @returns Comparison result (-1, 0, or 1)
|
|
1515
|
-
*/
|
|
1516
|
-
export function compareValues(a: Value, b: Value): i32 {
|
|
1517
|
-
// Handle null - null is less than all other values
|
|
1518
|
-
if (a.isNull()) return b.isNull() ? 0 : -1;
|
|
1519
|
-
if (b.isNull()) return 1;
|
|
1520
|
-
|
|
1521
|
-
// Allow int/float comparison for numbers
|
|
1522
|
-
if (a.isNumber() && b.isNumber()) {
|
|
1523
|
-
const aNum = a.asFloat();
|
|
1524
|
-
const bNum = b.asFloat();
|
|
1525
|
-
if (aNum < bNum) return -1;
|
|
1526
|
-
if (aNum > bNum) return 1;
|
|
1527
|
-
return 0;
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
// String comparison - lexicographic, case-sensitive
|
|
1531
|
-
if (a.isString() && b.isString()) {
|
|
1532
|
-
const aStr = a.asString();
|
|
1533
|
-
const bStr = b.asString();
|
|
1534
|
-
if (aStr < bStr) return -1;
|
|
1535
|
-
if (aStr > bStr) return 1;
|
|
1536
|
-
return 0;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
// Boolean comparison
|
|
1540
|
-
if (a.isBool() && b.isBool()) {
|
|
1541
|
-
const aBool = a.asBool();
|
|
1542
|
-
const bBool = b.asBool();
|
|
1543
|
-
if (!aBool && bBool) return -1;
|
|
1544
|
-
if (aBool && !bBool) return 1;
|
|
1545
|
-
return 0;
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// Mixed types or unsupported types - treat as equal (but probably shouldn't happen)
|
|
1549
|
-
return 0;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
// === Arithmetic Operators ===
|
|
1553
|
-
|
|
1554
|
-
/**
|
|
1555
|
-
* Add two values, supporting both numeric addition and string concatenation.
|
|
1556
|
-
*
|
|
1557
|
-
* Type coercion rules:
|
|
1558
|
-
* - If either operand is a string, both are converted to strings and concatenated
|
|
1559
|
-
* - Otherwise, both operands are treated as numbers and added together
|
|
1560
|
-
*
|
|
1561
|
-
* Examples:
|
|
1562
|
-
* - "hello" + " " + "world" → "hello world"
|
|
1563
|
-
* - "count: " + 5 → "count: 5"
|
|
1564
|
-
* - 1 + 2 → 3
|
|
1565
|
-
* - true + false → 1 (true=1, false=0, numeric addition)
|
|
1566
|
-
* - null + 5 → 5 (null=0, numeric addition)
|
|
1567
|
-
* - "prefix" + null → "prefixnull" (string concatenation)
|
|
1568
|
-
*
|
|
1569
|
-
* @param a - The left operand
|
|
1570
|
-
* @param b - The right operand
|
|
1571
|
-
* @returns The sum or concatenation result
|
|
1572
|
-
*/
|
|
1573
|
-
export function addValues(a: Value, b: Value): Value {
|
|
1574
|
-
// String concatenation takes priority over numeric addition
|
|
1575
|
-
// If either operand is a string, concatenate both as strings
|
|
1576
|
-
if (a.isString() || b.isString()) {
|
|
1577
|
-
const leftStr = a.isString() ? a.asString() : valueToString(a);
|
|
1578
|
-
const rightStr = b.isString() ? b.asString() : valueToString(b);
|
|
1579
|
-
return Value.String(leftStr + rightStr);
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Numeric addition
|
|
1583
|
-
const aNum = a.isNumber() ? a.asFloat() : a.isBool() ? (a.asBool() ? 1.0 : 0.0) : 0.0;
|
|
1584
|
-
const bNum = b.isNumber() ? b.asFloat() : b.isBool() ? (b.asBool() ? 1.0 : 0.0) : 0.0;
|
|
1585
|
-
return Value.Float(aNum + bNum);
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
export function subtractValues(a: Value, b: Value): Value {
|
|
1589
|
-
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
1590
|
-
const aVal = a.asFloat();
|
|
1591
|
-
const bVal = b.asFloat();
|
|
1592
|
-
return Value.Float(aVal - bVal);
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
export function multiplyValues(a: Value, b: Value): Value {
|
|
1596
|
-
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
1597
|
-
const aVal = a.asFloat();
|
|
1598
|
-
const bVal = b.asFloat();
|
|
1599
|
-
return Value.Float(aVal * bVal);
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
export function divideValues(a: Value, b: Value): Value {
|
|
1603
|
-
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
1604
|
-
const aVal = a.asFloat();
|
|
1605
|
-
const bVal = b.asFloat();
|
|
1606
|
-
if (bVal == 0) return Value.Null(); // Division by zero
|
|
1607
|
-
return Value.Float(aVal / bVal);
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
export function moduloValues(a: Value, b: Value): Value {
|
|
1611
|
-
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
1612
|
-
const aVal = a.asFloat();
|
|
1613
|
-
const bVal = b.asFloat();
|
|
1614
|
-
if (bVal == 0) return Value.Null(); // Division by zero
|
|
1615
|
-
// Use fmod for floating point modulo
|
|
1616
|
-
const result = aVal - Math.trunc(aVal / bVal) * bVal;
|
|
1617
|
-
return Value.Float(result);
|
|
1618
|
-
}
|
|
1619
|
-
|
|
1620
|
-
export function powerValues(a: Value, b: Value): Value {
|
|
1621
|
-
if (!a.isNumber() || !b.isNumber()) return Value.Null();
|
|
1622
|
-
const aVal = a.asFloat();
|
|
1623
|
-
const bVal = b.asFloat();
|
|
1624
|
-
const result = Math.pow(aVal, bVal);
|
|
1625
|
-
return Value.Float(result);
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
export function negateValue(a: Value): Value {
|
|
1629
|
-
if (!a.isNumber()) return Value.Null();
|
|
1630
|
-
const n = a.asFloat();
|
|
1631
|
-
return Value.Float(-n);
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
/**
|
|
1635
|
-
* Get a property from an object Value.
|
|
1636
|
-
*
|
|
1637
|
-
* @param obj - The Value to get the property from (should be an object)
|
|
1638
|
-
* @param property - The property name to get
|
|
1639
|
-
* @returns The Value of the property, or Null if not found or obj is null/not an object
|
|
1640
|
-
*/
|
|
1641
|
-
export function getProperty(obj: Value, property: string): Value {
|
|
1642
|
-
if (obj.isNull()) return Value.Null();
|
|
1643
|
-
if (!obj.isObject()) return Value.Null();
|
|
1644
|
-
|
|
1645
|
-
const map = obj.asObject();
|
|
1646
|
-
if (map.has(property)) {
|
|
1647
|
-
return map.get(property);
|
|
1648
|
-
}
|
|
1649
|
-
return Value.Null();
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
/**
|
|
1653
|
-
* Get an element from an array at the specified index.
|
|
1654
|
-
*
|
|
1655
|
-
* @param arr - The Value to get the element from (should be an array)
|
|
1656
|
-
* @param idx - The index to get (must be a number Value)
|
|
1657
|
-
* @returns The Value at the index, or Null if out of bounds or arr is null/not an array
|
|
1658
|
-
*/
|
|
1659
|
-
export function getIndex(arr: Value, idx: Value): Value {
|
|
1660
|
-
if (arr.isNull()) return Value.Null();
|
|
1661
|
-
if (idx.isNull()) return Value.Null();
|
|
1662
|
-
|
|
1663
|
-
// Handle object access with string keys (e.g., baseCosts["FastTrack"])
|
|
1664
|
-
if (arr.isObject()) {
|
|
1665
|
-
if (idx.isString()) {
|
|
1666
|
-
const obj = arr.asObject();
|
|
1667
|
-
const key = idx.asString();
|
|
1668
|
-
if (obj.has(key)) {
|
|
1669
|
-
return obj.get(key);
|
|
1670
|
-
}
|
|
1671
|
-
return Value.Null();
|
|
1672
|
-
}
|
|
1673
|
-
// If idx is a number, convert to string for object key lookup
|
|
1674
|
-
// This handles cases like obj[0] where obj is {"0": value}
|
|
1675
|
-
const keyStr = idx.asString();
|
|
1676
|
-
const obj = arr.asObject();
|
|
1677
|
-
if (obj.has(keyStr)) {
|
|
1678
|
-
return obj.get(keyStr);
|
|
1679
|
-
}
|
|
1680
|
-
return Value.Null();
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
if (!arr.isArray()) return Value.Null();
|
|
1684
|
-
|
|
1685
|
-
const array = arr.asArray();
|
|
1686
|
-
const index = idx.asFloat();
|
|
1687
|
-
|
|
1688
|
-
// Handle negative indices (from end of array)
|
|
1689
|
-
let actualIndex = <i32>index;
|
|
1690
|
-
if (actualIndex < 0) {
|
|
1691
|
-
actualIndex = <i32>(array.length + actualIndex);
|
|
1692
|
-
}
|
|
1693
|
-
|
|
1694
|
-
// Check bounds
|
|
1695
|
-
if (actualIndex < 0 || actualIndex >= array.length) {
|
|
1696
|
-
return Value.Null();
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
return array[actualIndex];
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
/**
|
|
1703
|
-
* Create an interval object for range checking.
|
|
1704
|
-
*
|
|
1705
|
-
* @param start - Start value of the interval
|
|
1706
|
-
* @param end - End value of the interval
|
|
1707
|
-
* @param startInclusive - Whether the start is inclusive (true) or exclusive (false)
|
|
1708
|
-
* @param endInclusive - Whether the end is inclusive (true) or exclusive (false)
|
|
1709
|
-
* @returns An object representing the interval
|
|
1710
|
-
*/
|
|
1711
|
-
export function createInterval(
|
|
1712
|
-
start: Value,
|
|
1713
|
-
end: Value,
|
|
1714
|
-
startInclusive: bool,
|
|
1715
|
-
endInclusive: bool,
|
|
1716
|
-
): Value {
|
|
1717
|
-
const intervalMap = new Map<string, Value>();
|
|
1718
|
-
intervalMap.set('__isInterval', Value.Bool(true));
|
|
1719
|
-
intervalMap.set('start', start);
|
|
1720
|
-
intervalMap.set('end', end);
|
|
1721
|
-
intervalMap.set('startInclusive', Value.Bool(startInclusive));
|
|
1722
|
-
intervalMap.set('endInclusive', Value.Bool(endInclusive));
|
|
1723
|
-
return Value.Object(intervalMap);
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
/**
|
|
1727
|
-
* Check if a value is within an interval.
|
|
1728
|
-
*
|
|
1729
|
-
* @param needle - The value to check
|
|
1730
|
-
* @param interval - The interval object created by createInterval
|
|
1731
|
-
* @returns true if the value is within the interval, false otherwise
|
|
1732
|
-
*/
|
|
1733
|
-
function valueInInterval(needle: Value, interval: Value): bool {
|
|
1734
|
-
if (!interval.isObject()) return false;
|
|
1735
|
-
|
|
1736
|
-
const intervalObj = interval.asObject();
|
|
1737
|
-
|
|
1738
|
-
// Check if this is actually an interval object
|
|
1739
|
-
if (!intervalObj.has('__isInterval')) return false;
|
|
1740
|
-
|
|
1741
|
-
const start = intervalObj.get('start');
|
|
1742
|
-
const end = intervalObj.get('end');
|
|
1743
|
-
const startInclusive = intervalObj.get('startInclusive').asBool();
|
|
1744
|
-
const endInclusive = intervalObj.get('endInclusive').asBool();
|
|
1745
|
-
|
|
1746
|
-
const cmpStart = compareValues(needle, start);
|
|
1747
|
-
const cmpEnd = compareValues(needle, end);
|
|
1748
|
-
|
|
1749
|
-
// Check start boundary
|
|
1750
|
-
let passesStart: bool;
|
|
1751
|
-
if (startInclusive) {
|
|
1752
|
-
passesStart = cmpStart >= 0;
|
|
1753
|
-
} else {
|
|
1754
|
-
passesStart = cmpStart > 0;
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
// Check end boundary
|
|
1758
|
-
let passesEnd: bool;
|
|
1759
|
-
if (endInclusive) {
|
|
1760
|
-
passesEnd = cmpEnd <= 0;
|
|
1761
|
-
} else {
|
|
1762
|
-
passesEnd = cmpEnd < 0;
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
return passesStart && passesEnd;
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
/**
|
|
1769
|
-
* Check if a value is in an array, a substring is in a string, or a value is in an interval.
|
|
1770
|
-
* For arrays: checks if the exact value exists in the array
|
|
1771
|
-
* For strings: checks if the needle string is a substring of the haystack
|
|
1772
|
-
* For intervals: checks if the value is within the range
|
|
1773
|
-
*
|
|
1774
|
-
* @param needle - The Value or string to search for
|
|
1775
|
-
* @param haystack - The Value to search in (should be an array, string, or interval)
|
|
1776
|
-
* @returns true if found, false otherwise
|
|
1777
|
-
*/
|
|
1778
|
-
export function valueIn(needle: Value, haystack: Value): Value {
|
|
1779
|
-
if (needle.isNull() || haystack.isNull()) return Value.Bool(false);
|
|
1780
|
-
|
|
1781
|
-
// Check if haystack is an interval
|
|
1782
|
-
if (haystack.isObject()) {
|
|
1783
|
-
const obj = haystack.asObject();
|
|
1784
|
-
if (obj.has('__isInterval')) {
|
|
1785
|
-
return Value.Bool(valueInInterval(needle, haystack));
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
// Check if haystack is an array
|
|
1790
|
-
if (haystack.isArray()) {
|
|
1791
|
-
const array = haystack.asArray();
|
|
1792
|
-
for (let i = 0; i < array.length; i++) {
|
|
1793
|
-
if (valuesEqual(needle, unchecked(array[i]))) {
|
|
1794
|
-
return Value.Bool(true);
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
return Value.Bool(false);
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
// Check if haystack is a string
|
|
1801
|
-
if (haystack.isString() && needle.isString()) {
|
|
1802
|
-
const hayStr = haystack.asString();
|
|
1803
|
-
const needStr = needle.asString();
|
|
1804
|
-
if (hayStr.indexOf(needStr) >= 0) {
|
|
1805
|
-
return Value.Bool(true);
|
|
1806
|
-
}
|
|
1807
|
-
return Value.Bool(false);
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
return Value.Bool(false);
|
|
1811
|
-
}
|