@typecad/cuttlefish 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/api/board-types.d.ts +1 -0
- package/dist/api/board-types.js +4 -0
- package/dist/api/capabilities.d.ts +12 -0
- package/dist/api/capabilities.js +1 -0
- package/dist/api/config.d.ts +120 -0
- package/dist/api/config.js +7 -0
- package/dist/api/index.d.ts +5 -0
- package/dist/api/index.js +2 -0
- package/dist/api/schema/builder.d.ts +14 -0
- package/dist/api/schema/builder.js +15 -0
- package/dist/api/schema/index.d.ts +2 -0
- package/dist/api/schema/index.js +1 -0
- package/dist/api/schema/types.d.ts +257 -0
- package/dist/api/schema/types.js +4 -0
- package/dist/api/shared/async-runtime-static.d.ts +8 -0
- package/dist/api/shared/async-runtime-static.js +201 -0
- package/dist/api/shared/async-types.d.ts +42 -0
- package/dist/api/shared/async-types.js +8 -0
- package/dist/api/shared/board-resolver.d.ts +12 -0
- package/dist/api/shared/board-resolver.js +7 -0
- package/dist/api/shared/cpp-type-ir.d.ts +229 -0
- package/dist/api/shared/cpp-type-ir.js +672 -0
- package/dist/api/shared/display-adapter.d.ts +12 -0
- package/dist/api/shared/display-adapter.js +145 -0
- package/dist/api/shared/display-adapters/eink-mono.d.ts +2 -0
- package/dist/api/shared/display-adapters/eink-mono.js +53 -0
- package/dist/api/shared/display-adapters/sdl.d.ts +2 -0
- package/dist/api/shared/display-adapters/sdl.js +452 -0
- package/dist/api/shared/display-adapters/ssd1309.d.ts +2 -0
- package/dist/api/shared/display-adapters/ssd1309.js +136 -0
- package/dist/api/shared/display-adapters/st7796.d.ts +2 -0
- package/dist/api/shared/display-adapters/st7796.js +132 -0
- package/dist/api/shared/display-capabilities.d.ts +45 -0
- package/dist/api/shared/display-capabilities.js +54 -0
- package/dist/api/shared/display-op-ir.d.ts +62 -0
- package/dist/api/shared/display-op-ir.js +19 -0
- package/dist/api/shared/display-profile.d.ts +265 -0
- package/dist/api/shared/display-profile.js +338 -0
- package/dist/api/shared/graphics-strategy.d.ts +29 -0
- package/dist/api/shared/graphics-strategy.js +11 -0
- package/dist/api/shared/hal-op-ir.d.ts +393 -0
- package/dist/api/shared/hal-op-ir.js +18 -0
- package/dist/api/shared/index.d.ts +26 -0
- package/dist/api/shared/index.js +21 -0
- package/dist/api/shared/ir-core.d.ts +363 -0
- package/dist/api/shared/ir-core.js +8 -0
- package/dist/api/shared/ir-declarations.d.ts +201 -0
- package/dist/api/shared/ir-declarations.js +26 -0
- package/dist/api/shared/ir.d.ts +85 -0
- package/dist/api/shared/ir.js +13 -0
- package/dist/api/shared/platform-strategy.d.ts +402 -0
- package/dist/api/shared/platform-strategy.js +14 -0
- package/dist/api/shared/polyfill-helper-registry.d.ts +11 -0
- package/dist/api/shared/polyfill-helper-registry.js +86 -0
- package/dist/api/shared/polyfill-types.d.ts +33 -0
- package/dist/api/shared/polyfill-types.js +76 -0
- package/dist/api/shared/promise-runtime.d.ts +10 -0
- package/dist/api/shared/promise-runtime.js +244 -0
- package/dist/api/shared/snprintf-types.d.ts +29 -0
- package/dist/api/shared/snprintf-types.js +8 -0
- package/dist/api/shared/string-method-registry.d.ts +53 -0
- package/dist/api/shared/string-method-registry.js +168 -0
- package/dist/api/shared/toolchain-types.d.ts +63 -0
- package/dist/api/shared/toolchain-types.js +71 -0
- package/dist/api/shared/types.d.ts +50 -0
- package/dist/api/shared/types.js +6 -0
- package/dist/ast/parse.d.ts +2 -0
- package/dist/ast/parse.js +4 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.js +95 -0
- package/dist/cli-utils.d.ts +37 -0
- package/dist/cli-utils.js +250 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +635 -0
- package/dist/config-loader.d.ts +78 -0
- package/dist/config-loader.js +508 -0
- package/dist/config-schema.d.ts +233 -0
- package/dist/config-schema.js +83 -0
- package/dist/create/board-checklist.d.ts +2 -0
- package/dist/create/board-checklist.js +52 -0
- package/dist/create/board-codegen.d.ts +9 -0
- package/dist/create/board-codegen.js +238 -0
- package/dist/create/board-generators.d.ts +12 -0
- package/dist/create/board-generators.js +652 -0
- package/dist/create/board-spec.d.ts +640 -0
- package/dist/create/board-spec.js +215 -0
- package/dist/create/eslint-rules-template.d.ts +2 -0
- package/dist/create/eslint-rules-template.js +672 -0
- package/dist/create/index.d.ts +11 -0
- package/dist/create/index.js +8 -0
- package/dist/create/init-scaffold.d.ts +27 -0
- package/dist/create/init-scaffold.js +150 -0
- package/dist/create/init-templates.d.ts +24 -0
- package/dist/create/init-templates.js +379 -0
- package/dist/create/init-wizard.d.ts +8 -0
- package/dist/create/init-wizard.js +168 -0
- package/dist/debug/breakpoint-loader.d.ts +24 -0
- package/dist/debug/breakpoint-loader.js +137 -0
- package/dist/debug/index.d.ts +3 -0
- package/dist/debug/index.js +7 -0
- package/dist/debug/preprocessor.d.ts +9 -0
- package/dist/debug/preprocessor.js +214 -0
- package/dist/debug/types.d.ts +32 -0
- package/dist/debug/types.js +6 -0
- package/dist/diagnostics/diagnostics-report.d.ts +51 -0
- package/dist/diagnostics/diagnostics-report.js +333 -0
- package/dist/diagnostics/json-schema.d.ts +138 -0
- package/dist/diagnostics/json-schema.js +27 -0
- package/dist/diagnostics/md-writer.d.ts +10 -0
- package/dist/diagnostics/md-writer.js +336 -0
- package/dist/diagnostics/mermaid-builder.d.ts +65 -0
- package/dist/diagnostics/mermaid-builder.js +681 -0
- package/dist/emit/cpp-emitter.d.ts +6 -0
- package/dist/emit/cpp-emitter.js +48 -0
- package/dist/emit/emitters/class-emitter.d.ts +2 -0
- package/dist/emit/emitters/class-emitter.js +523 -0
- package/dist/emit/emitters/emitter-context.d.ts +174 -0
- package/dist/emit/emitters/emitter-context.js +1 -0
- package/dist/emit/emitters/entrypoint-synthesizer.d.ts +2 -0
- package/dist/emit/emitters/entrypoint-synthesizer.js +175 -0
- package/dist/emit/emitters/function-emitter-impl.d.ts +19 -0
- package/dist/emit/emitters/function-emitter-impl.js +428 -0
- package/dist/emit/emitters/line-appender.d.ts +15 -0
- package/dist/emit/emitters/line-appender.js +344 -0
- package/dist/emit/emitters/namespace-emitter.d.ts +2 -0
- package/dist/emit/emitters/namespace-emitter.js +324 -0
- package/dist/emit/emitters/output-finalizer.d.ts +4 -0
- package/dist/emit/emitters/output-finalizer.js +267 -0
- package/dist/emit/emitters/setup.d.ts +3 -0
- package/dist/emit/emitters/setup.js +851 -0
- package/dist/emit/emitters/top-level-prep.d.ts +2 -0
- package/dist/emit/emitters/top-level-prep.js +706 -0
- package/dist/emit/emitters/type-decl-emitter.d.ts +2 -0
- package/dist/emit/emitters/type-decl-emitter.js +251 -0
- package/dist/emit/emitters/ui-emitter.d.ts +21 -0
- package/dist/emit/emitters/ui-emitter.js +564 -0
- package/dist/emit/enum-emitter.d.ts +1 -0
- package/dist/emit/enum-emitter.js +1 -0
- package/dist/emit/expression-renderer.d.ts +283 -0
- package/dist/emit/expression-renderer.js +1477 -0
- package/dist/emit/native-helpers-emitter.d.ts +11 -0
- package/dist/emit/native-helpers-emitter.js +34 -0
- package/dist/emit/route-hal-op.d.ts +5 -0
- package/dist/emit/route-hal-op.js +13 -0
- package/dist/emit/snprintf-helpers.d.ts +17 -0
- package/dist/emit/snprintf-helpers.js +462 -0
- package/dist/emit/statement-renderer.d.ts +189 -0
- package/dist/emit/statement-renderer.js +948 -0
- package/dist/emit/utils/async-state-machine.d.ts +42 -0
- package/dist/emit/utils/async-state-machine.js +343 -0
- package/dist/emit/utils/comment-helpers.d.ts +20 -0
- package/dist/emit/utils/comment-helpers.js +31 -0
- package/dist/emit/utils/cpp-helpers.d.ts +47 -0
- package/dist/emit/utils/cpp-helpers.js +85 -0
- package/dist/emit/utils/include-resolver.d.ts +58 -0
- package/dist/emit/utils/include-resolver.js +155 -0
- package/dist/emit/utils/index.d.ts +8 -0
- package/dist/emit/utils/index.js +12 -0
- package/dist/emit/utils/type-inference.d.ts +159 -0
- package/dist/emit/utils/type-inference.js +559 -0
- package/dist/eslint-check.d.ts +10 -0
- package/dist/eslint-check.js +102 -0
- package/dist/framework-package.d.ts +10 -0
- package/dist/framework-package.js +58 -0
- package/dist/framework-registry.d.ts +39 -0
- package/dist/framework-registry.js +30 -0
- package/dist/incremental-cache.d.ts +116 -0
- package/dist/incremental-cache.js +335 -0
- package/dist/ir/adc-range-validation.d.ts +11 -0
- package/dist/ir/adc-range-validation.js +250 -0
- package/dist/ir/ast-node-utils.d.ts +8 -0
- package/dist/ir/ast-node-utils.js +32 -0
- package/dist/ir/board-pin-utils.d.ts +13 -0
- package/dist/ir/board-pin-utils.js +65 -0
- package/dist/ir/board-resolver.d.ts +36 -0
- package/dist/ir/board-resolver.js +437 -0
- package/dist/ir/build-ir-state.d.ts +167 -0
- package/dist/ir/build-ir-state.js +306 -0
- package/dist/ir/build-ir.d.ts +2 -0
- package/dist/ir/build-ir.js +808 -0
- package/dist/ir/call-graph.d.ts +35 -0
- package/dist/ir/call-graph.js +194 -0
- package/dist/ir/compile-time-only.d.ts +2 -0
- package/dist/ir/compile-time-only.js +16 -0
- package/dist/ir/declaration-builders.d.ts +9 -0
- package/dist/ir/declaration-builders.js +608 -0
- package/dist/ir/entry-points.d.ts +30 -0
- package/dist/ir/entry-points.js +191 -0
- package/dist/ir/expression-to-ir.d.ts +5 -0
- package/dist/ir/expression-to-ir.js +2249 -0
- package/dist/ir/extract-property-chain.d.ts +10 -0
- package/dist/ir/extract-property-chain.js +30 -0
- package/dist/ir/feature-prescan.d.ts +3 -0
- package/dist/ir/feature-prescan.js +43 -0
- package/dist/ir/feature-registry.d.ts +30 -0
- package/dist/ir/feature-registry.js +816 -0
- package/dist/ir/filter.d.ts +7 -0
- package/dist/ir/filter.js +85 -0
- package/dist/ir/function-builder.d.ts +19 -0
- package/dist/ir/function-builder.js +670 -0
- package/dist/ir/hal/hal-emitter.d.ts +62 -0
- package/dist/ir/hal/hal-emitter.js +735 -0
- package/dist/ir/hal/hal-parser.d.ts +61 -0
- package/dist/ir/hal/hal-parser.js +473 -0
- package/dist/ir/hal/hal-plugins.d.ts +34 -0
- package/dist/ir/hal/hal-plugins.js +706 -0
- package/dist/ir/hal-resolver.d.ts +5 -0
- package/dist/ir/hal-resolver.js +5 -0
- package/dist/ir/heap-analysis.d.ts +8 -0
- package/dist/ir/heap-analysis.js +350 -0
- package/dist/ir/heap-array-validation.d.ts +24 -0
- package/dist/ir/heap-array-validation.js +29 -0
- package/dist/ir/identifier-collector.d.ts +18 -0
- package/dist/ir/identifier-collector.js +439 -0
- package/dist/ir/interrupt-analysis.d.ts +39 -0
- package/dist/ir/interrupt-analysis.js +467 -0
- package/dist/ir/memory-budget-validation.d.ts +10 -0
- package/dist/ir/memory-budget-validation.js +86 -0
- package/dist/ir/namespace-builder.d.ts +7 -0
- package/dist/ir/namespace-builder.js +152 -0
- package/dist/ir/ownership-analysis.d.ts +9 -0
- package/dist/ir/ownership-analysis.js +1207 -0
- package/dist/ir/peripheral-ownership.d.ts +3 -0
- package/dist/ir/peripheral-ownership.js +152 -0
- package/dist/ir/peripheral-symbols.d.ts +7 -0
- package/dist/ir/peripheral-symbols.js +57 -0
- package/dist/ir/peripheral-usage.d.ts +54 -0
- package/dist/ir/peripheral-usage.js +691 -0
- package/dist/ir/peripheral-validation.d.ts +8 -0
- package/dist/ir/peripheral-validation.js +77 -0
- package/dist/ir/pin-alias-conflict.d.ts +4 -0
- package/dist/ir/pin-alias-conflict.js +34 -0
- package/dist/ir/pin-capability-validation.d.ts +6 -0
- package/dist/ir/pin-capability-validation.js +413 -0
- package/dist/ir/pin-mode-validation.d.ts +10 -0
- package/dist/ir/pin-mode-validation.js +301 -0
- package/dist/ir/pin-safety.d.ts +11 -0
- package/dist/ir/pin-safety.js +58 -0
- package/dist/ir/program-analysis.d.ts +44 -0
- package/dist/ir/program-analysis.js +589 -0
- package/dist/ir/pulldown-validation.d.ts +7 -0
- package/dist/ir/pulldown-validation.js +49 -0
- package/dist/ir/pwm-timer-sharing.d.ts +4 -0
- package/dist/ir/pwm-timer-sharing.js +93 -0
- package/dist/ir/reachability.d.ts +68 -0
- package/dist/ir/reachability.js +224 -0
- package/dist/ir/register-decorators.d.ts +6 -0
- package/dist/ir/register-decorators.js +49 -0
- package/dist/ir/render-expr.d.ts +4 -0
- package/dist/ir/render-expr.js +127 -0
- package/dist/ir/resource-analysis.d.ts +19 -0
- package/dist/ir/resource-analysis.js +153 -0
- package/dist/ir/statement-to-ir.d.ts +23 -0
- package/dist/ir/statement-to-ir.js +349 -0
- package/dist/ir/symbol-table.d.ts +68 -0
- package/dist/ir/symbol-table.js +179 -0
- package/dist/ir/symbol-types.d.ts +56 -0
- package/dist/ir/symbol-types.js +107 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +4 -0
- package/dist/ir/timer0-pwm-timing-conflict.js +71 -0
- package/dist/ir/timing-validation.d.ts +7 -0
- package/dist/ir/timing-validation.js +95 -0
- package/dist/ir/transformers/array-methods.d.ts +8 -0
- package/dist/ir/transformers/array-methods.js +493 -0
- package/dist/ir/transformers/call-statement.d.ts +5 -0
- package/dist/ir/transformers/call-statement.js +758 -0
- package/dist/ir/transformers/callback-context-registry.d.ts +20 -0
- package/dist/ir/transformers/callback-context-registry.js +55 -0
- package/dist/ir/transformers/canvas-lowering.d.ts +42 -0
- package/dist/ir/transformers/canvas-lowering.js +249 -0
- package/dist/ir/transformers/control-flow.d.ts +8 -0
- package/dist/ir/transformers/control-flow.js +508 -0
- package/dist/ir/transformers/expressions.d.ts +7 -0
- package/dist/ir/transformers/expressions.js +406 -0
- package/dist/ir/transformers/hal-call-resolver.d.ts +29 -0
- package/dist/ir/transformers/hal-call-resolver.js +274 -0
- package/dist/ir/transformers/hal-emit-helpers.d.ts +14 -0
- package/dist/ir/transformers/hal-emit-helpers.js +63 -0
- package/dist/ir/transformers/namespace-methods.d.ts +20 -0
- package/dist/ir/transformers/namespace-methods.js +110 -0
- package/dist/ir/transformers/register-assignment.d.ts +5 -0
- package/dist/ir/transformers/register-assignment.js +56 -0
- package/dist/ir/transformers/ui-call-resolver.d.ts +149 -0
- package/dist/ir/transformers/ui-call-resolver.js +1136 -0
- package/dist/ir/transformers/ui-callback-lowering.d.ts +62 -0
- package/dist/ir/transformers/ui-callback-lowering.js +623 -0
- package/dist/ir/transformers/ui-mount.d.ts +20 -0
- package/dist/ir/transformers/ui-mount.js +35 -0
- package/dist/ir/transformers/ui-reactive.d.ts +68 -0
- package/dist/ir/transformers/ui-reactive.js +129 -0
- package/dist/ir/transformers/variables.d.ts +14 -0
- package/dist/ir/transformers/variables.js +1045 -0
- package/dist/ir/try-catch-validation.d.ts +26 -0
- package/dist/ir/try-catch-validation.js +128 -0
- package/dist/ir/type-resolution.d.ts +31 -0
- package/dist/ir/type-resolution.js +977 -0
- package/dist/ir/ui-element-auto-wire.d.ts +50 -0
- package/dist/ir/ui-element-auto-wire.js +218 -0
- package/dist/ir/unit-suspicion-validation.d.ts +12 -0
- package/dist/ir/unit-suspicion-validation.js +296 -0
- package/dist/ir/utils/walk-ir.d.ts +6 -0
- package/dist/ir/utils/walk-ir.js +162 -0
- package/dist/ir/validation-orchestrator.d.ts +4 -0
- package/dist/ir/validation-orchestrator.js +45 -0
- package/dist/libdef/base-class-resolver.d.ts +23 -0
- package/dist/libdef/base-class-resolver.js +50 -0
- package/dist/libdef/cpp-to-decl.d.ts +34 -0
- package/dist/libdef/cpp-to-decl.js +584 -0
- package/dist/libdef/header-parser.d.ts +53 -0
- package/dist/libdef/header-parser.js +269 -0
- package/dist/libdef/registry.d.ts +10 -0
- package/dist/libdef/registry.js +142 -0
- package/dist/mapping/peripheral-names.d.ts +23 -0
- package/dist/mapping/peripheral-names.js +63 -0
- package/dist/mapping/source-map.d.ts +46 -0
- package/dist/mapping/source-map.js +207 -0
- package/dist/orchestrator/dts-generator.d.ts +17 -0
- package/dist/orchestrator/dts-generator.js +83 -0
- package/dist/orchestrator/graph-builder.d.ts +18 -0
- package/dist/orchestrator/graph-builder.js +218 -0
- package/dist/orchestrator/semantic-facts-verifier.d.ts +38 -0
- package/dist/orchestrator/semantic-facts-verifier.js +121 -0
- package/dist/orchestrator/semantic-facts.d.ts +141 -0
- package/dist/orchestrator/semantic-facts.js +652 -0
- package/dist/orchestrator/type-checker.d.ts +39 -0
- package/dist/orchestrator/type-checker.js +760 -0
- package/dist/platform/async-runtime.d.ts +8 -0
- package/dist/platform/async-runtime.js +26 -0
- package/dist/platform/generic-strategy.d.ts +101 -0
- package/dist/platform/generic-strategy.js +289 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.js +9 -0
- package/dist/platform/registry.d.ts +21 -0
- package/dist/platform/registry.js +42 -0
- package/dist/platform/toolchain.d.ts +4 -0
- package/dist/platform/toolchain.js +43 -0
- package/dist/preview/client.d.ts +1 -0
- package/dist/preview/client.js +137 -0
- package/dist/preview/server.d.ts +5 -0
- package/dist/preview/server.js +199 -0
- package/dist/profiler/index.d.ts +28 -0
- package/dist/profiler/index.js +28 -0
- package/dist/profiler/profiler.d.ts +95 -0
- package/dist/profiler/profiler.js +338 -0
- package/dist/profiler/types.d.ts +87 -0
- package/dist/profiler/types.js +12 -0
- package/dist/stores/display-profile-store.d.ts +16 -0
- package/dist/stores/display-profile-store.js +48 -0
- package/dist/stores/theme-store.d.ts +13 -0
- package/dist/stores/theme-store.js +30 -0
- package/dist/testing.d.ts +45 -0
- package/dist/testing.js +47 -0
- package/dist/transpile/resolution.d.ts +29 -0
- package/dist/transpile/resolution.js +282 -0
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +722 -0
- package/dist/types.d.ts +217 -0
- package/dist/types.js +1 -0
- package/dist/ui/ui-bridge.d.ts +4 -0
- package/dist/ui/ui-bridge.js +34 -0
- package/dist/ui-hook.d.ts +84 -0
- package/dist/ui-hook.js +45 -0
- package/dist/utils/cli.d.ts +3 -0
- package/dist/utils/cli.js +463 -0
- package/dist/utils/fs.d.ts +4 -0
- package/dist/utils/fs.js +23 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/strings.d.ts +35 -0
- package/dist/utils/strings.js +142 -0
- package/dist/utils/ui.d.ts +66 -0
- package/dist/utils/ui.js +149 -0
- package/dist/watch.d.ts +35 -0
- package/dist/watch.js +111 -0
- package/package.json +127 -0
|
@@ -0,0 +1,2249 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { makeDiagnostic, makeSourceSpan } from "./ast-node-utils.js";
|
|
3
|
+
import { PIN_FACTORY_FUNCTIONS, CONSTANT_FOLD_FUNCTIONS, TYPED_ARRAY_ELEMENT_MAP, activeCArrayVars, activeArrayLiteralVars, activeStringVars, nestedFunctionAliases, nestedClassAliases, hoistedNestedClasses, mutableArrayVars, arrayLiteralSizes, filteredArrayLengthVars, activeNamespaceNames, activeEnumNames, activeStringEnumNames, topLevelClassNames, topLevelInterfaceNames, classTypeNames, topLevelClasses, getActiveExtendsClass, restParamFunctions, getContext, getCurrentBoardConstants } from "./build-ir-state.js";
|
|
4
|
+
import { getCurrentIrTypeScope } from "./symbol-types.js";
|
|
5
|
+
import { renderExprAsText } from "./render-expr.js";
|
|
6
|
+
import { lowerStatement, tryResolveHALExpression } from "./statement-to-ir.js";
|
|
7
|
+
import { isSignalName, resolveElementValue } from "./transformers/ui-call-resolver.js";
|
|
8
|
+
import { getCanvasAmbientCtx } from "./transformers/ui-callback-lowering.js";
|
|
9
|
+
import { halInstances } from "./hal-resolver.js";
|
|
10
|
+
import { escapeCppKeyword } from "../utils/strings.js";
|
|
11
|
+
import { tryLowerRegisterRead } from "./transformers/register-assignment.js";
|
|
12
|
+
import { tryLowerArrayAndStringMethods } from "./transformers/array-methods.js";
|
|
13
|
+
import { collectReturns, inferExprCppType, typeNodeToCppType } from "./type-resolution.js";
|
|
14
|
+
import { parseCppType, elementOf, renderCppType, bareType, parsedIsPointer, parsedIsVector, parsedIsMap, parsedIsSet, parsedIsTuple, parsedIsStdString, parsedElementString, parsedBareString, isContainer } from "../api/shared/cpp-type-ir.js";
|
|
15
|
+
function resolveExprCppType(expr) {
|
|
16
|
+
if (ts.isNonNullExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
17
|
+
return resolveExprCppType(expr.expression);
|
|
18
|
+
}
|
|
19
|
+
if (ts.isIdentifier(expr)) {
|
|
20
|
+
const scope = getCurrentIrTypeScope();
|
|
21
|
+
const t = scope?.locals.get(expr.text) ?? scope?.globals.get(expr.text);
|
|
22
|
+
return t && t !== "auto" ? t : undefined;
|
|
23
|
+
}
|
|
24
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
25
|
+
if (expr.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
26
|
+
const fieldKey = `this->${expr.name.text}`;
|
|
27
|
+
const scope = getCurrentIrTypeScope();
|
|
28
|
+
const fieldType = scope?.classFields.get(fieldKey) ?? scope?.locals.get(fieldKey);
|
|
29
|
+
return fieldType && fieldType !== "auto" ? fieldType : undefined;
|
|
30
|
+
}
|
|
31
|
+
const receiverType = resolveExprCppType(expr.expression);
|
|
32
|
+
if (!receiverType)
|
|
33
|
+
return undefined;
|
|
34
|
+
const className = receiverType.replace(/\*$/, "");
|
|
35
|
+
const classDef = topLevelClasses.get(className);
|
|
36
|
+
if (!classDef)
|
|
37
|
+
return undefined;
|
|
38
|
+
const field = classDef.fields.find(f => f.name === expr.name.text);
|
|
39
|
+
return field ? field.cppType : undefined;
|
|
40
|
+
}
|
|
41
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
42
|
+
const containerType = resolveExprCppType(expr.expression);
|
|
43
|
+
if (!containerType)
|
|
44
|
+
return undefined;
|
|
45
|
+
// One structured elementOf lookup replaces the vector / __tc_StaticArray
|
|
46
|
+
// inline parsers (the latter was a hand-rolled depth counter for the
|
|
47
|
+
// first template arg).
|
|
48
|
+
const elemIr = elementOf(parseCppType(containerType));
|
|
49
|
+
if (elemIr)
|
|
50
|
+
return renderCppType(elemIr);
|
|
51
|
+
}
|
|
52
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) {
|
|
53
|
+
const receiver = expr.expression.expression;
|
|
54
|
+
const methodName = expr.expression.name.text;
|
|
55
|
+
if (ts.isIdentifier(receiver) && topLevelClasses.has(receiver.text)) {
|
|
56
|
+
return topLevelClasses.get(receiver.text)?.methods.find((method) => method.name === methodName)?.returnType;
|
|
57
|
+
}
|
|
58
|
+
const receiverType = resolveExprCppType(receiver);
|
|
59
|
+
if (!receiverType)
|
|
60
|
+
return undefined;
|
|
61
|
+
// Peel pointer via structured bareType to recover the class name.
|
|
62
|
+
const className = (() => {
|
|
63
|
+
const bare = bareType(parseCppType(receiverType));
|
|
64
|
+
return bare.kind === "named" ? bare.name : "";
|
|
65
|
+
})();
|
|
66
|
+
return topLevelClasses.get(className)?.methods.find((method) => method.name === methodName)?.returnType;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Infer the C++ element type of an array literal from its first element's
|
|
72
|
+
* resolved cppType. Used so an INLINE array literal that flows into a
|
|
73
|
+
* `__tc_*` template helper (`[...].join(sep)`, `[...].concat(x)`, ...) renders
|
|
74
|
+
* as a TYPED `std::vector<ElemType>{...}` — a bare brace-init-list cannot drive
|
|
75
|
+
* template argument deduction, but a typed temporary can.
|
|
76
|
+
*
|
|
77
|
+
* Returns the resolved cppType of the first element (e.g. `"std::string"` for
|
|
78
|
+
* `['a', 'b']`, `"int32_t"` for `[1, 2]`), or `"auto"` if no element carries a
|
|
79
|
+
* resolvable type. The `"auto"` fallback preserves the historical behavior for
|
|
80
|
+
* direct-initialization contexts (`const T x = {...}`), where a bare brace list
|
|
81
|
+
* is correct. Demo #29 Finding D.
|
|
82
|
+
*/
|
|
83
|
+
function inferArrayElementType(tsElements) {
|
|
84
|
+
for (const elem of tsElements) {
|
|
85
|
+
const t = resolveExprCppType(elem);
|
|
86
|
+
if (t && t !== "auto") {
|
|
87
|
+
// A typed element (identifier/member-access/element-access) carries its
|
|
88
|
+
// declared cppType. Use it directly.
|
|
89
|
+
return t;
|
|
90
|
+
}
|
|
91
|
+
// Literal elements: resolve their cppType from the literal kind so a
|
|
92
|
+
// numeric/string/boolean literal array also gets a typed vector.
|
|
93
|
+
if (ts.isStringLiteral(elem) || ts.isNoSubstitutionTemplateLiteral(elem)) {
|
|
94
|
+
return "std::string";
|
|
95
|
+
}
|
|
96
|
+
if (ts.isNumericLiteral(elem)) {
|
|
97
|
+
// Float-looking literal → double, else int. Mirrors inferNumericCppType.
|
|
98
|
+
return /[.eE]/.test(elem.text) ? "double" : "int";
|
|
99
|
+
}
|
|
100
|
+
if (elem.kind === ts.SyntaxKind.TrueKeyword || elem.kind === ts.SyntaxKind.FalseKeyword) {
|
|
101
|
+
return "bool";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return "auto";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Math.* constant property accesses that lower to a numeric literal rather
|
|
108
|
+
* than `std::<name>` (which doesn't exist — `std::` has no PI/E members).
|
|
109
|
+
* Using literals avoids `<cmath>`/`M_PI` `_USE_MATH_DEFINES` portability
|
|
110
|
+
* issues on Windows/MSVC. Mirrors the TS `Math` constant values.
|
|
111
|
+
*/
|
|
112
|
+
const MATH_CONSTANT_LITERALS = {
|
|
113
|
+
PI: 3.141592653589793,
|
|
114
|
+
E: 2.718281828459045,
|
|
115
|
+
LN2: 0.6931471805599453,
|
|
116
|
+
LN10: 2.302585092994046,
|
|
117
|
+
LOG2E: 1.4426950408889634,
|
|
118
|
+
LOG10E: 0.4342944819032518,
|
|
119
|
+
SQRT2: 1.4142135623730951,
|
|
120
|
+
SQRT1_2: 0.7071067811865476,
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Resolve the declared C++ return type of a call expression, for the sole
|
|
124
|
+
* purpose of the `valueType === null` null-comparison guard. Returns the type
|
|
125
|
+
* (null-stripped, e.g. `Account` for `Account | null`) or `undefined`.
|
|
126
|
+
*
|
|
127
|
+
* Handles the two inline-call forms the guard needs:
|
|
128
|
+
* - `freeFn(args)` — a top-level `function` declaration's annotated return
|
|
129
|
+
* type, looked up from the source file (the IR `functionReturnTypes` map is
|
|
130
|
+
* not reachable here).
|
|
131
|
+
* - `this.method(args)` / `expr.method(args)` — a class method's return type,
|
|
132
|
+
* resolved via the top-level class registry (methods carry `returnType`).
|
|
133
|
+
*
|
|
134
|
+
* Demo #18 Finding A: without this, `findAccount(1) === null` and
|
|
135
|
+
* `this.find(1) === null` lower to the invalid `call() == CUTTLEFISH_UNDEFINED`.
|
|
136
|
+
*/
|
|
137
|
+
function resolveCallReturnTypeForNullGuard(call, sourceText) {
|
|
138
|
+
const callee = call.expression;
|
|
139
|
+
// `this.method(...)` or `someExpr.method(...)`.
|
|
140
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
141
|
+
const methodName = callee.name.text;
|
|
142
|
+
// Resolve the receiver class. `this` could be any top-level class; look up
|
|
143
|
+
// the method name across all of them (method names are usually unique
|
|
144
|
+
// within a file). For an identifier receiver, infer its class from the
|
|
145
|
+
// active type maps.
|
|
146
|
+
let candidateClassNames = [];
|
|
147
|
+
if (callee.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
148
|
+
candidateClassNames = Array.from(topLevelClassNames);
|
|
149
|
+
}
|
|
150
|
+
else if (ts.isIdentifier(callee.expression)) {
|
|
151
|
+
const recvType = getCurrentIrTypeScope()?.locals.get(callee.expression.text) ?? getCurrentIrTypeScope()?.globals.get(callee.expression.text);
|
|
152
|
+
if (recvType)
|
|
153
|
+
candidateClassNames = [recvType.replace(/\*$/, "").replace(/^const\s+/, "")];
|
|
154
|
+
}
|
|
155
|
+
for (const className of candidateClassNames) {
|
|
156
|
+
const classDef = topLevelClasses.get(className);
|
|
157
|
+
if (classDef) {
|
|
158
|
+
const method = classDef.methods.find(m => m.name === methodName);
|
|
159
|
+
if (method)
|
|
160
|
+
return method.returnType;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
// `freeFn(...)` — scan the source file for the function declaration.
|
|
166
|
+
if (ts.isIdentifier(callee)) {
|
|
167
|
+
const fnName = callee.text;
|
|
168
|
+
const sourceFile = call.getSourceFile();
|
|
169
|
+
let declaredReturnType;
|
|
170
|
+
const visit = (node) => {
|
|
171
|
+
if (declaredReturnType)
|
|
172
|
+
return;
|
|
173
|
+
if (ts.isFunctionDeclaration(node) && node.name?.text === fnName) {
|
|
174
|
+
declaredReturnType = node.type;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
ts.forEachChild(node, visit);
|
|
178
|
+
};
|
|
179
|
+
ts.forEachChild(sourceFile, visit);
|
|
180
|
+
if (declaredReturnType) {
|
|
181
|
+
return typeNodeToCppType(declaredReturnType);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new Map()) {
|
|
188
|
+
function emitUnsupportedExpression(message) {
|
|
189
|
+
diagnostics.push(makeDiagnostic(sourceText, expr.pos, message, "error", "TS2CPP_UNSUPPORTED_EXPR"));
|
|
190
|
+
return { kind: "raw", value: "0 /* unsupported_expr */" };
|
|
191
|
+
}
|
|
192
|
+
function isOptionalChainNode(node) {
|
|
193
|
+
return !!node.questionDotToken ||
|
|
194
|
+
(typeof ts.isOptionalChain === "function" && ts.isOptionalChain(node));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Resolve .length property access to the correct C++ expression.
|
|
198
|
+
* Shared by both renderMemberAccessText and the main PropertyAccess handler
|
|
199
|
+
* to ensure consistent behavior for top-level and nested .length access.
|
|
200
|
+
*
|
|
201
|
+
* Returns the C++ text. Special case: returns the filteredArrayLengthVars
|
|
202
|
+
* variable name prefixed with "__FILTERED_LEN__" so the caller can detect it.
|
|
203
|
+
*/
|
|
204
|
+
function resolveLengthProperty(receiverNode, objectText) {
|
|
205
|
+
if (ts.isStringLiteral(receiverNode) || ts.isNoSubstitutionTemplateLiteral(receiverNode)) {
|
|
206
|
+
return `${receiverNode.text.length}`;
|
|
207
|
+
}
|
|
208
|
+
// Escape C++ keywords in identifier texts for generated C++ output
|
|
209
|
+
const safeText = ts.isIdentifier(receiverNode) ? escapeCppKeyword(objectText) : objectText;
|
|
210
|
+
if (ts.isIdentifier(receiverNode) && filteredArrayLengthVars.has(receiverNode.text)) {
|
|
211
|
+
return `__FILTERED_LEN__${filteredArrayLengthVars.get(receiverNode.text)}`;
|
|
212
|
+
}
|
|
213
|
+
// Demo #17 Finding C — activeCArrayVars is FUNCTION-SCOPED (cleared by
|
|
214
|
+
// resetFunctionScopeState per function), so it is the authoritative signal
|
|
215
|
+
// for THIS function's typed-array / raw-C-array locals. mutableArrayVars,
|
|
216
|
+
// by contrast, is FILE-SCOPED (populated by a pre-scan and survives across
|
|
217
|
+
// functions), so a name like `buf` that was mutated in fn1-fn4 stays in
|
|
218
|
+
// mutableArrayVars when a DIFFERENT `buf` (a `new Uint8Array([...])`
|
|
219
|
+
// literal) is declared in fn5. Checking activeCArrayVars FIRST prevents the
|
|
220
|
+
// stale file-level mutableArrayVars entry from forcing the function-local
|
|
221
|
+
// raw C array down the `.size()` path (avr-g++: "request for member 'size'
|
|
222
|
+
// in 'buf', which is of non-class type 'uint8_t [5]'").
|
|
223
|
+
if (ts.isIdentifier(receiverNode) && activeCArrayVars.has(receiverNode.text)) {
|
|
224
|
+
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
225
|
+
}
|
|
226
|
+
if (ts.isIdentifier(receiverNode) && mutableArrayVars.has(receiverNode.text)) {
|
|
227
|
+
// std::vector has no `.length()` member (that's std::string); use
|
|
228
|
+
// `.size()`. Cast to long long to match loop-counter type and avoid
|
|
229
|
+
// -Wsign-compare. Demo #27 Finding C — this branch (function-local
|
|
230
|
+
// mutable array) and the array-literal branch below previously emitted
|
|
231
|
+
// the invalid `vector.length()`. The `this->field` path was already
|
|
232
|
+
// corrected by demo #22 fix F; this closes the bare-local-identifier
|
|
233
|
+
// hole.
|
|
234
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
235
|
+
}
|
|
236
|
+
if (ts.isIdentifier(receiverNode) && activeArrayLiteralVars.has(receiverNode.text)) {
|
|
237
|
+
const varName = receiverNode.text;
|
|
238
|
+
const varType = getCurrentIrTypeScope()?.locals.get(varName);
|
|
239
|
+
// `.size()` is valid ONLY when the local actually lowered to something
|
|
240
|
+
// with a `.size()` METHOD — a std::vector OR a promoted
|
|
241
|
+
// __tc_StaticArray. A NON-mutated local array literal on a target that
|
|
242
|
+
// does NOT need std::vector (`!strategy.needsStdVector()`, e.g. Arduino
|
|
243
|
+
// AVR) lowers to a RAW C array (`T name[] = {...}`), even though its
|
|
244
|
+
// varType still resolves to `std::vector<...>`, and a raw C array has NO
|
|
245
|
+
// `.size()` member. The emit-side discriminator (class-emitter.ts
|
|
246
|
+
// `addCArrayIfNotMutable`) uses exactly `!needsStdVector()` to decide
|
|
247
|
+
// raw-C-array vs std::vector, so we mirror it here. Previously this
|
|
248
|
+
// branch tested only the varType prefix, so a read-only local literal
|
|
249
|
+
// emitted `name.size()` on a raw C array → avr-g++ "request for member
|
|
250
|
+
// 'size' in 'name', which is of non-class type". `mutableArrayVars`
|
|
251
|
+
// membership is the second signal: a mutated local is promoted to
|
|
252
|
+
// __tc_StaticArray (variables.ts), which DOES have `.size()`. Demo #33.
|
|
253
|
+
const emitsRawCArray = !getContext().activeStrategy?.needsStdVector();
|
|
254
|
+
const loweredToContainer = (typeof varType === 'string' && varType.startsWith('StaticArray<')) ||
|
|
255
|
+
(typeof varType === 'string' && varType.startsWith('std::vector<') && (!emitsRawCArray || mutableArrayVars.has(varName)));
|
|
256
|
+
if (loweredToContainer) {
|
|
257
|
+
// Demo #27 Finding C — `.size()` not `.length()` on a vector/StaticArray
|
|
258
|
+
// local (see the mutableArrayVars comment above).
|
|
259
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
260
|
+
}
|
|
261
|
+
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
262
|
+
}
|
|
263
|
+
if (ts.isIdentifier(receiverNode) && getCurrentIrTypeScope()?.locals.get(receiverNode.text) === "auto") {
|
|
264
|
+
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
265
|
+
}
|
|
266
|
+
if (ts.isCallExpression(receiverNode)) {
|
|
267
|
+
const returnType = ts.isPropertyAccessExpression(receiverNode.expression) && ts.isIdentifier(receiverNode.expression.expression)
|
|
268
|
+
? getCurrentIrTypeScope()?.locals.get(receiverNode.expression.expression.text)
|
|
269
|
+
: undefined;
|
|
270
|
+
if (returnType === "std::string")
|
|
271
|
+
return `static_cast<long long>(${safeText}.length())`;
|
|
272
|
+
// Cast .size() to long long to match the loop-counter type (TS number ->
|
|
273
|
+
// long long). Without this, `i < vec.size()` compares long long vs
|
|
274
|
+
// size_t (unsigned) and g++ -Wall warns -Wsign-compare on every
|
|
275
|
+
// indexed loop over an array.
|
|
276
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
277
|
+
}
|
|
278
|
+
// Resolve by concrete cppType first so std::string vars render member calls
|
|
279
|
+
// even when their resolved type is const char* (string-literal initialized).
|
|
280
|
+
if (ts.isIdentifier(receiverNode)) {
|
|
281
|
+
// locals holds function-scoped bindings; globals holds top-level
|
|
282
|
+
// (module-scope) bindings, which survive resetFunctionScopeState. A
|
|
283
|
+
// top-level `const S: int32_t[] = [...]` accessed inside a function is
|
|
284
|
+
// only in globals, so fall back to it.
|
|
285
|
+
const varType = getCurrentIrTypeScope()?.locals.get(receiverNode.text)
|
|
286
|
+
?? getCurrentIrTypeScope()?.globals.get(receiverNode.text);
|
|
287
|
+
// Demo #17 Finding B — a typed-array local (`new Uint8Array([...])` /
|
|
288
|
+
// `new Int8Array(N)`) lowers to a RAW C array whose cppType is a static
|
|
289
|
+
// array spelling (`uint8_t[5]`, `int32_t[8]`) OR a bare element pointer
|
|
290
|
+
// (`uint8_t*`). Both forms have NO `.size()` member, so `.length` must
|
|
291
|
+
// lower to sizeof. The activeCArrayVars set is the canonical signal
|
|
292
|
+
// (populated by the variable transformer for `new <TypedArray>(...)`);
|
|
293
|
+
// the cppType shape check is a belt-and-braces fallback for cases where
|
|
294
|
+
// registration was missed (e.g. initializer took a non-standard path).
|
|
295
|
+
// Without this, `.length` fell through to the default `.size()` and
|
|
296
|
+
// avr-g++ rejected it ("request for member 'size' in 'buf', which is of
|
|
297
|
+
// non-class type 'uint8_t [5]'").
|
|
298
|
+
const isRawCArrayType = typeof varType === 'string'
|
|
299
|
+
&& /\]\s*$/.test(varType); // ends with `[N]` or `[]`
|
|
300
|
+
if (activeCArrayVars.has(receiverNode.text) || isRawCArrayType) {
|
|
301
|
+
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
302
|
+
}
|
|
303
|
+
if (varType === "std::string") {
|
|
304
|
+
// Demo #30 Finding B — cast `std::string::length()` to `long long` so
|
|
305
|
+
// it matches the array/vector `.size()` lowering (also `long long`)
|
|
306
|
+
// AND the snprintf `%lld` format specifier. `std::string::length()`
|
|
307
|
+
// returns `size_type` (unsigned), which g++ -Wformat= rejects against
|
|
308
|
+
// `%d`/`%lld` and against `static_cast<long long>` only when NOT cast.
|
|
309
|
+
// Casting here makes `.length` uniform across every string/array/
|
|
310
|
+
// container receiver — one signed integral type, one format specifier.
|
|
311
|
+
return `static_cast<long long>(${safeText}.length())`;
|
|
312
|
+
}
|
|
313
|
+
// Demo #33 Finding B — a top-level `const` array literal on a target
|
|
314
|
+
// that does NOT need std::vector (`!strategy.needsStdVector()`, e.g.
|
|
315
|
+
// Arduino AVR) emits as a RAW C array (`int32_t S[] = {...}`), even
|
|
316
|
+
// though its declared type resolves to `std::vector<...>`. It is NOT in
|
|
317
|
+
// `mutableArrayVars` (only mutated locals promote to StaticArray) NOR
|
|
318
|
+
// in `activeArrayLiteralVars` (which resetFunctionScopeState clears
|
|
319
|
+
// before each function, so a top-level array isn't visible from inside
|
|
320
|
+
// a function body). So `.length` fell through to the default `.size()`
|
|
321
|
+
// — invalid for a raw C array (avr-g++: "request for member 'size' in
|
|
322
|
+
// 'S', which is of non-class type"). The emit-side discriminator
|
|
323
|
+
// (class-emitter.ts `addCArrayIfNotMutable`) uses `!needsStdVector()`,
|
|
324
|
+
// mirrored here: a top-level const array on a no-std::vector target is
|
|
325
|
+
// a raw C array → sizeof. On native/generic (`needsStdVector()` true)
|
|
326
|
+
// it really is a std::vector → .size(). The function-local path is
|
|
327
|
+
// handled by the activeArrayLiteralVars branch above (FIX-4).
|
|
328
|
+
if (typeof varType === 'string' && varType.startsWith('std::vector<')) {
|
|
329
|
+
const emitsRawCArray = !getContext().activeStrategy?.needsStdVector();
|
|
330
|
+
const isTopLevel = !getCurrentIrTypeScope()?.locals.has(receiverNode.text)
|
|
331
|
+
&& getCurrentIrTypeScope()?.globals.has(receiverNode.text);
|
|
332
|
+
if (emitsRawCArray && isTopLevel) {
|
|
333
|
+
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
334
|
+
}
|
|
335
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
336
|
+
}
|
|
337
|
+
if (varType === "const char*" || varType === "char*") {
|
|
338
|
+
return `strlen(${safeText})`;
|
|
339
|
+
}
|
|
340
|
+
// Unresolved C-string variables (resolved const char*/char*/__tc_str_ptr) → strlen()
|
|
341
|
+
if (activeStringVars.has(receiverNode.text)) {
|
|
342
|
+
return `strlen(${safeText})`;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Handle this->field.length — route by the field's resolved C++ type. Every
|
|
346
|
+
// STL container exposes .size(), so vector/map/set all lower correctly; only
|
|
347
|
+
// a C-string field lowers to strlen. Previously a std::map/std::set field
|
|
348
|
+
// fell through to the strlen default (the field type didn't match the
|
|
349
|
+
// std::vector/StaticArray prefix), emitting strlen(this->m) on a struct —
|
|
350
|
+
// invalid C++ that g++ rejects (or, worse, silently miscompiles). Demo #22
|
|
351
|
+
// adjacency probe surfaced this pre-existing bug.
|
|
352
|
+
if (ts.isPropertyAccessExpression(receiverNode) && receiverNode.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
353
|
+
const fieldType = getCurrentIrTypeScope()?.locals.get(`this->${receiverNode.name.text}`);
|
|
354
|
+
if (fieldType === "std::string")
|
|
355
|
+
return `static_cast<long long>(${safeText}.length())`;
|
|
356
|
+
if (fieldType === "const char*" || fieldType === "char*")
|
|
357
|
+
return `strlen(${safeText})`;
|
|
358
|
+
// Container-like field (.size() applies). Covers std::vector, std::map,
|
|
359
|
+
// std::set, and both __tc_StaticArray<T,N> and the bare StaticArray<T,N>
|
|
360
|
+
// spelling (the latter parses as a named template, so check explicitly).
|
|
361
|
+
if (fieldType) {
|
|
362
|
+
const ir = parseCppType(fieldType);
|
|
363
|
+
const isBareStaticArray = ir.kind === "named" && ir.name === "StaticArray";
|
|
364
|
+
if (isContainer(ir) || isBareStaticArray) {
|
|
365
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Unknown field type — .size() is valid on every STL container and on
|
|
369
|
+
// std::string, so default to it rather than the C-string strlen (which is
|
|
370
|
+
// only correct for const char*). This matches the bare-identifier
|
|
371
|
+
// fallback at the end of this function.
|
|
372
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
373
|
+
}
|
|
374
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
375
|
+
}
|
|
376
|
+
function renderMemberAccessText(receiverNode, memberName) {
|
|
377
|
+
const escapedName = escapeCppKeyword(memberName);
|
|
378
|
+
const isThisAccess = receiverNode.kind === ts.SyntaxKind.ThisKeyword ||
|
|
379
|
+
(ts.isIdentifier(receiverNode) && receiverNode.text === "this");
|
|
380
|
+
if (isThisAccess) {
|
|
381
|
+
if (memberName === "length") {
|
|
382
|
+
return `static_cast<long long>(this->size())`;
|
|
383
|
+
}
|
|
384
|
+
return `this->${escapedName}`;
|
|
385
|
+
}
|
|
386
|
+
if (ts.isIdentifier(receiverNode) && pointerVars.has(receiverNode.text)) {
|
|
387
|
+
return `${receiverNode.text}->${escapedName}`;
|
|
388
|
+
}
|
|
389
|
+
if (ts.isIdentifier(receiverNode) && receiverNode.text === "Math") {
|
|
390
|
+
if (memberName === "random")
|
|
391
|
+
return `__tc_random()`;
|
|
392
|
+
const mathConst = MATH_CONSTANT_LITERALS[memberName];
|
|
393
|
+
if (mathConst !== undefined)
|
|
394
|
+
return String(mathConst);
|
|
395
|
+
return `std::${escapedName}`;
|
|
396
|
+
}
|
|
397
|
+
if (ts.isIdentifier(receiverNode) && activeNamespaceNames.has(receiverNode.text)) {
|
|
398
|
+
return `${receiverNode.text}::${escapedName}`;
|
|
399
|
+
}
|
|
400
|
+
const objectText = formatExpressionText(receiverNode);
|
|
401
|
+
// Detect if receiver is a method call that returns a pointer (for chaining)
|
|
402
|
+
if (ts.isCallExpression(receiverNode) && ts.isPropertyAccessExpression(receiverNode.expression)) {
|
|
403
|
+
const innerReceiver = receiverNode.expression.expression;
|
|
404
|
+
const innerMethodName = receiverNode.expression.name.text;
|
|
405
|
+
if (ts.isIdentifier(innerReceiver) && pointerVars.has(innerReceiver.text)) {
|
|
406
|
+
let className = pointerVars.get(innerReceiver.text);
|
|
407
|
+
className = nestedClassAliases.get(className) ?? className;
|
|
408
|
+
const cls = hoistedNestedClasses.find(c => c.name === className);
|
|
409
|
+
const chainMethod = cls?.methods.find(m => m.name === innerMethodName);
|
|
410
|
+
if (chainMethod && parsedIsPointer(chainMethod.returnType)) {
|
|
411
|
+
return `${objectText}->${escapedName}`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (memberName === "length" || memberName === "size") {
|
|
416
|
+
const resolved = resolveLengthProperty(receiverNode, objectText);
|
|
417
|
+
// Filtered length vars use a special prefix — extract the variable name
|
|
418
|
+
if (resolved.startsWith("__FILTERED_LEN__")) {
|
|
419
|
+
return resolved.slice("__FILTERED_LEN__".length);
|
|
420
|
+
}
|
|
421
|
+
return resolved;
|
|
422
|
+
}
|
|
423
|
+
return `${objectText}.${escapedName}`;
|
|
424
|
+
}
|
|
425
|
+
function renderOptionalGuardedAccess(receiverNode, accessText) {
|
|
426
|
+
const receiverText = formatExpressionText(receiverNode);
|
|
427
|
+
return `(cuttlefish_exists(${receiverText}) ? ${accessText} : 0)`;
|
|
428
|
+
}
|
|
429
|
+
const formatExpressionText = (node) => {
|
|
430
|
+
if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) {
|
|
431
|
+
return formatExpressionText(node.expression);
|
|
432
|
+
}
|
|
433
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
434
|
+
return `(${formatExpressionText(node.expression)})`;
|
|
435
|
+
}
|
|
436
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
437
|
+
const accessText = renderMemberAccessText(node.expression, node.name.text);
|
|
438
|
+
if (isOptionalChainNode(node)) {
|
|
439
|
+
return renderOptionalGuardedAccess(node.expression, accessText);
|
|
440
|
+
}
|
|
441
|
+
return accessText;
|
|
442
|
+
}
|
|
443
|
+
if (ts.isElementAccessExpression(node)) {
|
|
444
|
+
const objectText = formatExpressionText(node.expression);
|
|
445
|
+
const argumentText = node.argumentExpression ? formatExpressionText(node.argumentExpression) : "0";
|
|
446
|
+
return `${objectText}[${argumentText}]`;
|
|
447
|
+
}
|
|
448
|
+
if (ts.isCallExpression(node)) {
|
|
449
|
+
const argsText = node.arguments.map((arg) => formatExpressionText(arg)).join(", ");
|
|
450
|
+
if (isOptionalChainNode(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
451
|
+
const calleeText = renderMemberAccessText(node.expression.expression, node.expression.name.text);
|
|
452
|
+
return renderOptionalGuardedAccess(node.expression.expression, `${calleeText}(${argsText})`);
|
|
453
|
+
}
|
|
454
|
+
const calleeText = formatExpressionText(node.expression);
|
|
455
|
+
return `${calleeText}(${argsText})`;
|
|
456
|
+
}
|
|
457
|
+
if (ts.isPrefixUnaryExpression(node)) {
|
|
458
|
+
return `${node.operator === ts.SyntaxKind.PlusPlusToken ? "++" : node.operator === ts.SyntaxKind.MinusMinusToken ? "--" : ts.tokenToString(node.operator) ?? ""}${formatExpressionText(node.operand)}`;
|
|
459
|
+
}
|
|
460
|
+
if (ts.isBinaryExpression(node)) {
|
|
461
|
+
let operator = ts.tokenToString(node.operatorToken.kind) ?? node.operatorToken.getText();
|
|
462
|
+
if (operator === "===") {
|
|
463
|
+
operator = "==";
|
|
464
|
+
}
|
|
465
|
+
else if (operator === "!==") {
|
|
466
|
+
operator = "!=";
|
|
467
|
+
}
|
|
468
|
+
else if (operator === "??") {
|
|
469
|
+
// Nullish coalescing must not use truthiness semantics because 0/false
|
|
470
|
+
// are valid values in TypeScript. Emit a helper call instead.
|
|
471
|
+
const left = formatExpressionText(node.left);
|
|
472
|
+
const right = formatExpressionText(node.right);
|
|
473
|
+
return `cuttlefish_nullish(${left}, ${right})`;
|
|
474
|
+
}
|
|
475
|
+
else if (operator === "**") {
|
|
476
|
+
// C++ has no ** operator — translate to pow()
|
|
477
|
+
const left = formatExpressionText(node.left);
|
|
478
|
+
const right = formatExpressionText(node.right);
|
|
479
|
+
return `pow(${left}, ${right})`;
|
|
480
|
+
}
|
|
481
|
+
return `${formatExpressionText(node.left)} ${operator} ${formatExpressionText(node.right)}`;
|
|
482
|
+
}
|
|
483
|
+
return node.getText();
|
|
484
|
+
};
|
|
485
|
+
if (ts.isNumericLiteral(expr)) {
|
|
486
|
+
const numValue = Number(expr.text);
|
|
487
|
+
// Use original source text to detect float literals — TypeScript normalizes "2.0" to "2" in expr.text
|
|
488
|
+
const originalText = sourceText.substring(expr.pos, expr.end).trim();
|
|
489
|
+
const isFloat = /[.eE]/.test(originalText);
|
|
490
|
+
return { kind: "number", value: numValue, ...(isFloat ? { cppType: "double" } : {}) };
|
|
491
|
+
}
|
|
492
|
+
if (expr.kind === ts.SyntaxKind.RegularExpressionLiteral) {
|
|
493
|
+
const regexText = expr.getText();
|
|
494
|
+
return { kind: "raw", value: `std::regex(${JSON.stringify(regexText)})` };
|
|
495
|
+
}
|
|
496
|
+
if (ts.isAsExpression(expr) || ts.isTypeAssertionExpression(expr)) {
|
|
497
|
+
const inner = expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
498
|
+
// When the cast involves an `enum class` on one side and an integral type
|
|
499
|
+
// on the other, emit a `static_cast<T>(...)` instead of erasing. C++ enum
|
|
500
|
+
// class has NO implicit conversion, so the erased cast produces invalid
|
|
501
|
+
// C++. The SUPPORT_MATRIX handles this for specific sites (relational,
|
|
502
|
+
// index, Map key, storage boundary) but NOT for the general `as` cast —
|
|
503
|
+
// the user's explicit escape hatch (enum stress test Finding C).
|
|
504
|
+
const targetTypeName = expr.type && ts.isTypeReferenceNode(expr.type) && ts.isIdentifier(expr.type.typeName)
|
|
505
|
+
? expr.type.typeName.text : "";
|
|
506
|
+
const isTargetEnum = targetTypeName && activeEnumNames.has(targetTypeName) && !activeStringEnumNames.has(targetTypeName);
|
|
507
|
+
if (isTargetEnum) {
|
|
508
|
+
// int → enum: static_cast<EnumType>(value)
|
|
509
|
+
return { kind: "raw", value: `static_cast<${targetTypeName}>(${renderExprAsText(inner)})` };
|
|
510
|
+
}
|
|
511
|
+
// enum → int: detect if the source expression is an enum-typed identifier
|
|
512
|
+
// or enum member access, and the target is an integral C++ type.
|
|
513
|
+
const isIntegralTarget = targetTypeName && /^(int|long|short|char|double|float|uint|int8|int16|int32|int64|size_t)/.test(targetTypeName);
|
|
514
|
+
if (isIntegralTarget) {
|
|
515
|
+
// Check if the inner expression is an enum-typed value. An identifier
|
|
516
|
+
// may be an enum-typed VARIABLE (param, local) — resolve its type from
|
|
517
|
+
// the IR type scope's locals/globals. An enum member access
|
|
518
|
+
// (Mode.Run) resolves via the enum name on the object.
|
|
519
|
+
const innerExpr = expr.expression;
|
|
520
|
+
let sourceEnumName = "";
|
|
521
|
+
if (ts.isIdentifier(innerExpr)) {
|
|
522
|
+
// Direct enum name (e.g. `Mode` used as a value).
|
|
523
|
+
if (activeEnumNames.has(innerExpr.text) && !activeStringEnumNames.has(innerExpr.text)) {
|
|
524
|
+
sourceEnumName = innerExpr.text;
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
// Variable whose type is an enum — resolve from locals/globals.
|
|
528
|
+
const varType = getCurrentIrTypeScope()?.locals.get(innerExpr.text)
|
|
529
|
+
?? getCurrentIrTypeScope()?.globals.get(innerExpr.text);
|
|
530
|
+
if (varType && activeEnumNames.has(varType) && !activeStringEnumNames.has(varType)) {
|
|
531
|
+
sourceEnumName = varType;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
else if (ts.isPropertyAccessExpression(innerExpr) && ts.isIdentifier(innerExpr.expression)
|
|
536
|
+
&& activeEnumNames.has(innerExpr.expression.text) && !activeStringEnumNames.has(innerExpr.expression.text)) {
|
|
537
|
+
sourceEnumName = innerExpr.expression.text;
|
|
538
|
+
}
|
|
539
|
+
if (sourceEnumName) {
|
|
540
|
+
return { kind: "raw", value: `static_cast<${targetTypeName}>(${renderExprAsText(inner)})` };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
return inner;
|
|
544
|
+
}
|
|
545
|
+
if (ts.isAwaitExpression(expr)) {
|
|
546
|
+
return {
|
|
547
|
+
kind: "await",
|
|
548
|
+
value: expressionToIR(expr.expression, sourceText, diagnostics, pointerVars),
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (expr.kind === ts.SyntaxKind.VoidExpression) {
|
|
552
|
+
const voidExpr = expr;
|
|
553
|
+
const inner = expressionToIR(voidExpr.expression, sourceText, diagnostics, pointerVars);
|
|
554
|
+
const innerText = renderExprAsText(inner);
|
|
555
|
+
return { kind: "raw", value: `(void)(${innerText}), CUTTLEFISH_UNDEFINED` };
|
|
556
|
+
}
|
|
557
|
+
// Handle 'as const' and other type assertions - unwrap and process inner expression
|
|
558
|
+
if (ts.isAsExpression(expr)) {
|
|
559
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
560
|
+
}
|
|
561
|
+
// Handle type assertions like (<Type>expr) - unwrap and process inner expression
|
|
562
|
+
if (ts.isTypeAssertionExpression(expr)) {
|
|
563
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
564
|
+
}
|
|
565
|
+
// Handle 'satisfies' expressions (TS 4.9+) - unwrap and process inner expression
|
|
566
|
+
if (ts.isSatisfiesExpression?.(expr)) {
|
|
567
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
568
|
+
}
|
|
569
|
+
if (expr.kind === ts.SyntaxKind.SatisfiesExpression) {
|
|
570
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
571
|
+
}
|
|
572
|
+
// Handle instanceof expressions (must be before generic binary expression handling)
|
|
573
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
|
574
|
+
const object = expressionToIR(expr.left, sourceText, diagnostics);
|
|
575
|
+
const className = expr.right.getText();
|
|
576
|
+
return { kind: "instanceof", object, className };
|
|
577
|
+
}
|
|
578
|
+
// Handle 'in' operator: "key" in obj → map.count() or vector find
|
|
579
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.InKeyword) {
|
|
580
|
+
const left = renderExprAsText(expressionToIR(expr.left, sourceText, diagnostics, pointerVars));
|
|
581
|
+
const rightNode = expr.right;
|
|
582
|
+
let rightVarType;
|
|
583
|
+
if (ts.isIdentifier(rightNode)) {
|
|
584
|
+
rightVarType = getCurrentIrTypeScope()?.locals.get(rightNode.text) ?? getCurrentIrTypeScope()?.globals.get(rightNode.text);
|
|
585
|
+
}
|
|
586
|
+
if (rightVarType && parsedIsMap(rightVarType)) {
|
|
587
|
+
return { kind: "raw", value: `(${rightNode.getText()}.count(${left}) > 0)` };
|
|
588
|
+
}
|
|
589
|
+
if (rightVarType && parsedIsVector(rightVarType)) {
|
|
590
|
+
return { kind: "raw", value: `(std::find(${rightNode.getText()}.begin(), ${rightNode.getText()}.end(), ${left}) != ${rightNode.getText()}.end())` };
|
|
591
|
+
}
|
|
592
|
+
if (rightVarType && parsedIsSet(rightVarType)) {
|
|
593
|
+
return { kind: "raw", value: `(${rightNode.getText()}.count(${left}) > 0)` };
|
|
594
|
+
}
|
|
595
|
+
return { kind: "raw", value: `(std::find(${rightNode.getText()}.begin(), ${rightNode.getText()}.end(), ${left}) != ${rightNode.getText()}.end())` };
|
|
596
|
+
}
|
|
597
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
|
|
598
|
+
const leftText = renderExprAsText(expressionToIR(expr.left, sourceText, diagnostics, pointerVars));
|
|
599
|
+
const rightText = renderExprAsText(expressionToIR(expr.right, sourceText, diagnostics, pointerVars));
|
|
600
|
+
return { kind: "raw", value: `static_cast<unsigned int>(static_cast<unsigned int>(${leftText}) >> ${rightText})` };
|
|
601
|
+
}
|
|
602
|
+
// Detect string-bearing + chains and fold them into string_concat IR so they
|
|
603
|
+
// flow through the same snprintf / std::string pipeline that template literals use.
|
|
604
|
+
const STRING_RETURNING_METHODS = new Set([
|
|
605
|
+
'toUpperCase', 'toLowerCase', 'trim', 'replace',
|
|
606
|
+
'charAt', 'substring', 'slice', 'endsWith', 'includes', 'toString',
|
|
607
|
+
]);
|
|
608
|
+
const ALL_STRING_METHODS = new Set([
|
|
609
|
+
'toUpperCase', 'toLowerCase', 'trim', 'replace', 'charAt', 'charCodeAt',
|
|
610
|
+
'substring', 'slice', 'endsWith', 'startsWith', 'includes', 'indexOf',
|
|
611
|
+
'lastIndexOf', 'padStart', 'padEnd', 'repeat', 'split', 'toString',
|
|
612
|
+
]);
|
|
613
|
+
function isStringBearingConcatChain(e) {
|
|
614
|
+
if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e) || ts.isTemplateExpression(e))
|
|
615
|
+
return true;
|
|
616
|
+
if (ts.isIdentifier(e)) {
|
|
617
|
+
if (activeStringVars.has(e.text) || getCurrentIrTypeScope()?.locals.get(e.text) === "std::string")
|
|
618
|
+
return true;
|
|
619
|
+
const varType = getCurrentIrTypeScope()?.locals.get(e.text) ?? getCurrentIrTypeScope()?.globals.get(e.text);
|
|
620
|
+
if (varType === "const char*" || varType === "char*")
|
|
621
|
+
return true;
|
|
622
|
+
// A variable whose declared type is a string enum holds a const char*.
|
|
623
|
+
if (varType && activeStringEnumNames.has(varType))
|
|
624
|
+
return true;
|
|
625
|
+
if (process.env.CF_ENUM_DEBUG && e.text === "c") {
|
|
626
|
+
console.error("[CF_ENUM_DEBUG] c varType=", JSON.stringify(varType), "stringEnums=", JSON.stringify([...activeStringEnumNames]));
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (ts.isParenthesizedExpression(e)) {
|
|
630
|
+
return isStringBearingConcatChain(e.expression);
|
|
631
|
+
}
|
|
632
|
+
if (ts.isConditionalExpression(e)) {
|
|
633
|
+
return isStringBearingConcatChain(e.whenTrue) || isStringBearingConcatChain(e.whenFalse);
|
|
634
|
+
}
|
|
635
|
+
if (ts.isPropertyAccessExpression(e)) {
|
|
636
|
+
if (e.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
637
|
+
const fieldType = getCurrentIrTypeScope()?.locals.get(`this->${e.name.text}`);
|
|
638
|
+
if (fieldType === "std::string" || fieldType === "const char*" || fieldType === "char*")
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (ts.isIdentifier(e.expression)) {
|
|
642
|
+
const objType = getCurrentIrTypeScope()?.locals.get(e.expression.text) ?? getCurrentIrTypeScope()?.globals.get(e.expression.text);
|
|
643
|
+
if (objType) {
|
|
644
|
+
const className = objType.replace(/\*$/, "");
|
|
645
|
+
const classDef = topLevelClasses.get(className);
|
|
646
|
+
if (classDef) {
|
|
647
|
+
const field = classDef.fields.find(f => f.name === e.name.text);
|
|
648
|
+
if (field) {
|
|
649
|
+
const ft = field.cppType;
|
|
650
|
+
if (ft === "std::string" || ft === "const char*" || ft === "char*")
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
// Handle chained property access: s.player.weaponName where the
|
|
657
|
+
// receiver is itself a property access (e.g. local pointer alias).
|
|
658
|
+
if (ts.isPropertyAccessExpression(e.expression)) {
|
|
659
|
+
const receiverType = resolveExprCppType(e.expression);
|
|
660
|
+
if (receiverType) {
|
|
661
|
+
const className = receiverType.replace(/\*$/, "");
|
|
662
|
+
const classDef = topLevelClasses.get(className);
|
|
663
|
+
if (classDef) {
|
|
664
|
+
const field = classDef.fields.find(f => f.name === e.name.text);
|
|
665
|
+
if (field) {
|
|
666
|
+
const ft = field.cppType;
|
|
667
|
+
if (ft === "std::string" || ft === "const char*" || ft === "char*")
|
|
668
|
+
return true;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (ts.isCallExpression(e)) {
|
|
675
|
+
const callType = resolveExprCppType(e);
|
|
676
|
+
if (callType === "std::string" || callType === "const char*" || callType === "char*")
|
|
677
|
+
return true;
|
|
678
|
+
if (ts.isPropertyAccessExpression(e.expression)) {
|
|
679
|
+
const methodName = e.expression.name.text;
|
|
680
|
+
if (STRING_RETURNING_METHODS.has(methodName))
|
|
681
|
+
return true;
|
|
682
|
+
if (ts.isIdentifier(e.expression.expression)) {
|
|
683
|
+
const receiverType = getCurrentIrTypeScope()?.locals.get(e.expression.expression.text);
|
|
684
|
+
if (receiverType === "std::string" || receiverType === "const char*" || activeStringVars.has(e.expression.expression.text))
|
|
685
|
+
return true;
|
|
686
|
+
}
|
|
687
|
+
if (ts.isPropertyAccessExpression(e.expression.expression) && e.expression.expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
688
|
+
const fieldType = getCurrentIrTypeScope()?.locals.get(`this->${e.expression.expression.name.text}`);
|
|
689
|
+
if (fieldType === "std::string" || fieldType === "const char*")
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (ts.isIdentifier(e.expression)) {
|
|
694
|
+
const fnName = e.expression.text;
|
|
695
|
+
const returnType = getCurrentIrTypeScope()?.locals.get(`fn:${fnName}`) ?? getCurrentIrTypeScope()?.globals.get(`fn:${fnName}`);
|
|
696
|
+
if (returnType === "std::string" || returnType === "const char*")
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (ts.isElementAccessExpression(e) && ts.isIdentifier(e.expression)) {
|
|
701
|
+
const arrType = getCurrentIrTypeScope()?.locals.get(e.expression.text) ?? getCurrentIrTypeScope()?.globals.get(e.expression.text);
|
|
702
|
+
if (arrType) {
|
|
703
|
+
// Element type of a vector, checked for string-ness.
|
|
704
|
+
const elemStr = parsedElementString(arrType);
|
|
705
|
+
if (elemStr && (elemStr === "std::string" || elemStr === "const char*"))
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
if (ts.isBinaryExpression(e) &&
|
|
710
|
+
e.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
711
|
+
return isStringBearingConcatChain(e.left) || isStringBearingConcatChain(e.right);
|
|
712
|
+
}
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
function flattenStringConcatParts(e) {
|
|
716
|
+
if (ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e)) {
|
|
717
|
+
return e.text ? [{ kind: "string", value: e.text }] : [];
|
|
718
|
+
}
|
|
719
|
+
if (ts.isTemplateExpression(e)) {
|
|
720
|
+
const nested = expressionToIR(e, sourceText, diagnostics, pointerVars);
|
|
721
|
+
return nested.kind === "string_concat" ? nested.parts : [nested];
|
|
722
|
+
}
|
|
723
|
+
if (ts.isBinaryExpression(e) &&
|
|
724
|
+
e.operatorToken.kind === ts.SyntaxKind.PlusToken &&
|
|
725
|
+
isStringBearingConcatChain(e)) {
|
|
726
|
+
return [
|
|
727
|
+
...flattenStringConcatParts(e.left),
|
|
728
|
+
...flattenStringConcatParts(e.right),
|
|
729
|
+
];
|
|
730
|
+
}
|
|
731
|
+
return [{ kind: "template_string", expression: expressionToIR(e, sourceText, diagnostics, pointerVars) }];
|
|
732
|
+
}
|
|
733
|
+
// Route string-bearing + chains into string_concat IR (same path as template literals).
|
|
734
|
+
if (ts.isBinaryExpression(expr) &&
|
|
735
|
+
expr.operatorToken.kind === ts.SyntaxKind.PlusToken &&
|
|
736
|
+
isStringBearingConcatChain(expr)) {
|
|
737
|
+
const parts = flattenStringConcatParts(expr);
|
|
738
|
+
if (parts.length === 1)
|
|
739
|
+
return parts[0];
|
|
740
|
+
return { kind: "string_concat", parts };
|
|
741
|
+
}
|
|
742
|
+
// Nullish coalescing: use a helper instead of truthiness so that
|
|
743
|
+
// values like 0 and false are preserved correctly.
|
|
744
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
745
|
+
const left = renderExprAsText(expressionToIR(expr.left, sourceText, diagnostics, pointerVars));
|
|
746
|
+
const right = renderExprAsText(expressionToIR(expr.right, sourceText, diagnostics, pointerVars));
|
|
747
|
+
return { kind: "raw", value: `cuttlefish_nullish(${left}, ${right})` };
|
|
748
|
+
}
|
|
749
|
+
// Detect type guard pattern: typeof x == "literal" or typeof x != "literal"
|
|
750
|
+
// where x has a known type. Emit a compile-time constant to avoid dead code.
|
|
751
|
+
if (ts.isBinaryExpression(expr) && (expr.operatorToken.kind === ts.SyntaxKind.EqualsToken || expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken || expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken)) {
|
|
752
|
+
const leftIsTypeof = ts.isTypeOfExpression(expr.left);
|
|
753
|
+
const rightIsString = ts.isStringLiteral(expr.right);
|
|
754
|
+
if (leftIsTypeof && rightIsString) {
|
|
755
|
+
const typeofOperand = expr.left.expression;
|
|
756
|
+
if (ts.isIdentifier(typeofOperand)) {
|
|
757
|
+
const varType = getCurrentIrTypeScope()?.locals.get(typeofOperand.text);
|
|
758
|
+
const expectedTypeName = expr.right.text;
|
|
759
|
+
const actualTypeName = varType === "int" || varType === "float" || varType === "double" || varType === "long" || varType === "long long" || varType === "unsigned long long" || varType === "unsigned" || varType === "size_t"
|
|
760
|
+
? "number"
|
|
761
|
+
: varType === "bool"
|
|
762
|
+
? "boolean"
|
|
763
|
+
: varType === "std::string"
|
|
764
|
+
? "string"
|
|
765
|
+
: varType === "void"
|
|
766
|
+
? "undefined"
|
|
767
|
+
: "object";
|
|
768
|
+
const isEquality = expr.operatorToken.kind === ts.SyntaxKind.EqualsToken || expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken;
|
|
769
|
+
const matches = actualTypeName === expectedTypeName;
|
|
770
|
+
return { kind: "boolean", value: isEquality ? matches : !matches };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Handle `valueType === null` / `!== null` / `=== undefined` on a value type.
|
|
775
|
+
// `T | null` erases to a value type (vector/map/set/struct), which is never
|
|
776
|
+
// null in C++, so comparing it to nullptr is invalid (`no match for
|
|
777
|
+
// operator==`). Resolve to a compile-time boolean. Demo #9 Finding D.
|
|
778
|
+
//
|
|
779
|
+
// A value type here is any non-pointer C++ type: the STL containers, strings,
|
|
780
|
+
// and — critically — *interface names*, because an `interface Foo` lowers to
|
|
781
|
+
// a C++ `struct Foo` (a value), not a pointer. Classes are always reference
|
|
782
|
+
// types (pointers), so they are excluded by the `!parsedIsPointer(vt)` filter
|
|
783
|
+
// (and by not being in topLevelInterfaceNames). Demo #18 Finding A: a struct
|
|
784
|
+
// returned from a function/method and stored in a local (`let s: Account |
|
|
785
|
+
// null = find(); s === null`) is now recognised as a value type and lowers to
|
|
786
|
+
// a compile-time `false` instead of the invalid `s == CUTTLEFISH_UNDEFINED`.
|
|
787
|
+
if (ts.isBinaryExpression(expr)
|
|
788
|
+
&& (expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken
|
|
789
|
+
|| expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken
|
|
790
|
+
|| expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken
|
|
791
|
+
|| expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken)) {
|
|
792
|
+
const isNullRhs = expr.right.kind === ts.SyntaxKind.NullKeyword || expr.right.kind === ts.SyntaxKind.UndefinedKeyword;
|
|
793
|
+
const isNullLhs = expr.left.kind === ts.SyntaxKind.NullKeyword || expr.left.kind === ts.SyntaxKind.UndefinedKeyword;
|
|
794
|
+
if (isNullRhs || isNullLhs) {
|
|
795
|
+
const valueNode = isNullRhs ? expr.left : expr.right;
|
|
796
|
+
// Resolve the operand's C++ type. Identifiers read it from the active
|
|
797
|
+
// local/global type maps; an inline call (`findAccount(1) === null` or
|
|
798
|
+
// `this.find(1) === null`) is resolved from the callee's declared return
|
|
799
|
+
// type — see resolveCallReturnTypeForNullGuard below.
|
|
800
|
+
let vt;
|
|
801
|
+
if (ts.isIdentifier(valueNode)) {
|
|
802
|
+
vt = getCurrentIrTypeScope()?.locals.get(valueNode.text) ?? getCurrentIrTypeScope()?.globals.get(valueNode.text);
|
|
803
|
+
}
|
|
804
|
+
else if (ts.isCallExpression(valueNode)) {
|
|
805
|
+
vt = resolveCallReturnTypeForNullGuard(valueNode, sourceText);
|
|
806
|
+
}
|
|
807
|
+
else if (ts.isParenthesizedExpression(valueNode) || ts.isNonNullExpression(valueNode)) {
|
|
808
|
+
const inner = ts.isParenthesizedExpression(valueNode) ? valueNode.expression : valueNode.expression;
|
|
809
|
+
if (ts.isIdentifier(inner)) {
|
|
810
|
+
vt = getCurrentIrTypeScope()?.locals.get(inner.text) ?? getCurrentIrTypeScope()?.globals.get(inner.text);
|
|
811
|
+
}
|
|
812
|
+
else if (ts.isCallExpression(inner)) {
|
|
813
|
+
vt = resolveCallReturnTypeForNullGuard(inner, sourceText);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
// A "value type" for null-comparison purposes: a container, std::string,
|
|
817
|
+
// or a value-typed interface/class name. Pointers are excluded (they
|
|
818
|
+
// compare against nullptr, not {}).
|
|
819
|
+
const isValueType = vt && !parsedIsPointer(vt) && (parsedIsVector(vt)
|
|
820
|
+
|| parsedIsMap(vt)
|
|
821
|
+
|| parsedIsSet(vt)
|
|
822
|
+
|| parsedIsStdString(vt)
|
|
823
|
+
|| topLevelInterfaceNames.has(parsedBareString(vt))
|
|
824
|
+
|| topLevelClassNames.has(parsedBareString(vt)));
|
|
825
|
+
if (isValueType) {
|
|
826
|
+
const isEquality = expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || expr.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken;
|
|
827
|
+
// A value type is never null → `=== null` is false, `!== null` is true.
|
|
828
|
+
return { kind: "boolean", value: isEquality ? false : true };
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
// Standalone typeof x emits a type-name string literal.
|
|
833
|
+
if (ts.isTypeOfExpression(expr)) {
|
|
834
|
+
const operand = expr.expression;
|
|
835
|
+
if (ts.isIdentifier(operand)) {
|
|
836
|
+
const varType = getCurrentIrTypeScope()?.locals.get(operand.text) ?? getCurrentIrTypeScope()?.globals.get(operand.text);
|
|
837
|
+
// The cuttlefish intNN_t/uintNN_t family maps to TS `number`.
|
|
838
|
+
const isNumberType = varType === "int" || varType === "float" || varType === "double"
|
|
839
|
+
|| varType === "long" || varType === "long long" || varType === "unsigned long long"
|
|
840
|
+
|| varType === "unsigned" || varType === "size_t"
|
|
841
|
+
|| (varType !== undefined && /^(int|uint)(8|16|32|64)_t$/.test(varType));
|
|
842
|
+
const typeName = isNumberType
|
|
843
|
+
? "number"
|
|
844
|
+
: varType === "bool"
|
|
845
|
+
? "boolean"
|
|
846
|
+
: varType === "std::string"
|
|
847
|
+
? "string"
|
|
848
|
+
: varType === "void"
|
|
849
|
+
? "undefined"
|
|
850
|
+
: "object";
|
|
851
|
+
return { kind: "string", value: typeName };
|
|
852
|
+
}
|
|
853
|
+
if (ts.isNumericLiteral(operand))
|
|
854
|
+
return { kind: "string", value: "number" };
|
|
855
|
+
if (ts.isStringLiteral(operand) || ts.isNoSubstitutionTemplateLiteral(operand))
|
|
856
|
+
return { kind: "string", value: "string" };
|
|
857
|
+
if (operand.kind === ts.SyntaxKind.TrueKeyword || operand.kind === ts.SyntaxKind.FalseKeyword)
|
|
858
|
+
return { kind: "string", value: "boolean" };
|
|
859
|
+
if (ts.isArrayLiteralExpression(operand))
|
|
860
|
+
return { kind: "string", value: "object" };
|
|
861
|
+
if (ts.isObjectLiteralExpression(operand))
|
|
862
|
+
return { kind: "string", value: "object" };
|
|
863
|
+
if (ts.isFunctionExpression(operand) || ts.isArrowFunction(operand))
|
|
864
|
+
return { kind: "string", value: "function" };
|
|
865
|
+
if (ts.isNewExpression(operand))
|
|
866
|
+
return { kind: "string", value: "object" };
|
|
867
|
+
if (ts.isPropertyAccessExpression(operand)) {
|
|
868
|
+
if (operand.name.text === "length")
|
|
869
|
+
return { kind: "string", value: "number" };
|
|
870
|
+
return { kind: "string", value: "object" };
|
|
871
|
+
}
|
|
872
|
+
if (ts.isCallExpression(operand) && ts.isPropertyAccessExpression(operand.expression)) {
|
|
873
|
+
const methodName = operand.expression.name.text;
|
|
874
|
+
if (["push", "pop", "shift", "unshift", "splice", "sort", "reverse", "fill", "concat", "slice", "filter", "map", "reduce", "find", "findIndex", "every", "some", "forEach", "includes", "indexOf", "lastIndexOf", "join"].includes(methodName)) {
|
|
875
|
+
return { kind: "string", value: "object" };
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return { kind: "string", value: "object" };
|
|
879
|
+
}
|
|
880
|
+
// Recurse into binary expressions so nested TypeCAD calls are translated correctly.
|
|
881
|
+
if (ts.isBinaryExpression(expr)) {
|
|
882
|
+
let operator = ts.tokenToString(expr.operatorToken.kind) ?? expr.operatorToken.getText();
|
|
883
|
+
if (operator === "===")
|
|
884
|
+
operator = "==";
|
|
885
|
+
else if (operator === "!==")
|
|
886
|
+
operator = "!=";
|
|
887
|
+
return {
|
|
888
|
+
kind: "binary",
|
|
889
|
+
left: expressionToIR(expr.left, sourceText, diagnostics, pointerVars),
|
|
890
|
+
operator,
|
|
891
|
+
right: expressionToIR(expr.right, sourceText, diagnostics, pointerVars),
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
// Preserve parenthesized expressions as a `paren` IR node so that explicit
|
|
895
|
+
// grouping from the TS source is retained in the emitted C++ (e.g. `(2+3)*4`).
|
|
896
|
+
if (ts.isParenthesizedExpression(expr)) {
|
|
897
|
+
return { kind: "paren", inner: expressionToIR(expr.expression, sourceText, diagnostics, pointerVars) };
|
|
898
|
+
}
|
|
899
|
+
// Recurse into prefix unary so nested calls are translated correctly.
|
|
900
|
+
if (ts.isPrefixUnaryExpression(expr)) {
|
|
901
|
+
const operator = ts.tokenToString(expr.operator) ?? "";
|
|
902
|
+
return {
|
|
903
|
+
kind: "unary",
|
|
904
|
+
operator,
|
|
905
|
+
operand: expressionToIR(expr.operand, sourceText, diagnostics, pointerVars),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
// Handle postfix unary (i++, i--) so they compose correctly in IR.
|
|
909
|
+
if (ts.isPostfixUnaryExpression(expr)) {
|
|
910
|
+
const operator = ts.tokenToString(expr.operator) ?? "";
|
|
911
|
+
return {
|
|
912
|
+
kind: "unary",
|
|
913
|
+
operator,
|
|
914
|
+
operand: expressionToIR(expr.operand, sourceText, diagnostics, pointerVars),
|
|
915
|
+
postfix: true,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (ts.isCallExpression(expr)) {
|
|
919
|
+
// ---- board() / boardResolve() — compile-time board constant lookup ----
|
|
920
|
+
if (ts.isIdentifier(expr.expression) && (expr.expression.text === "board" || expr.expression.text === "boardResolve")) {
|
|
921
|
+
const pathArg = expr.arguments[0];
|
|
922
|
+
if (pathArg && ts.isStringLiteral(pathArg)) {
|
|
923
|
+
const bc = getCurrentBoardConstants();
|
|
924
|
+
if (bc) {
|
|
925
|
+
const val = bc.get(pathArg.text);
|
|
926
|
+
if (val !== undefined) {
|
|
927
|
+
// Preserve the value's type: string board constants (e.g.
|
|
928
|
+
// "architecture" → "avr") must render as C++ string literals, not
|
|
929
|
+
// be coerced via Number() which yields NaN.
|
|
930
|
+
if (typeof val === "string")
|
|
931
|
+
return { kind: "string", value: val };
|
|
932
|
+
return { kind: "number", value: Number(val) };
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
// Unresolved — emit 0 so the value is at least syntactically valid
|
|
937
|
+
return { kind: "number", value: 0 };
|
|
938
|
+
}
|
|
939
|
+
// ---- HAL inline evaluator for expression context ----
|
|
940
|
+
const halResult = tryResolveHALExpression(expr, sourceText, diagnostics, pointerVars);
|
|
941
|
+
if (halResult)
|
|
942
|
+
return halResult.ir;
|
|
943
|
+
// ---- UI signal read: temp() → temp (the device variable) ----
|
|
944
|
+
// A signal lowers to a plain variable, so calling it (the Signal<T>()
|
|
945
|
+
// accessor) is just a read of that variable.
|
|
946
|
+
if (ts.isIdentifier(expr.expression) &&
|
|
947
|
+
expr.arguments.length === 0 &&
|
|
948
|
+
isSignalName(expr.expression.text)) {
|
|
949
|
+
return { kind: "identifier", value: expr.expression.text };
|
|
950
|
+
}
|
|
951
|
+
// ---- UI element .value read: screen.led.value → __ui_nodes[N].value ----
|
|
952
|
+
if (ts.isPropertyAccessExpression(expr.expression) &&
|
|
953
|
+
expr.expression.name.text === "value" &&
|
|
954
|
+
ts.isPropertyAccessExpression(expr.expression.expression) &&
|
|
955
|
+
ts.isIdentifier(expr.expression.expression.expression)) {
|
|
956
|
+
const treeName = expr.expression.expression.expression.text;
|
|
957
|
+
const elemId = expr.expression.expression.name.text;
|
|
958
|
+
const nodeIdx = resolveElementValue(treeName, elemId);
|
|
959
|
+
if (nodeIdx !== undefined) {
|
|
960
|
+
return { kind: "raw", value: `__ui_nodes[${nodeIdx}].value` };
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
// ---- UI element .text read: screen.ssid.text → __ui_nodes[N].textBuffer ----
|
|
964
|
+
if (ts.isPropertyAccessExpression(expr.expression) &&
|
|
965
|
+
expr.expression.name.text === "text" &&
|
|
966
|
+
ts.isPropertyAccessExpression(expr.expression.expression) &&
|
|
967
|
+
ts.isIdentifier(expr.expression.expression.expression)) {
|
|
968
|
+
const treeName = expr.expression.expression.expression.text;
|
|
969
|
+
const elemId = expr.expression.expression.name.text;
|
|
970
|
+
const nodeIdx = resolveElementValue(treeName, elemId);
|
|
971
|
+
if (nodeIdx !== undefined) {
|
|
972
|
+
return { kind: "raw", value: `__ui_nodes[${nodeIdx}].textBuffer` };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
// Warn about optional chaining on call expressions — we preserve a null guard,
|
|
976
|
+
// but the runtime semantics are still only approximate compared to TypeScript.
|
|
977
|
+
if (isOptionalChainNode(expr)) {
|
|
978
|
+
diagnostics.push(makeDiagnostic(sourceText, expr.pos, "Optional chaining (?.) is lowered with an approximate null guard in C++; semantics may differ from TypeScript.", "warning", "TS2CPP_OPTIONAL_CHAINING"));
|
|
979
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
980
|
+
const argsText = expr.arguments
|
|
981
|
+
.map(arg => renderExprAsText(expressionToIR(arg, sourceText, diagnostics, pointerVars)))
|
|
982
|
+
.join(", ");
|
|
983
|
+
const calleeText = renderMemberAccessText(expr.expression.expression, expr.expression.name.text);
|
|
984
|
+
return {
|
|
985
|
+
kind: "raw",
|
|
986
|
+
value: renderOptionalGuardedAccess(expr.expression.expression, `${calleeText}(${argsText})`),
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
// Bare-identifier optional call: `fn?.()`. Without this branch the call
|
|
990
|
+
// falls through to the generic path and emits `fn()` unconditionally —
|
|
991
|
+
// calling an empty std::function throws std::bad_function_call. Wrap the
|
|
992
|
+
// call in the same null guard as the property-access case.
|
|
993
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
994
|
+
const argsText = expr.arguments
|
|
995
|
+
.map(arg => renderExprAsText(expressionToIR(arg, sourceText, diagnostics, pointerVars)))
|
|
996
|
+
.join(", ");
|
|
997
|
+
const calleeText = expr.expression.text;
|
|
998
|
+
return {
|
|
999
|
+
kind: "raw",
|
|
1000
|
+
value: renderOptionalGuardedAccess(expr.expression, `${calleeText}(${argsText})`),
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
// ---- Pin factory constant-folding ---------------------------------------
|
|
1005
|
+
// createDigitalPin(pin, gpio), createPWMPin(pin, gpio), etc. are folded to
|
|
1006
|
+
// just the pin number (first argument) at compile time.
|
|
1007
|
+
if (ts.isIdentifier(expr.expression) && PIN_FACTORY_FUNCTIONS.has(expr.expression.text)) {
|
|
1008
|
+
if (expr.arguments.length >= 1 && ts.isNumericLiteral(expr.arguments[0])) {
|
|
1009
|
+
return { kind: "number", value: Number(expr.arguments[0].text) };
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// ---- Helper function constant-folding -----------------------------------
|
|
1013
|
+
// pinNumber(n) is folded to just the number value at compile time.
|
|
1014
|
+
if (ts.isIdentifier(expr.expression) && CONSTANT_FOLD_FUNCTIONS.has(expr.expression.text)) {
|
|
1015
|
+
if (expr.arguments.length >= 1 && ts.isNumericLiteral(expr.arguments[0])) {
|
|
1016
|
+
return { kind: "number", value: Number(expr.arguments[0].text) };
|
|
1017
|
+
}
|
|
1018
|
+
// Also handle nested expressions like pinNumber(someVar)
|
|
1019
|
+
if (expr.arguments.length >= 1) {
|
|
1020
|
+
return expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
const arrayMethodRes = tryLowerArrayAndStringMethods(expr, sourceText, diagnostics, pointerVars);
|
|
1024
|
+
if (arrayMethodRes !== null) {
|
|
1025
|
+
return arrayMethodRes;
|
|
1026
|
+
}
|
|
1027
|
+
if (ts.isPropertyAccessExpression(expr.expression) && ALL_STRING_METHODS.has(expr.expression.name.text)) {
|
|
1028
|
+
const receiver = expr.expression.expression;
|
|
1029
|
+
const isStringLiteralReceiver = ts.isStringLiteral(receiver) || ts.isNoSubstitutionTemplateLiteral(receiver);
|
|
1030
|
+
const isTemplateExprReceiver = ts.isTemplateExpression(receiver);
|
|
1031
|
+
const isStringVarReceiver = ts.isIdentifier(receiver) && (activeStringVars.has(receiver.text) || getCurrentIrTypeScope()?.locals.get(receiver.text) === "std::string");
|
|
1032
|
+
if (isStringLiteralReceiver || isTemplateExprReceiver || isStringVarReceiver) {
|
|
1033
|
+
const receiverIR = expressionToIR(receiver, sourceText, diagnostics, pointerVars);
|
|
1034
|
+
let receiverText = renderExprAsText(receiverIR);
|
|
1035
|
+
if (receiver.kind === ts.SyntaxKind.StringLiteral || receiver.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
1036
|
+
receiverText = `std::string(${receiverText})`;
|
|
1037
|
+
}
|
|
1038
|
+
const methodName = expr.expression.name.text;
|
|
1039
|
+
const argsText = expr.arguments.map(a => renderExprAsText(expressionToIR(a, sourceText, diagnostics, pointerVars))).join(", ");
|
|
1040
|
+
return { kind: "raw", value: `${receiverText}.${methodName}(${argsText})` };
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "Error") {
|
|
1044
|
+
const message = expr.arguments.length > 0
|
|
1045
|
+
? renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars))
|
|
1046
|
+
: '"error"';
|
|
1047
|
+
return { kind: "raw", value: `std::runtime_error(${message})` };
|
|
1048
|
+
}
|
|
1049
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
1050
|
+
const fnName = expr.expression.text;
|
|
1051
|
+
if (fnName === "parseInt" && expr.arguments.length >= 1) {
|
|
1052
|
+
const argText = renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars));
|
|
1053
|
+
// atoi/atof take const char*; the arg is typically a std::string.
|
|
1054
|
+
// Use .c_str() so the conversion compiles.
|
|
1055
|
+
return { kind: "raw", value: `atoi((${argText}).c_str())` };
|
|
1056
|
+
}
|
|
1057
|
+
if (fnName === "parseFloat" && expr.arguments.length >= 1) {
|
|
1058
|
+
const argText = renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars));
|
|
1059
|
+
return { kind: "raw", value: `atof((${argText}).c_str())` };
|
|
1060
|
+
}
|
|
1061
|
+
if (fnName === "isNaN" && expr.arguments.length >= 1) {
|
|
1062
|
+
const argText = renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars));
|
|
1063
|
+
return { kind: "raw", value: `std::isnan(${argText})` };
|
|
1064
|
+
}
|
|
1065
|
+
if (fnName === "isFinite" && expr.arguments.length >= 1) {
|
|
1066
|
+
const argText = renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars));
|
|
1067
|
+
return { kind: "raw", value: `std::isfinite(${argText})` };
|
|
1068
|
+
}
|
|
1069
|
+
if (fnName === "Number" && expr.arguments.length >= 1) {
|
|
1070
|
+
return expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1071
|
+
}
|
|
1072
|
+
if (fnName === "String" && expr.arguments.length >= 1) {
|
|
1073
|
+
const argIR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1074
|
+
return { kind: "template_string", expression: argIR };
|
|
1075
|
+
}
|
|
1076
|
+
if (fnName === "Boolean" && expr.arguments.length >= 1) {
|
|
1077
|
+
const argIR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1078
|
+
return { kind: "raw", value: `static_cast<bool>(${renderExprAsText(argIR)})` };
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// Handle static method calls on built-in objects: Array.isArray, Object.keys/values/entries/assign
|
|
1082
|
+
if (ts.isPropertyAccessExpression(expr.expression) && ts.isIdentifier(expr.expression.expression)) {
|
|
1083
|
+
const objName = expr.expression.expression.text;
|
|
1084
|
+
const methodName = expr.expression.name.text;
|
|
1085
|
+
if (objName === "Array" && methodName === "isArray" && expr.arguments.length === 1) {
|
|
1086
|
+
const argIR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1087
|
+
const argText = renderExprAsText(argIR);
|
|
1088
|
+
let argType;
|
|
1089
|
+
if (ts.isIdentifier(expr.arguments[0])) {
|
|
1090
|
+
argType = getCurrentIrTypeScope()?.locals.get(expr.arguments[0].text) ?? getCurrentIrTypeScope()?.globals.get(expr.arguments[0].text);
|
|
1091
|
+
}
|
|
1092
|
+
if (argType && parsedIsVector(argType)) {
|
|
1093
|
+
return { kind: "boolean", value: true };
|
|
1094
|
+
}
|
|
1095
|
+
return { kind: "boolean", value: false };
|
|
1096
|
+
}
|
|
1097
|
+
if (objName === "Object" && expr.arguments.length >= 1) {
|
|
1098
|
+
const argNode = expr.arguments[0];
|
|
1099
|
+
const argIR = expressionToIR(argNode, sourceText, diagnostics, pointerVars);
|
|
1100
|
+
const argText = renderExprAsText(argIR);
|
|
1101
|
+
let argType;
|
|
1102
|
+
if (ts.isIdentifier(argNode)) {
|
|
1103
|
+
argType = getCurrentIrTypeScope()?.locals.get(argNode.text) ?? getCurrentIrTypeScope()?.globals.get(argNode.text);
|
|
1104
|
+
}
|
|
1105
|
+
else if (ts.isPropertyAccessExpression(argNode) && argNode.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1106
|
+
// `Object.keys(this.field)` — resolve the field's type via the
|
|
1107
|
+
// this->field map populated during class IR build.
|
|
1108
|
+
argType = getCurrentIrTypeScope()?.locals.get(`this->${argNode.name.text}`);
|
|
1109
|
+
if (!argType) {
|
|
1110
|
+
argType = getCurrentIrTypeScope()?.classFields.get(`this->${argNode.name.text}`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
if (methodName === "keys") {
|
|
1114
|
+
if (argType && parsedIsMap(argType)) {
|
|
1115
|
+
return { kind: "raw", value: `__tc_mapKeys(${argText})` };
|
|
1116
|
+
}
|
|
1117
|
+
if (ts.isObjectLiteralExpression(argNode)) {
|
|
1118
|
+
const fieldNames = argNode.properties
|
|
1119
|
+
.filter(ts.isPropertyAssignment)
|
|
1120
|
+
.filter(p => ts.isIdentifier(p.name))
|
|
1121
|
+
.map(p => p.name.text);
|
|
1122
|
+
return { kind: "array", elementType: "const char*", elements: fieldNames.map(n => ({ kind: "string", value: n })) };
|
|
1123
|
+
}
|
|
1124
|
+
return emitUnsupportedExpression("Object.keys on non-map types is unsupported.");
|
|
1125
|
+
}
|
|
1126
|
+
if (methodName === "values") {
|
|
1127
|
+
if (argType && parsedIsMap(argType)) {
|
|
1128
|
+
return { kind: "raw", value: `__tc_mapValues(${argText})` };
|
|
1129
|
+
}
|
|
1130
|
+
if (ts.isObjectLiteralExpression(argNode)) {
|
|
1131
|
+
const elements = argNode.properties
|
|
1132
|
+
.filter(ts.isPropertyAssignment)
|
|
1133
|
+
.map(p => expressionToIR(p.initializer, sourceText, diagnostics, pointerVars));
|
|
1134
|
+
return { kind: "array", elementType: "auto", elements };
|
|
1135
|
+
}
|
|
1136
|
+
return emitUnsupportedExpression("Object.values on non-map types is unsupported.");
|
|
1137
|
+
}
|
|
1138
|
+
if (methodName === "entries") {
|
|
1139
|
+
if (argType && parsedIsMap(argType)) {
|
|
1140
|
+
return { kind: "raw", value: `__tc_mapEntries(${argText})` };
|
|
1141
|
+
}
|
|
1142
|
+
return emitUnsupportedExpression("Object.entries on non-map types is unsupported.");
|
|
1143
|
+
}
|
|
1144
|
+
if (methodName === "assign" && expr.arguments.length === 2) {
|
|
1145
|
+
const targetText = argText;
|
|
1146
|
+
const srcIR = expressionToIR(expr.arguments[1], sourceText, diagnostics, pointerVars);
|
|
1147
|
+
const srcText = renderExprAsText(srcIR);
|
|
1148
|
+
return { kind: "raw", value: `(${targetText} = ${srcText})` };
|
|
1149
|
+
}
|
|
1150
|
+
if (methodName === "fromEntries" && expr.arguments.length === 1) {
|
|
1151
|
+
const entriesNode = expr.arguments[0];
|
|
1152
|
+
const entriesIR = expressionToIR(entriesNode, sourceText, diagnostics, pointerVars);
|
|
1153
|
+
const entriesText = renderExprAsText(entriesIR);
|
|
1154
|
+
return { kind: "raw", value: `__tc_fromEntries(${entriesText})` };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (objName === "JSON") {
|
|
1158
|
+
if (methodName === "stringify" && expr.arguments.length >= 1) {
|
|
1159
|
+
const argIR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1160
|
+
const argText = renderExprAsText(argIR);
|
|
1161
|
+
return { kind: "raw", value: `__tc_jsonStringify(${argText})` };
|
|
1162
|
+
}
|
|
1163
|
+
if (methodName === "parse" && expr.arguments.length >= 1) {
|
|
1164
|
+
const argIR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1165
|
+
const argText = renderExprAsText(argIR);
|
|
1166
|
+
return { kind: "raw", value: `__tc_jsonParse(${argText})` };
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "defineBoardManifest" && expr.arguments.length === 1) {
|
|
1171
|
+
return expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1172
|
+
}
|
|
1173
|
+
// Handle method calls like this.method() or obj.method()
|
|
1174
|
+
// Use -> for pointer variables (from 'new') and for 'this', . for value types
|
|
1175
|
+
let calleeText;
|
|
1176
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
1177
|
+
const receiver = expr.expression.expression;
|
|
1178
|
+
const rawMethodName = expr.expression.name.kind === ts.SyntaxKind.PrivateIdentifier
|
|
1179
|
+
? `__priv_${expr.expression.name.text.substring(1)}`
|
|
1180
|
+
: expr.expression.name.text;
|
|
1181
|
+
// NOTE: do NOT escapeCppKeyword before the Map/Set method checks below —
|
|
1182
|
+
// `m.delete(k)` would become `methodName === "delete_"` and miss the
|
|
1183
|
+
// map→erase lowering (demo #8 Finding D). Escape only when building the
|
|
1184
|
+
// final callee text for the generic path.
|
|
1185
|
+
const methodName = rawMethodName;
|
|
1186
|
+
// --- .toFixed(digits) on numeric values ---
|
|
1187
|
+
if (methodName === "toFixed" && expr.arguments.length >= 1) {
|
|
1188
|
+
const receiverIR = expressionToIR(receiver, sourceText, diagnostics, pointerVars);
|
|
1189
|
+
const digitsArg = expr.arguments[0];
|
|
1190
|
+
let digits;
|
|
1191
|
+
if (ts.isNumericLiteral(digitsArg)) {
|
|
1192
|
+
digits = parseInt(digitsArg.text, 10);
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
const digitsIR = expressionToIR(digitsArg, sourceText, diagnostics, pointerVars);
|
|
1196
|
+
if (digitsIR.kind === "number" && Number.isInteger(digitsIR.value)) {
|
|
1197
|
+
digits = digitsIR.value;
|
|
1198
|
+
}
|
|
1199
|
+
else {
|
|
1200
|
+
digits = 0;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
const receiverText = renderExprAsText(receiverIR);
|
|
1204
|
+
return { kind: "raw", value: `__tc_toFixed(${receiverText}, ${digits})` };
|
|
1205
|
+
}
|
|
1206
|
+
// --- Map/Set iteration methods: .values() / .keys() / .entries() ---
|
|
1207
|
+
// These lower to the same __tc_mapValues/__tc_mapKeys/__tc_mapEntries
|
|
1208
|
+
// helpers used for Object.values(map) etc. (defined in the native
|
|
1209
|
+
// strategy). They return std::vector<V/K/pair>, so a downstream
|
|
1210
|
+
// for...of iterates the values/keys/pairs correctly — NOT the raw
|
|
1211
|
+
// std::pair entries of the underlying std::map (demo #15 fix A).
|
|
1212
|
+
if ((methodName === "values" || methodName === "keys" || methodName === "entries") && expr.arguments.length === 0) {
|
|
1213
|
+
let receiverType;
|
|
1214
|
+
if (ts.isIdentifier(receiver)) {
|
|
1215
|
+
receiverType = getCurrentIrTypeScope()?.locals.get(receiver.text) ?? getCurrentIrTypeScope()?.globals.get(receiver.text);
|
|
1216
|
+
}
|
|
1217
|
+
else if (ts.isPropertyAccessExpression(receiver) && receiver.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1218
|
+
receiverType = getCurrentIrTypeScope()?.locals.get(`this->${receiver.name.text}`);
|
|
1219
|
+
}
|
|
1220
|
+
if (receiverType && (parsedIsMap(receiverType) || parsedIsSet(receiverType))) {
|
|
1221
|
+
const recIR = expressionToIR(receiver, sourceText, diagnostics, pointerVars);
|
|
1222
|
+
const recText = renderExprAsText(recIR);
|
|
1223
|
+
// Set.values()/keys() both yield the elements; entries() yields
|
|
1224
|
+
// pair<elem,elem>. Map.values()/keys()/entries() are as expected.
|
|
1225
|
+
const isSet = parsedIsSet(receiverType);
|
|
1226
|
+
const helper = methodName === "values"
|
|
1227
|
+
? (isSet ? "__tc_setValues" : "__tc_mapValues")
|
|
1228
|
+
: methodName === "keys"
|
|
1229
|
+
? (isSet ? "__tc_setValues" : "__tc_mapKeys")
|
|
1230
|
+
: (isSet ? "__tc_setEntries" : "__tc_mapEntries");
|
|
1231
|
+
return { kind: "raw", value: `${helper}(${recText})` };
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
// --- Map/Set method lowering ---
|
|
1235
|
+
if ((methodName === "set" || methodName === "get" || methodName === "has" || methodName === "delete" || methodName === "add") && expr.arguments.length >= 1) {
|
|
1236
|
+
let receiverType;
|
|
1237
|
+
let receiverText;
|
|
1238
|
+
if (ts.isIdentifier(receiver)) {
|
|
1239
|
+
receiverType = getCurrentIrTypeScope()?.locals.get(receiver.text) ?? getCurrentIrTypeScope()?.globals.get(receiver.text);
|
|
1240
|
+
}
|
|
1241
|
+
else if (ts.isPropertyAccessExpression(receiver) && receiver.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1242
|
+
receiverType = getCurrentIrTypeScope()?.locals.get(`this->${receiver.name.text}`);
|
|
1243
|
+
}
|
|
1244
|
+
if (receiverType && (parsedIsMap(receiverType) || parsedIsSet(receiverType))) {
|
|
1245
|
+
const recIR = expressionToIR(receiver, sourceText, diagnostics, pointerVars);
|
|
1246
|
+
receiverText = renderExprAsText(recIR);
|
|
1247
|
+
const arg0IR = expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars);
|
|
1248
|
+
let arg0Text = renderExprAsText(arg0IR);
|
|
1249
|
+
// When the key is an enum-typed operand and the container's key type
|
|
1250
|
+
// is integral, wrap it in static_cast so it matches the comparator /
|
|
1251
|
+
// converts the enum class to the integral key. Handles BOTH shapes:
|
|
1252
|
+
// - an enum member access (Color.Red) — detected via activeEnumNames; and
|
|
1253
|
+
// - a bare identifier whose declared type is an enum (k where k: K)
|
|
1254
|
+
// — resolved through the in-scope IR type map. Demo #28 Finding E
|
|
1255
|
+
// review: previously only the enum-member shape was cast, so a
|
|
1256
|
+
// bare enum-typed key variable on .has/.get/.delete (expression
|
|
1257
|
+
// form) compiled to an uncast map.count(k)/map.at(k) and failed.
|
|
1258
|
+
const receiverIr = parseCppType(receiverType);
|
|
1259
|
+
const mapKeyType = receiverIr.kind === "map" ? renderCppType(receiverIr.key) : "";
|
|
1260
|
+
const keyIsIntegral = /^(int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|size_t|long|short|unsigned|char)$/.test(mapKeyType);
|
|
1261
|
+
let keyIsEnum = false;
|
|
1262
|
+
if (keyIsIntegral) {
|
|
1263
|
+
const keyArg = expr.arguments[0];
|
|
1264
|
+
if (ts.isPropertyAccessExpression(keyArg) && ts.isIdentifier(keyArg.expression)) {
|
|
1265
|
+
keyIsEnum = activeEnumNames.has(keyArg.expression.text);
|
|
1266
|
+
}
|
|
1267
|
+
else if (ts.isIdentifier(keyArg)) {
|
|
1268
|
+
const keyVarType = getCurrentIrTypeScope()?.locals.get(keyArg.text) ?? getCurrentIrTypeScope()?.globals.get(keyArg.text);
|
|
1269
|
+
if (keyVarType && activeEnumNames.has(keyVarType)) {
|
|
1270
|
+
keyIsEnum = true;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (keyIsEnum) {
|
|
1275
|
+
arg0Text = `static_cast<${mapKeyType}>(${arg0Text})`;
|
|
1276
|
+
}
|
|
1277
|
+
if (parsedIsMap(receiverType)) {
|
|
1278
|
+
if (methodName === "set" && expr.arguments.length >= 2) {
|
|
1279
|
+
const arg1IR = expressionToIR(expr.arguments[1], sourceText, diagnostics, pointerVars);
|
|
1280
|
+
const arg1Text = renderExprAsText(arg1IR);
|
|
1281
|
+
return { kind: "raw", value: `(${receiverText}[${arg0Text}] = ${arg1Text})` };
|
|
1282
|
+
}
|
|
1283
|
+
if (methodName === "get") {
|
|
1284
|
+
// Use .at() (const-correct, throws on miss) rather than operator[]
|
|
1285
|
+
// (non-const, fails on a const-bound Map, silently inserts on miss).
|
|
1286
|
+
// The idiomatic m.get(k)! asserts presence, matching .at() contract. (demo #15 fix B)
|
|
1287
|
+
return { kind: "raw", value: `${receiverText}.at(${arg0Text})` };
|
|
1288
|
+
}
|
|
1289
|
+
if (methodName === "has") {
|
|
1290
|
+
return { kind: "raw", value: `(${receiverText}.count(${arg0Text}) > 0)` };
|
|
1291
|
+
}
|
|
1292
|
+
if (methodName === "delete") {
|
|
1293
|
+
return { kind: "raw", value: `(${receiverText}.erase(${arg0Text}) > 0)` };
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (parsedIsSet(receiverType)) {
|
|
1297
|
+
if (methodName === "add") {
|
|
1298
|
+
return { kind: "raw", value: `${receiverText}.insert(${arg0Text})` };
|
|
1299
|
+
}
|
|
1300
|
+
if (methodName === "has") {
|
|
1301
|
+
return { kind: "raw", value: `(${receiverText}.count(${arg0Text}) > 0)` };
|
|
1302
|
+
}
|
|
1303
|
+
if (methodName === "delete") {
|
|
1304
|
+
return { kind: "raw", value: `(${receiverText}.erase(${arg0Text}) > 0)` };
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
// Check if it's a this.method() call - in C++, this is a pointer so use ->
|
|
1310
|
+
if (receiver.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1311
|
+
calleeText = `this->${escapeCppKeyword(methodName)}`;
|
|
1312
|
+
}
|
|
1313
|
+
else if (ts.isIdentifier(receiver) && receiver.text === "Math") {
|
|
1314
|
+
const mathMethod = expr.expression.name.text;
|
|
1315
|
+
if (mathMethod === "random") {
|
|
1316
|
+
calleeText = "__tc_random";
|
|
1317
|
+
}
|
|
1318
|
+
else if ((mathMethod === "max" || mathMethod === "min") && expr.arguments && expr.arguments.length >= 1) {
|
|
1319
|
+
const op = mathMethod === "max" ? ">" : "<";
|
|
1320
|
+
const args = expr.arguments.map(a => expressionToIR(a, sourceText, diagnostics, pointerVars));
|
|
1321
|
+
let result = args[0];
|
|
1322
|
+
for (let i = 1; i < args.length; i++) {
|
|
1323
|
+
result = {
|
|
1324
|
+
kind: "ternary",
|
|
1325
|
+
condition: { kind: "binary", left: result, operator: op, right: args[i] },
|
|
1326
|
+
whenTrue: result,
|
|
1327
|
+
whenFalse: args[i],
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
return result;
|
|
1331
|
+
}
|
|
1332
|
+
else {
|
|
1333
|
+
calleeText = `std::${mathMethod}`;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
else if (ts.isIdentifier(receiver) && (hoistedNestedClasses.some(c => c.name === receiver.text) || nestedClassAliases.has(receiver.text) || topLevelClassNames.has(receiver.text))) {
|
|
1337
|
+
// Static method call on a hoisted or top-level class: use :: with resolved name
|
|
1338
|
+
const resolvedName = nestedClassAliases.get(receiver.text) ?? receiver.text;
|
|
1339
|
+
calleeText = `${resolvedName}::${methodName}`;
|
|
1340
|
+
}
|
|
1341
|
+
else if (ts.isIdentifier(receiver) && activeNamespaceNames.has(receiver.text)) {
|
|
1342
|
+
// Namespace method call: use ::
|
|
1343
|
+
calleeText = `${receiver.text}::${methodName}`;
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
const objText = renderExprAsText(expressionToIR(receiver, sourceText, diagnostics, pointerVars));
|
|
1347
|
+
let accessor = ".";
|
|
1348
|
+
if (ts.isIdentifier(receiver) && (pointerVars.has(receiver.text) || parsedIsPointer(getCurrentIrTypeScope()?.locals.get(receiver.text) ?? "") || parsedIsPointer(getCurrentIrTypeScope()?.globals.get(receiver.text) ?? ""))) {
|
|
1349
|
+
accessor = "->";
|
|
1350
|
+
}
|
|
1351
|
+
else if (ts.isPropertyAccessExpression(receiver)
|
|
1352
|
+
&& receiver.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
1353
|
+
&& ts.isIdentifier(receiver.name)) {
|
|
1354
|
+
const fieldType = getCurrentIrTypeScope()?.locals.get(`this->${receiver.name.text}`);
|
|
1355
|
+
if (fieldType && parsedIsPointer(fieldType)) {
|
|
1356
|
+
accessor = "->";
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
else if (ts.isCallExpression(receiver) && ts.isPropertyAccessExpression(receiver.expression)) {
|
|
1360
|
+
const innerReceiver = receiver.expression.expression;
|
|
1361
|
+
const innerMethodName = receiver.expression.name.text;
|
|
1362
|
+
if (ts.isIdentifier(innerReceiver) && pointerVars.has(innerReceiver.text)) {
|
|
1363
|
+
// Check if the inner method returns a pointer type (for method chaining)
|
|
1364
|
+
let className = pointerVars.get(innerReceiver.text);
|
|
1365
|
+
// Resolve nested class aliases (e.g., "Builder" → "__tc_fn6__Builder")
|
|
1366
|
+
if (className)
|
|
1367
|
+
className = nestedClassAliases.get(className) ?? className;
|
|
1368
|
+
const cls = className ? hoistedNestedClasses.find(c => c.name === className) : undefined;
|
|
1369
|
+
const chainMethod = cls?.methods.find(m => m.name === innerMethodName);
|
|
1370
|
+
if (chainMethod && parsedIsPointer(chainMethod.returnType)) {
|
|
1371
|
+
accessor = "->";
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
else if (ts.isIdentifier(innerReceiver) && topLevelClassNames.has(innerReceiver.text)) {
|
|
1375
|
+
// Static method call on a top-level class returning a pointer
|
|
1376
|
+
const cls = topLevelClasses.get(innerReceiver.text);
|
|
1377
|
+
if (!cls) {
|
|
1378
|
+
// Cross-module class: factory methods typically return class instances (pointers)
|
|
1379
|
+
accessor = "->";
|
|
1380
|
+
}
|
|
1381
|
+
else {
|
|
1382
|
+
const chainMethod = cls.methods.find(m => m.name === innerMethodName);
|
|
1383
|
+
if (chainMethod && (parsedIsPointer(chainMethod.returnType) || chainMethod.returnType === innerReceiver.text)) {
|
|
1384
|
+
accessor = "->";
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (accessor === ".") {
|
|
1390
|
+
const receiverType = resolveExprCppType(receiver);
|
|
1391
|
+
if (receiverType && parsedIsPointer(receiverType)) {
|
|
1392
|
+
accessor = "->";
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
calleeText = `${objText}${accessor}${escapeCppKeyword(methodName)}`;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
else {
|
|
1399
|
+
const rawText = expr.expression.getText();
|
|
1400
|
+
calleeText = nestedFunctionAliases.get(rawText) ?? rawText;
|
|
1401
|
+
}
|
|
1402
|
+
const argIRs = [];
|
|
1403
|
+
for (const arg of expr.arguments) {
|
|
1404
|
+
if (ts.isSpreadElement(arg)) {
|
|
1405
|
+
// Spreading into a call. Cuttlefish lowers every rest parameter
|
|
1406
|
+
// (`...args: T[]`) to a SINGLE `const std::vector<T>&` parameter, so a
|
|
1407
|
+
// spread of a vector variable (`sum(...arr)`) must pass the vector
|
|
1408
|
+
// directly — NOT `arr.begin(), arr.end()` (which passes two iterators
|
|
1409
|
+
// to one vector param and fails g++). For a non-vector spread we pass
|
|
1410
|
+
// the rendered expression as-is. See demo #6 fix E.
|
|
1411
|
+
argIRs.push(expressionToIR(arg.expression, sourceText, diagnostics, pointerVars));
|
|
1412
|
+
}
|
|
1413
|
+
else {
|
|
1414
|
+
argIRs.push(expressionToIR(arg, sourceText, diagnostics, pointerVars));
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
let isStatic = false;
|
|
1418
|
+
let isNamespace = false;
|
|
1419
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
1420
|
+
let root = expr.expression;
|
|
1421
|
+
while (ts.isPropertyAccessExpression(root)) {
|
|
1422
|
+
root = root.expression;
|
|
1423
|
+
}
|
|
1424
|
+
if (ts.isIdentifier(root)) {
|
|
1425
|
+
const name = root.text;
|
|
1426
|
+
if (activeNamespaceNames.has(name)) {
|
|
1427
|
+
isNamespace = true;
|
|
1428
|
+
}
|
|
1429
|
+
else if (topLevelClassNames.has(name) || nestedClassAliases.has(name) || name === "Math") {
|
|
1430
|
+
isStatic = true;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return {
|
|
1435
|
+
kind: "method-call",
|
|
1436
|
+
callee: calleeText,
|
|
1437
|
+
args: argIRs,
|
|
1438
|
+
isStatic,
|
|
1439
|
+
isNamespace,
|
|
1440
|
+
// isPointer when the callee text already uses ->, OR when the receiver
|
|
1441
|
+
// resolves to a pointer type. The receiver is the object the method is
|
|
1442
|
+
// called on: for `a.b.c()` it is `a.b`. resolveExprCppType walks
|
|
1443
|
+
// class/interface fields to find the receiver's C++ type; if it ends
|
|
1444
|
+
// with '*' (a class-pointer struct field, e.g. FloorState.monster), the
|
|
1445
|
+
// method call must use ->. Without this, `dungeon.monster.bounty()`
|
|
1446
|
+
// emits `.` and fails g++ ("request for member 'bounty' ... pointer
|
|
1447
|
+
// type"). See SUPPORT_MATRIX §4.5 (demo #4 fix).
|
|
1448
|
+
isPointer: calleeText.includes("->")
|
|
1449
|
+
|| (ts.isCallExpression(expr)
|
|
1450
|
+
&& ts.isPropertyAccessExpression(expr.expression)
|
|
1451
|
+
&& (() => {
|
|
1452
|
+
const receiverType = resolveExprCppType(expr.expression.expression);
|
|
1453
|
+
return !!receiverType && parsedIsPointer(receiverType);
|
|
1454
|
+
})()),
|
|
1455
|
+
restElementType: restParamFunctions.get(calleeText),
|
|
1456
|
+
cppType: resolveExprCppType(expr),
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
if (ts.isNewExpression(expr)) {
|
|
1460
|
+
let ctorText = formatExpressionText(expr.expression);
|
|
1461
|
+
// Base ctor name without type arguments — used for the Map/Set checks
|
|
1462
|
+
// below, which must fire even when the user wrote `new Map<K, V>()`.
|
|
1463
|
+
const baseCtorName = ctorText;
|
|
1464
|
+
if (expr.typeArguments && expr.typeArguments.length > 0) {
|
|
1465
|
+
// Resolve type args through typeNodeToCppType so `new Registry<string, int32_t>()`
|
|
1466
|
+
// emits `Registry<std::string, int32_t>` (TS type names → C++ type names)
|
|
1467
|
+
// rather than the raw source text or dropping the args entirely.
|
|
1468
|
+
const typeArgs = expr.typeArguments.map((ta) => typeNodeToCppType(ta, undefined)).join(", ");
|
|
1469
|
+
ctorText = `${ctorText}<${typeArgs}>`;
|
|
1470
|
+
}
|
|
1471
|
+
// Special case: new TypedArray([...]) → C++ array initializer
|
|
1472
|
+
const elementType = TYPED_ARRAY_ELEMENT_MAP[ctorText];
|
|
1473
|
+
if (elementType) {
|
|
1474
|
+
const args = expr.arguments ?? [];
|
|
1475
|
+
if (args.length === 1 && ts.isArrayLiteralExpression(args[0])) {
|
|
1476
|
+
const elements = args[0].elements.map(e => expressionToIR(e, sourceText, diagnostics, pointerVars));
|
|
1477
|
+
return { kind: "array", elements, elementType };
|
|
1478
|
+
}
|
|
1479
|
+
// new TypedArray(n) — allocate n elements (zero-initialized)
|
|
1480
|
+
if (args.length === 1) {
|
|
1481
|
+
const sizeIR = expressionToIR(args[0], sourceText, diagnostics, pointerVars);
|
|
1482
|
+
const size = renderExprAsText(sizeIR);
|
|
1483
|
+
const count = parseInt(size, 10);
|
|
1484
|
+
if (!isNaN(count) && count > 0 && count <= 256) {
|
|
1485
|
+
// Return array IR with zero elements so var_decl renderer emits proper C array
|
|
1486
|
+
const zeros = Array(count).fill(0).map(() => ({ kind: "number", value: 0 }));
|
|
1487
|
+
return { kind: "array", elements: zeros, elementType };
|
|
1488
|
+
}
|
|
1489
|
+
// Dynamic size fallback — emit as raw (may not compile in all contexts)
|
|
1490
|
+
return { kind: "raw", value: `{${elementType}(${size})}` };
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
const argsText = (expr.arguments ?? [])
|
|
1494
|
+
.map((arg) => {
|
|
1495
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
1496
|
+
const fields = [];
|
|
1497
|
+
for (const prop of arg.properties) {
|
|
1498
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
1499
|
+
const name = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.getText();
|
|
1500
|
+
const val = renderExprAsText(expressionToIR(prop.initializer, sourceText, diagnostics, pointerVars));
|
|
1501
|
+
fields.push(`.${name} = ${val}`);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
return fields.length > 0 ? `{ ${fields.join(", ")} }` : "0";
|
|
1505
|
+
}
|
|
1506
|
+
return renderExprAsText(expressionToIR(arg, sourceText, diagnostics, pointerVars));
|
|
1507
|
+
})
|
|
1508
|
+
.join(", ");
|
|
1509
|
+
if (ctorText === "Error") {
|
|
1510
|
+
const message = expr.arguments && expr.arguments.length > 0
|
|
1511
|
+
? renderExprAsText(expressionToIR(expr.arguments[0], sourceText, diagnostics, pointerVars))
|
|
1512
|
+
: '"error"';
|
|
1513
|
+
return { kind: "raw", value: `std::runtime_error(${message})` };
|
|
1514
|
+
}
|
|
1515
|
+
if (baseCtorName === "Map") {
|
|
1516
|
+
// Demo #30 Finding E — `new Map()` lowers to `{}` (an empty
|
|
1517
|
+
// std::map), but `new Map([[k, v], ...])` (the idiomatic TS
|
|
1518
|
+
// constructor-with-initial-entries form) previously had its argument
|
|
1519
|
+
// DROPPED and also lowered to `{}`, so the map started empty. The fix:
|
|
1520
|
+
// when an initializer argument is present, render it into the
|
|
1521
|
+
// brace-init-list. `argsText` already renders a single array-literal
|
|
1522
|
+
// argument as `{ {k1, v1}, {k2, v2}, ... }` (renderExprAsText of an
|
|
1523
|
+
// array of arrays), which is exactly the `std::initializer_list<pair>`
|
|
1524
|
+
// form `std::map`'s constructor accepts. A non-iterable argument
|
|
1525
|
+
// (`new Map(otherMap)`) has no clean C++ equivalent and falls back to
|
|
1526
|
+
// `{}` — matching the prior behavior. This mirrors the Set fix below.
|
|
1527
|
+
if ((expr.arguments ?? []).length === 1) {
|
|
1528
|
+
return { kind: "raw", value: argsText };
|
|
1529
|
+
}
|
|
1530
|
+
return { kind: "raw", value: "{}" };
|
|
1531
|
+
}
|
|
1532
|
+
if (baseCtorName === "Set") {
|
|
1533
|
+
// Demo #30 Finding E — `new Set()` lowers to `{}` (an empty std::set),
|
|
1534
|
+
// but `new Set([a, b, c])` (the idiomatic TS constructor-with-initial-
|
|
1535
|
+
// elements form) previously had its argument DROPPED and also lowered to
|
|
1536
|
+
// `{}`, so the set started empty. The fix: when an initializer argument
|
|
1537
|
+
// is present, render it into the brace-init-list. `argsText` already
|
|
1538
|
+
// renders a single array-literal argument as `{ a, b, c }`
|
|
1539
|
+
// (renderExprAsText of an array), which is exactly the
|
|
1540
|
+
// `std::initializer_list<T>` form `std::set`'s constructor accepts. A
|
|
1541
|
+
// non-iterable argument (`new Set(otherSet)`) has no clean C++
|
|
1542
|
+
// equivalent and falls back to `{}` — matching the prior behavior.
|
|
1543
|
+
if ((expr.arguments ?? []).length === 1) {
|
|
1544
|
+
return { kind: "raw", value: argsText };
|
|
1545
|
+
}
|
|
1546
|
+
return { kind: "raw", value: "{}" };
|
|
1547
|
+
}
|
|
1548
|
+
// `new Array<E>(...)` — the idiomatic TS pre-sized/empty array constructor.
|
|
1549
|
+
// Demo #29 Finding B: this was previously emitted verbatim
|
|
1550
|
+
// (`new Array<uint32_t>(256)`) and failed at g++ time ("'Array' does not
|
|
1551
|
+
// name a type"), because the `Array` constructor — unlike `Array.from`/
|
|
1552
|
+
// `Array.of` (which are intentionally rejected) — had neither a lowering
|
|
1553
|
+
// nor a gate. The clean C++ equivalent is a `std::vector<E>`:
|
|
1554
|
+
// `new Array<E>(n)` → `std::vector<E>(n)` (n value-initialized elems)
|
|
1555
|
+
// `new Array<E>()` → `std::vector<E>()` (empty)
|
|
1556
|
+
// `new Array<E>(a,b,c)` → `std::vector<E>{a,b,c}` (element list)
|
|
1557
|
+
// The element type comes from the type argument; without one we cannot
|
|
1558
|
+
// pick a C++ element type, so fall through to the unsupported-ctor path
|
|
1559
|
+
// (gated by feature-registry) rather than guess.
|
|
1560
|
+
if (baseCtorName === "Array" && expr.typeArguments && expr.typeArguments.length === 1) {
|
|
1561
|
+
const elemCpp = typeNodeToCppType(expr.typeArguments[0], undefined);
|
|
1562
|
+
const args = expr.arguments ?? [];
|
|
1563
|
+
if (args.length === 0) {
|
|
1564
|
+
return { kind: "raw", value: `std::vector<${elemCpp}>()` };
|
|
1565
|
+
}
|
|
1566
|
+
if (args.length === 1) {
|
|
1567
|
+
// Sized constructor: n value-initialized elements. The size argument
|
|
1568
|
+
// is rendered through the normal expression path.
|
|
1569
|
+
const sizeText = renderExprAsText(expressionToIR(args[0], sourceText, diagnostics, pointerVars));
|
|
1570
|
+
return { kind: "raw", value: `std::vector<${elemCpp}>(${sizeText})` };
|
|
1571
|
+
}
|
|
1572
|
+
// Two+ args: treat as an element list (TS `new Array<E>(a, b, c)`).
|
|
1573
|
+
return { kind: "raw", value: `std::vector<${elemCpp}>{ ${argsText} }` };
|
|
1574
|
+
}
|
|
1575
|
+
const resolvedCtorText = nestedClassAliases.get(ctorText) ?? ctorText;
|
|
1576
|
+
// Tag the node with the constructed class name so the heap-allocation
|
|
1577
|
+
// validator can detect heap allocation by CONSTRUCT (demo #34 Finding A),
|
|
1578
|
+
// not by pattern-matching the `raw` text (whose presence depended on
|
|
1579
|
+
// unrelated import structure).
|
|
1580
|
+
return { kind: "raw", value: `new ${resolvedCtorText}(${argsText})`, newClassName: resolvedCtorText };
|
|
1581
|
+
}
|
|
1582
|
+
if (ts.isStringLiteral(expr)) {
|
|
1583
|
+
return { kind: "string", value: expr.text };
|
|
1584
|
+
}
|
|
1585
|
+
// Handle template literals without interpolation (simple strings)
|
|
1586
|
+
if (ts.isNoSubstitutionTemplateLiteral(expr)) {
|
|
1587
|
+
return { kind: "string", value: expr.text };
|
|
1588
|
+
}
|
|
1589
|
+
// Handle template literals with interpolation - convert to string concatenation
|
|
1590
|
+
if (ts.isTemplateExpression(expr)) {
|
|
1591
|
+
// Build a string concatenation expression from the template literal
|
|
1592
|
+
const parts = [];
|
|
1593
|
+
// Add the head text (before first interpolation)
|
|
1594
|
+
if (expr.head.text) {
|
|
1595
|
+
parts.push({ kind: "string", value: expr.head.text });
|
|
1596
|
+
}
|
|
1597
|
+
// Add each template span (interpolation + trailing text)
|
|
1598
|
+
for (const span of expr.templateSpans) {
|
|
1599
|
+
// Add the interpolated expression (converted to string if needed)
|
|
1600
|
+
const exprIR = expressionToIR(span.expression, sourceText, diagnostics, pointerVars);
|
|
1601
|
+
// Wrap in a template_string conversion - the emitter will handle toString conversion
|
|
1602
|
+
parts.push({ kind: "template_string", expression: exprIR });
|
|
1603
|
+
// Add the trailing text
|
|
1604
|
+
if (span.literal.text) {
|
|
1605
|
+
parts.push({ kind: "string", value: span.literal.text });
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
// If only one part, return it directly
|
|
1609
|
+
if (parts.length === 1) {
|
|
1610
|
+
return parts[0];
|
|
1611
|
+
}
|
|
1612
|
+
// Build concatenation chain
|
|
1613
|
+
return { kind: "string_concat", parts };
|
|
1614
|
+
}
|
|
1615
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
1616
|
+
return { kind: "boolean", value: expr.kind === ts.SyntaxKind.TrueKeyword };
|
|
1617
|
+
}
|
|
1618
|
+
// Handle null keyword
|
|
1619
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
1620
|
+
return { kind: "identifier", value: "nullptr" };
|
|
1621
|
+
}
|
|
1622
|
+
if (expr.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
1623
|
+
return { kind: "identifier", value: "CUTTLEFISH_UNDEFINED" };
|
|
1624
|
+
}
|
|
1625
|
+
if (ts.isIdentifier(expr)) {
|
|
1626
|
+
// Resolve tracked HAL instances to their resolved values
|
|
1627
|
+
const halInst = halInstances.get(expr.text);
|
|
1628
|
+
if (halInst) {
|
|
1629
|
+
// For Pin instances, resolve to the pin number
|
|
1630
|
+
if (halInst.fieldValues.has("_pin")) {
|
|
1631
|
+
return { kind: "raw", value: halInst.fieldValues.get("_pin") };
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
// Apply nested-function-alias mangling so a `return dbl` reference to a
|
|
1635
|
+
// nested function declaration resolves to its hoisted mangled name
|
|
1636
|
+
// (makeScaler__dbl). Without this, the reference emits the bare name and
|
|
1637
|
+
// g++ reports "'dbl' was not declared in scope" (demo #9 Finding B).
|
|
1638
|
+
const nestedAlias = nestedFunctionAliases.get(expr.text);
|
|
1639
|
+
return { kind: "identifier", value: nestedAlias ?? expr.text };
|
|
1640
|
+
}
|
|
1641
|
+
// Handle 'this' keyword
|
|
1642
|
+
if (expr.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1643
|
+
return { kind: "raw", value: "this" };
|
|
1644
|
+
}
|
|
1645
|
+
// Handle property access expressions like obj.property or this.field
|
|
1646
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
1647
|
+
// ── Ambient canvas dims: ctx.width / ctx.height → __ui_canvas_w / _h ──
|
|
1648
|
+
{
|
|
1649
|
+
const canvasCtx = getCanvasAmbientCtx();
|
|
1650
|
+
if (canvasCtx &&
|
|
1651
|
+
ts.isIdentifier(expr.expression) &&
|
|
1652
|
+
expr.expression.text === canvasCtx &&
|
|
1653
|
+
(expr.name.text === "width" || expr.name.text === "height")) {
|
|
1654
|
+
return {
|
|
1655
|
+
kind: "identifier",
|
|
1656
|
+
value: expr.name.text === "width" ? "__ui_canvas_w" : "__ui_canvas_h",
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
// ── UI element .value read: screen.led.value → __ui_nodes[N].value ──
|
|
1661
|
+
if (expr.name.text === "value" &&
|
|
1662
|
+
ts.isPropertyAccessExpression(expr.expression) &&
|
|
1663
|
+
ts.isIdentifier(expr.expression.expression)) {
|
|
1664
|
+
const treeName = expr.expression.expression.text;
|
|
1665
|
+
const elemId = expr.expression.name.text;
|
|
1666
|
+
const nodeIdx = resolveElementValue(treeName, elemId);
|
|
1667
|
+
if (nodeIdx !== undefined) {
|
|
1668
|
+
return { kind: "raw", value: `__ui_nodes[${nodeIdx}].value` };
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
// ── UI element .text read: screen.ssid.text → __ui_nodes[N].textBuffer ──
|
|
1672
|
+
if (expr.name.text === "text" &&
|
|
1673
|
+
ts.isPropertyAccessExpression(expr.expression) &&
|
|
1674
|
+
ts.isIdentifier(expr.expression.expression)) {
|
|
1675
|
+
const treeName = expr.expression.expression.text;
|
|
1676
|
+
const elemId = expr.expression.name.text;
|
|
1677
|
+
const nodeIdx = resolveElementValue(treeName, elemId);
|
|
1678
|
+
if (nodeIdx !== undefined) {
|
|
1679
|
+
return { kind: "raw", value: `__ui_nodes[${nodeIdx}].textBuffer` };
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
const propName = expr.name.kind === ts.SyntaxKind.PrivateIdentifier
|
|
1683
|
+
? `__priv_${expr.name.text.substring(1)}`
|
|
1684
|
+
: expr.name.text;
|
|
1685
|
+
if (isOptionalChainNode(expr)) {
|
|
1686
|
+
diagnostics.push(makeDiagnostic(sourceText, expr.pos, "Optional chaining (?.) is lowered with an approximate null guard in C++; semantics may differ from TypeScript.", "warning", "TS2CPP_OPTIONAL_CHAINING"));
|
|
1687
|
+
const accessText = renderMemberAccessText(expr.expression, propName);
|
|
1688
|
+
return { kind: "raw", value: renderOptionalGuardedAccess(expr.expression, accessText) };
|
|
1689
|
+
}
|
|
1690
|
+
const regRead = tryLowerRegisterRead(expr, sourceText, diagnostics);
|
|
1691
|
+
if (regRead !== null) {
|
|
1692
|
+
return regRead;
|
|
1693
|
+
}
|
|
1694
|
+
if (expr.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1695
|
+
return { kind: "property-access", object: { kind: "raw", value: "this" }, property: propName };
|
|
1696
|
+
}
|
|
1697
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "Math") {
|
|
1698
|
+
if (propName === "random")
|
|
1699
|
+
return { kind: "raw", value: "__tc_random()" };
|
|
1700
|
+
const mathConst = MATH_CONSTANT_LITERALS[propName];
|
|
1701
|
+
if (mathConst !== undefined)
|
|
1702
|
+
return { kind: "number", value: mathConst };
|
|
1703
|
+
return { kind: "raw", value: `std::${propName}` };
|
|
1704
|
+
}
|
|
1705
|
+
const object = expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
1706
|
+
if (propName === "length") {
|
|
1707
|
+
const objectText = renderExprAsText(object);
|
|
1708
|
+
const resolved = resolveLengthProperty(expr.expression, objectText);
|
|
1709
|
+
if (resolved.startsWith("__FILTERED_LEN__")) {
|
|
1710
|
+
return { kind: "identifier", value: resolved.slice("__FILTERED_LEN__".length) };
|
|
1711
|
+
}
|
|
1712
|
+
return { kind: "raw", value: resolved };
|
|
1713
|
+
}
|
|
1714
|
+
// `.size` on a Map/Set (std::map/std::set) — these expose size as a method
|
|
1715
|
+
// (`m.size()`), not a member. Map it to a method call so it doesn't fall
|
|
1716
|
+
// through to the pointer-deref property-access path (which would emit
|
|
1717
|
+
// `m->size` / `this->field.size`). Mirrors the `.length` → `.size()`
|
|
1718
|
+
// lowering for vectors.
|
|
1719
|
+
//
|
|
1720
|
+
// Demo #29 Finding C: this previously resolved the receiver type ONLY for a
|
|
1721
|
+
// bare identifier, so `this.field.size` / `obj.field.size` on a Map/Set
|
|
1722
|
+
// field fell through to the generic property-access path and emitted the
|
|
1723
|
+
// bare member `this->field.size` (g++: "has no member named 'size'"). The
|
|
1724
|
+
// `.length` path (resolveLengthProperty) was already extended to member
|
|
1725
|
+
// receivers in demos #22/#27; this closes the parallel `.size` gap. We now
|
|
1726
|
+
// resolve the receiver via the shared `resolveExprCppType`, which covers
|
|
1727
|
+
// bare identifiers, `this.field`, `obj.field`, and element access uniformly.
|
|
1728
|
+
if (propName === "size") {
|
|
1729
|
+
const receiverType = resolveExprCppType(expr.expression);
|
|
1730
|
+
if (receiverType && (parsedIsMap(receiverType) || parsedIsSet(receiverType))) {
|
|
1731
|
+
const objectText = renderExprAsText(object);
|
|
1732
|
+
return { kind: "raw", value: `static_cast<long long>(${objectText}.size())` };
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
// Use -> for pointer variables in property access
|
|
1736
|
+
if (ts.isIdentifier(expr.expression) && (pointerVars.has(expr.expression.text) || parsedIsPointer(getCurrentIrTypeScope()?.locals.get(expr.expression.text) ?? "") || parsedIsPointer(getCurrentIrTypeScope()?.globals.get(expr.expression.text) ?? ""))) {
|
|
1737
|
+
return {
|
|
1738
|
+
kind: "property-access",
|
|
1739
|
+
object: { kind: "identifier", value: expr.expression.text },
|
|
1740
|
+
property: propName,
|
|
1741
|
+
isPointer: true
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
// Use -> for this->pointerField in property access (e.g. this.player.name)
|
|
1745
|
+
if (ts.isPropertyAccessExpression(expr.expression)
|
|
1746
|
+
&& expr.expression.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
1747
|
+
&& ts.isIdentifier(expr.expression.name)) {
|
|
1748
|
+
const fieldName = expr.expression.name.text;
|
|
1749
|
+
const fieldType = getCurrentIrTypeScope()?.locals.get(`this->${fieldName}`);
|
|
1750
|
+
if (fieldType && parsedIsPointer(fieldType)) {
|
|
1751
|
+
return {
|
|
1752
|
+
kind: "property-access",
|
|
1753
|
+
object: expressionToIR(expr.expression, sourceText, diagnostics, pointerVars),
|
|
1754
|
+
property: propName,
|
|
1755
|
+
isPointer: true,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
// General deep chain: resolve receiver type, use -> if receiver is a pointer
|
|
1760
|
+
const receiverCppType = resolveExprCppType(expr.expression);
|
|
1761
|
+
if (receiverCppType && parsedIsPointer(receiverCppType)) {
|
|
1762
|
+
return {
|
|
1763
|
+
kind: "property-access",
|
|
1764
|
+
object,
|
|
1765
|
+
property: propName,
|
|
1766
|
+
isPointer: true,
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
let isEnum = false;
|
|
1770
|
+
let isNamespace = false;
|
|
1771
|
+
let isStatic = false;
|
|
1772
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
1773
|
+
const name = expr.expression.text;
|
|
1774
|
+
if (activeEnumNames.has(name)) {
|
|
1775
|
+
isEnum = true;
|
|
1776
|
+
}
|
|
1777
|
+
else if (activeNamespaceNames.has(name)) {
|
|
1778
|
+
isNamespace = true;
|
|
1779
|
+
}
|
|
1780
|
+
else if (topLevelClassNames.has(name) || nestedClassAliases.has(name)) {
|
|
1781
|
+
isStatic = true;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
else if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
1785
|
+
let root = expr.expression;
|
|
1786
|
+
while (ts.isPropertyAccessExpression(root)) {
|
|
1787
|
+
root = root.expression;
|
|
1788
|
+
}
|
|
1789
|
+
if (ts.isIdentifier(root) && activeNamespaceNames.has(root.text)) {
|
|
1790
|
+
isNamespace = true;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
let isPointer = false;
|
|
1794
|
+
if (ts.isIdentifier(expr.expression) && pointerVars.has(expr.expression.text)) {
|
|
1795
|
+
isPointer = true;
|
|
1796
|
+
}
|
|
1797
|
+
else if (expr.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
1798
|
+
isPointer = true;
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
kind: "property-access",
|
|
1802
|
+
object,
|
|
1803
|
+
property: propName,
|
|
1804
|
+
isEnum,
|
|
1805
|
+
isNamespace,
|
|
1806
|
+
isStatic,
|
|
1807
|
+
isPointer
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
if (ts.isElementAccessExpression(expr)) {
|
|
1811
|
+
const object = expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
1812
|
+
const index = expressionToIR(expr.argumentExpression, sourceText, diagnostics, pointerVars);
|
|
1813
|
+
if (index.kind === "number" && Number.isInteger(index.value)) {
|
|
1814
|
+
let objectType;
|
|
1815
|
+
let elementType;
|
|
1816
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
1817
|
+
objectType = getCurrentIrTypeScope()?.locals.get(expr.expression.text) ?? getCurrentIrTypeScope()?.globals.get(expr.expression.text);
|
|
1818
|
+
if (objectType) {
|
|
1819
|
+
elementType = parsedElementString(objectType);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
else if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
1823
|
+
const receiverNode = expr.expression.expression;
|
|
1824
|
+
if (ts.isIdentifier(receiverNode)) {
|
|
1825
|
+
const receiverType = getCurrentIrTypeScope()?.locals.get(receiverNode.text) ?? getCurrentIrTypeScope()?.globals.get(receiverNode.text);
|
|
1826
|
+
if (receiverType) {
|
|
1827
|
+
const className = parsedBareString(receiverType);
|
|
1828
|
+
const classDef = topLevelClasses.get(className);
|
|
1829
|
+
if (classDef) {
|
|
1830
|
+
const fieldName = expr.expression.name.text;
|
|
1831
|
+
const field = classDef.fields.find((f) => f.name === fieldName);
|
|
1832
|
+
if (field) {
|
|
1833
|
+
objectType = field.cppType;
|
|
1834
|
+
elementType = parsedElementString(field.cppType);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
if (typeof objectType === "string" && parsedIsTuple(objectType)) {
|
|
1841
|
+
return { kind: "tuple-access", object, index: index.value };
|
|
1842
|
+
}
|
|
1843
|
+
return { kind: "element-access", object, index, elementType };
|
|
1844
|
+
}
|
|
1845
|
+
return { kind: "element-access", object, index };
|
|
1846
|
+
}
|
|
1847
|
+
// Handle ternary/conditional expressions: a ? b : c
|
|
1848
|
+
if (ts.isConditionalExpression(expr)) {
|
|
1849
|
+
return {
|
|
1850
|
+
kind: "ternary",
|
|
1851
|
+
condition: expressionToIR(expr.condition, sourceText, diagnostics),
|
|
1852
|
+
whenTrue: expressionToIR(expr.whenTrue, sourceText, diagnostics),
|
|
1853
|
+
whenFalse: expressionToIR(expr.whenFalse, sourceText, diagnostics),
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
// Handle array literals
|
|
1857
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
1858
|
+
// Check for spread element in array
|
|
1859
|
+
const spreadIndex = expr.elements.findIndex(e => ts.isSpreadElement(e));
|
|
1860
|
+
if (spreadIndex !== -1) {
|
|
1861
|
+
// Handle spread in array: [...arr, x, y]
|
|
1862
|
+
const spreadElement = expr.elements[spreadIndex];
|
|
1863
|
+
if (ts.isSpreadElement(spreadElement)) {
|
|
1864
|
+
// If spread source is a known-size array variable, expand inline
|
|
1865
|
+
if (ts.isIdentifier(spreadElement.expression) && arrayLiteralSizes.has(spreadElement.expression.text)) {
|
|
1866
|
+
const srcName = spreadElement.expression.text;
|
|
1867
|
+
const srcSize = arrayLiteralSizes.get(srcName);
|
|
1868
|
+
const expandedElements = [];
|
|
1869
|
+
for (let i = 0; i < srcSize; i++) {
|
|
1870
|
+
expandedElements.push({ kind: "raw", value: `${srcName}[${i}]` });
|
|
1871
|
+
}
|
|
1872
|
+
for (const elem of expr.elements.slice(spreadIndex + 1)) {
|
|
1873
|
+
expandedElements.push(expressionToIR(elem, sourceText, diagnostics));
|
|
1874
|
+
}
|
|
1875
|
+
return { kind: "array", elementType: "auto", elements: expandedElements };
|
|
1876
|
+
}
|
|
1877
|
+
const spreadExpr = expressionToIR(spreadElement.expression, sourceText, diagnostics);
|
|
1878
|
+
const additionalElements = expr.elements
|
|
1879
|
+
.slice(spreadIndex + 1)
|
|
1880
|
+
.map(e => expressionToIR(e, sourceText, diagnostics));
|
|
1881
|
+
// Emit as spread_array IR node
|
|
1882
|
+
return {
|
|
1883
|
+
kind: "spread_array",
|
|
1884
|
+
elementType: "auto",
|
|
1885
|
+
spreadExpr,
|
|
1886
|
+
additionalElements
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const nonOmittedElements = expr.elements.filter(e => e.kind !== ts.SyntaxKind.OmittedExpression);
|
|
1891
|
+
const elements = nonOmittedElements
|
|
1892
|
+
.map((e) => expressionToIR(e, sourceText, diagnostics));
|
|
1893
|
+
// Resolve the element cppType when possible so downstream renderers can emit
|
|
1894
|
+
// a TYPED `std::vector<ElemType>{...}`. This matters when an inline array
|
|
1895
|
+
// literal is the receiver of a `__tc_*` template helper
|
|
1896
|
+
// (`[...].join(sep)`, `[...].concat(x)`, ...): a bare `{...}` cannot drive
|
|
1897
|
+
// template argument deduction, but `std::vector<std::string>{...}` can.
|
|
1898
|
+
// Infer from the first element's resolved cppType; fall back to `"auto"`
|
|
1899
|
+
// (the historical default) when no element carries a type. Demo #29 Finding D.
|
|
1900
|
+
const elementType = inferArrayElementType(nonOmittedElements);
|
|
1901
|
+
return { kind: "array", elementType, elements };
|
|
1902
|
+
}
|
|
1903
|
+
// Handle object literals - suppress warning for compile-time type contexts
|
|
1904
|
+
// Object literals in board package files are often type-asserted to pin interfaces
|
|
1905
|
+
// These are compile-time constructs that don't need C++ emission
|
|
1906
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
1907
|
+
const fields = [];
|
|
1908
|
+
const spreadSources = [];
|
|
1909
|
+
for (const prop of expr.properties) {
|
|
1910
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
1911
|
+
const name = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.getText();
|
|
1912
|
+
fields.push({
|
|
1913
|
+
name,
|
|
1914
|
+
value: expressionToIR(prop.initializer, sourceText, diagnostics, pointerVars),
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
1918
|
+
fields.push({
|
|
1919
|
+
name: prop.name.text,
|
|
1920
|
+
value: { kind: "identifier", value: prop.name.text },
|
|
1921
|
+
});
|
|
1922
|
+
}
|
|
1923
|
+
else if (ts.isMethodDeclaration(prop)) {
|
|
1924
|
+
const name = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.getText();
|
|
1925
|
+
fields.push({
|
|
1926
|
+
name,
|
|
1927
|
+
value: { kind: "raw", value: "/* method stub */" },
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
else if (ts.isAccessor(prop)) {
|
|
1931
|
+
const name = ts.isIdentifier(prop.name) ? prop.name.text : prop.name.getText();
|
|
1932
|
+
fields.push({
|
|
1933
|
+
name,
|
|
1934
|
+
value: { kind: "raw", value: "/* accessor stub */" },
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
else if (ts.isSpreadAssignment(prop)) {
|
|
1938
|
+
spreadSources.push(expressionToIR(prop.expression, sourceText, diagnostics, pointerVars));
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
if (fields.length === 0 && spreadSources.length > 0) {
|
|
1942
|
+
const allFields = [];
|
|
1943
|
+
for (const src of spreadSources) {
|
|
1944
|
+
allFields.push({ name: "__spread__", value: src });
|
|
1945
|
+
}
|
|
1946
|
+
return { kind: "object", fields: allFields };
|
|
1947
|
+
}
|
|
1948
|
+
if (spreadSources.length > 0 && fields.length > 0) {
|
|
1949
|
+
const allFields = [];
|
|
1950
|
+
for (const src of spreadSources) {
|
|
1951
|
+
const srcText = renderExprAsText(src);
|
|
1952
|
+
allFields.push({ name: "__spread__", value: src });
|
|
1953
|
+
}
|
|
1954
|
+
allFields.push(...fields);
|
|
1955
|
+
return { kind: "object", fields: allFields };
|
|
1956
|
+
}
|
|
1957
|
+
return { kind: "object", fields };
|
|
1958
|
+
}
|
|
1959
|
+
// Handle function expressions and arrow functions in compile-time contexts
|
|
1960
|
+
// These are stubs in board package files that get replaced by transpiler magic
|
|
1961
|
+
// For interrupt handlers, we need to generate a proper callback function
|
|
1962
|
+
if (ts.isFunctionExpression(expr) || ts.isArrowFunction(expr)) {
|
|
1963
|
+
const body = expr.body;
|
|
1964
|
+
// Resolve each parameter's C++ type from its type annotation rather than
|
|
1965
|
+
// hardcoding "auto". This matters for callbacks hoisted to free functions
|
|
1966
|
+
// (top-level-prep.ts collectCallbackFromExpression): the ISR signature is
|
|
1967
|
+
// rendered from these cppTypes, and an "auto" param is filtered out, so a
|
|
1968
|
+
// `.map((n) => ...)` callback would lower to `void X_isr_N()` (no params)
|
|
1969
|
+
// and the __tc_map template couldn't deduce the element/result types.
|
|
1970
|
+
// Default to "auto" only when the param has no annotation.
|
|
1971
|
+
const paramList = [];
|
|
1972
|
+
const lambdaLocalTypes = new Map();
|
|
1973
|
+
for (const param of expr.parameters) {
|
|
1974
|
+
if (ts.isIdentifier(param.name)) {
|
|
1975
|
+
const resolved = typeNodeToCppType(param.type, undefined);
|
|
1976
|
+
const cppType = resolved && resolved !== "auto" ? resolved : "auto";
|
|
1977
|
+
paramList.push({
|
|
1978
|
+
name: param.name.text,
|
|
1979
|
+
cppType,
|
|
1980
|
+
});
|
|
1981
|
+
if (cppType !== "auto") {
|
|
1982
|
+
lambdaLocalTypes.set(param.name.text, cppType);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
const isBlock = ts.isBlock(body);
|
|
1987
|
+
const bodyStmts = isBlock
|
|
1988
|
+
? body.statements.map(stmt => {
|
|
1989
|
+
const lowered = lowerStatement(stmt, "", sourceText, diagnostics, new Map(), new Map(), "<lambda>", new Map(), pointerVars);
|
|
1990
|
+
const stmts = lowered ?? [{ kind: "call", sourceSpan: makeSourceSpan(stmt, "", sourceText), callee: "__EMIT__", args: [{ kind: "string", value: "" }] }];
|
|
1991
|
+
return stmts;
|
|
1992
|
+
}).flat()
|
|
1993
|
+
: [{
|
|
1994
|
+
kind: "return",
|
|
1995
|
+
sourceSpan: makeSourceSpan(body, "", sourceText),
|
|
1996
|
+
value: expressionToIR(body, sourceText, diagnostics, pointerVars),
|
|
1997
|
+
}];
|
|
1998
|
+
// Infer return type: use explicit annotation, or infer from body. Thread
|
|
1999
|
+
// the lambda's own param types into the inference so a body like
|
|
2000
|
+
// `(n) => n.capacity` can resolve `n` and deduce the return type.
|
|
2001
|
+
// When the body returns a value but the type can't be resolved (common when
|
|
2002
|
+
// the param is an interface, whose fields aren't in the class registry),
|
|
2003
|
+
// fall back to "auto" (C++14 return-type deduction) rather than "void" —
|
|
2004
|
+
// this lets the __tc_map/__tc_filter template helpers deduce the result
|
|
2005
|
+
// type via decltype(fn(v[0])) instead of failing on a void(auto) callable.
|
|
2006
|
+
let inferredReturnType;
|
|
2007
|
+
const explicitReturnType = typeNodeToCppType(expr.type, undefined);
|
|
2008
|
+
if (explicitReturnType !== "auto" && explicitReturnType !== "void") {
|
|
2009
|
+
inferredReturnType = explicitReturnType;
|
|
2010
|
+
}
|
|
2011
|
+
else if (isBlock) {
|
|
2012
|
+
const returns = collectReturns(body).filter((item) => item.expression);
|
|
2013
|
+
const returnTypes = returns
|
|
2014
|
+
.map((item) => inferExprCppType(item.expression, new Map(), lambdaLocalTypes, sourceText))
|
|
2015
|
+
.filter((item) => item !== "auto");
|
|
2016
|
+
if (returnTypes.includes("float") || returnTypes.includes("double")) {
|
|
2017
|
+
inferredReturnType = "double";
|
|
2018
|
+
}
|
|
2019
|
+
else if (returnTypes.includes("std::string")) {
|
|
2020
|
+
inferredReturnType = "std::string";
|
|
2021
|
+
}
|
|
2022
|
+
else if (returnTypes.includes("int") || returnTypes.includes("bool")) {
|
|
2023
|
+
inferredReturnType = "int";
|
|
2024
|
+
}
|
|
2025
|
+
else if (returnTypes.length > 0) {
|
|
2026
|
+
inferredReturnType = returnTypes[0];
|
|
2027
|
+
}
|
|
2028
|
+
else {
|
|
2029
|
+
// Body has returns but the types didn't resolve (e.g. interface-typed
|
|
2030
|
+
// param). Use C++14 auto return deduction so the callback is callable.
|
|
2031
|
+
inferredReturnType = returns.length > 0 ? "auto" : "void";
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
else {
|
|
2035
|
+
inferredReturnType = inferExprCppType(body, new Map(), lambdaLocalTypes, sourceText);
|
|
2036
|
+
if (inferredReturnType === "auto") {
|
|
2037
|
+
// Expression body whose type didn't resolve — still prefer auto over
|
|
2038
|
+
// void so the value is returned (not dropped).
|
|
2039
|
+
inferredReturnType = "auto";
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
const lambdaParams = paramList.map(p => ({ name: p.name, cppType: p.cppType }));
|
|
2043
|
+
return { kind: "lambda", params: lambdaParams, body: bodyStmts, returnType: inferredReturnType, isExpressionBody: !isBlock };
|
|
2044
|
+
}
|
|
2045
|
+
// Handle instanceof expressions
|
|
2046
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
|
2047
|
+
const object = expressionToIR(expr.left, sourceText, diagnostics);
|
|
2048
|
+
const className = expr.right.getText();
|
|
2049
|
+
return { kind: "instanceof", object, className };
|
|
2050
|
+
}
|
|
2051
|
+
// Handle class expressions (anonymous classes assigned to variables)
|
|
2052
|
+
if (ts.isClassExpression(expr)) {
|
|
2053
|
+
const className = expr.name ? expr.name.text : `__tc_anonClass`;
|
|
2054
|
+
const members = [];
|
|
2055
|
+
let ctor;
|
|
2056
|
+
const fields = [];
|
|
2057
|
+
const methods = [];
|
|
2058
|
+
const getters = [];
|
|
2059
|
+
const setters = [];
|
|
2060
|
+
for (const member of expr.members) {
|
|
2061
|
+
if (ts.isConstructorDeclaration(member)) {
|
|
2062
|
+
const ctorParams = [];
|
|
2063
|
+
for (const param of member.parameters) {
|
|
2064
|
+
if (ts.isIdentifier(param.name)) {
|
|
2065
|
+
ctorParams.push({ name: param.name.text, cppType: "auto", isRest: !!param.dotDotDotToken });
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
const ctorBody = member.body
|
|
2069
|
+
? [...member.body.statements].map(s => {
|
|
2070
|
+
const lowered = lowerStatement(s, "", sourceText, diagnostics, new Map(), new Map(), "<anon-class>", new Map(), pointerVars);
|
|
2071
|
+
return lowered ?? [];
|
|
2072
|
+
}).flat()
|
|
2073
|
+
: [];
|
|
2074
|
+
ctor = { parameters: ctorParams, statements: ctorBody };
|
|
2075
|
+
continue;
|
|
2076
|
+
}
|
|
2077
|
+
if (ts.isMethodDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
2078
|
+
const methodBody = member.body
|
|
2079
|
+
? [...member.body.statements].map(s => {
|
|
2080
|
+
const lowered = lowerStatement(s, "", sourceText, diagnostics, new Map(), new Map(), `<anon-class>.${member.name.getText()}`, new Map(), pointerVars);
|
|
2081
|
+
return lowered ?? [];
|
|
2082
|
+
}).flat()
|
|
2083
|
+
: [];
|
|
2084
|
+
methods.push({
|
|
2085
|
+
name: member.name.text,
|
|
2086
|
+
returnType: "auto",
|
|
2087
|
+
parameters: [],
|
|
2088
|
+
statements: methodBody,
|
|
2089
|
+
visibility: "public",
|
|
2090
|
+
isStatic: false,
|
|
2091
|
+
isAbstract: false,
|
|
2092
|
+
});
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2095
|
+
if (ts.isPropertyDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
2096
|
+
fields.push({
|
|
2097
|
+
name: member.name.text,
|
|
2098
|
+
cppType: "auto",
|
|
2099
|
+
visibility: "public",
|
|
2100
|
+
initializer: member.initializer ? expressionToIR(member.initializer, sourceText, diagnostics, pointerVars) : undefined,
|
|
2101
|
+
});
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
if (ts.isGetAccessorDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
2105
|
+
const getterBody = member.body
|
|
2106
|
+
? [...member.body.statements].map(s => {
|
|
2107
|
+
const lowered = lowerStatement(s, "", sourceText, diagnostics, new Map(), new Map(), `<anon-class>.get:${member.name.getText()}`, new Map(), pointerVars);
|
|
2108
|
+
return lowered ?? [];
|
|
2109
|
+
}).flat()
|
|
2110
|
+
: [];
|
|
2111
|
+
getters.push({
|
|
2112
|
+
name: member.name.text,
|
|
2113
|
+
returnType: "auto",
|
|
2114
|
+
statements: getterBody,
|
|
2115
|
+
visibility: "public",
|
|
2116
|
+
isStatic: false,
|
|
2117
|
+
});
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
if (ts.isSetAccessorDeclaration(member) && member.name && ts.isIdentifier(member.name)) {
|
|
2121
|
+
const setterBody = member.body
|
|
2122
|
+
? [...member.body.statements].map(s => {
|
|
2123
|
+
const lowered = lowerStatement(s, "", sourceText, diagnostics, new Map(), new Map(), `<anon-class>.set:${member.name.getText()}`, new Map(), pointerVars);
|
|
2124
|
+
return lowered ?? [];
|
|
2125
|
+
}).flat()
|
|
2126
|
+
: [];
|
|
2127
|
+
setters.push({
|
|
2128
|
+
name: member.name.text,
|
|
2129
|
+
parameter: { name: "value", cppType: "auto", isRest: false },
|
|
2130
|
+
statements: setterBody,
|
|
2131
|
+
visibility: "public",
|
|
2132
|
+
isStatic: false,
|
|
2133
|
+
});
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
const syntheticSpan = { filePath: "", startOffset: 0, endOffset: 0, startLine: 0, startColumn: 0, endLine: 0, endColumn: 0 };
|
|
2138
|
+
hoistedNestedClasses.push({
|
|
2139
|
+
name: className,
|
|
2140
|
+
isAbstract: false,
|
|
2141
|
+
sourceSpan: syntheticSpan,
|
|
2142
|
+
fields,
|
|
2143
|
+
methods,
|
|
2144
|
+
getters,
|
|
2145
|
+
setters,
|
|
2146
|
+
...(ctor ? { constructor: ctor } : {}),
|
|
2147
|
+
});
|
|
2148
|
+
topLevelClassNames.add(className);
|
|
2149
|
+
classTypeNames.add(className);
|
|
2150
|
+
const extendsClass = expr.heritageClauses
|
|
2151
|
+
?.find((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword)
|
|
2152
|
+
?.types[0]?.expression?.getText();
|
|
2153
|
+
if (extendsClass) {
|
|
2154
|
+
hoistedNestedClasses[hoistedNestedClasses.length - 1].extendsClass = extendsClass;
|
|
2155
|
+
}
|
|
2156
|
+
return { kind: "identifier", value: className };
|
|
2157
|
+
}
|
|
2158
|
+
// Handle delete expressions: delete obj.key → map.erase(key)
|
|
2159
|
+
if (ts.isDeleteExpression(expr)) {
|
|
2160
|
+
const target = expr.expression;
|
|
2161
|
+
if (ts.isPropertyAccessExpression(target)) {
|
|
2162
|
+
const keyText = target.name.text;
|
|
2163
|
+
const objNode = target.expression;
|
|
2164
|
+
const objText = renderExprAsText(expressionToIR(objNode, sourceText, diagnostics, pointerVars));
|
|
2165
|
+
let objType;
|
|
2166
|
+
if (ts.isIdentifier(objNode)) {
|
|
2167
|
+
objType = getCurrentIrTypeScope()?.locals.get(objNode.text) ?? getCurrentIrTypeScope()?.globals.get(objNode.text);
|
|
2168
|
+
}
|
|
2169
|
+
if (objType && (parsedIsMap(objType) || parsedIsSet(objType))) {
|
|
2170
|
+
return { kind: "raw", value: `(${objText}.erase(${keyText}), true)` };
|
|
2171
|
+
}
|
|
2172
|
+
if (objType && parsedIsVector(objType)) {
|
|
2173
|
+
const elemText = renderExprAsText(expressionToIR(target, sourceText, diagnostics, pointerVars));
|
|
2174
|
+
return { kind: "raw", value: `(${objText}.erase(std::find(${objText}.begin(), ${objText}.end(), ${keyText})), true)` };
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
if (ts.isElementAccessExpression(target)) {
|
|
2178
|
+
const keyIR = expressionToIR(target.argumentExpression, sourceText, diagnostics, pointerVars);
|
|
2179
|
+
const objNode = target.expression;
|
|
2180
|
+
const objText = renderExprAsText(expressionToIR(objNode, sourceText, diagnostics, pointerVars));
|
|
2181
|
+
const keyText = renderExprAsText(keyIR);
|
|
2182
|
+
let objType;
|
|
2183
|
+
if (ts.isIdentifier(objNode)) {
|
|
2184
|
+
objType = getCurrentIrTypeScope()?.locals.get(objNode.text) ?? getCurrentIrTypeScope()?.globals.get(objNode.text);
|
|
2185
|
+
}
|
|
2186
|
+
if (objType && parsedIsMap(objType)) {
|
|
2187
|
+
return { kind: "raw", value: `(${objText}.erase(${keyText}) > 0)` };
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
if (ts.isPropertyAccessExpression(target)) {
|
|
2191
|
+
const keyText = target.name.text;
|
|
2192
|
+
const objNode = target.expression;
|
|
2193
|
+
const objText = renderExprAsText(expressionToIR(objNode, sourceText, diagnostics, pointerVars));
|
|
2194
|
+
let objType;
|
|
2195
|
+
if (ts.isIdentifier(objNode)) {
|
|
2196
|
+
objType = getCurrentIrTypeScope()?.locals.get(objNode.text) ?? getCurrentIrTypeScope()?.globals.get(objNode.text);
|
|
2197
|
+
}
|
|
2198
|
+
if (objType) {
|
|
2199
|
+
const fieldTypeName = objType;
|
|
2200
|
+
let defaultValue = "0";
|
|
2201
|
+
if (fieldTypeName === "std::string" || fieldTypeName === "const char*")
|
|
2202
|
+
defaultValue = "\"\"";
|
|
2203
|
+
else if (fieldTypeName === "bool")
|
|
2204
|
+
defaultValue = "false";
|
|
2205
|
+
else if (parsedIsPointer(fieldTypeName))
|
|
2206
|
+
defaultValue = "nullptr";
|
|
2207
|
+
return { kind: "raw", value: `(${objText}.${keyText} = ${defaultValue}, true)` };
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return emitUnsupportedExpression("delete on non-map types is unsupported in C++.");
|
|
2211
|
+
}
|
|
2212
|
+
// Handle comma expressions: (a, b, c) → evaluate all, return last
|
|
2213
|
+
if (ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.CommaToken) {
|
|
2214
|
+
const leftIR = expressionToIR(expr.left, sourceText, diagnostics, pointerVars);
|
|
2215
|
+
const rightIR = expressionToIR(expr.right, sourceText, diagnostics, pointerVars);
|
|
2216
|
+
const leftText = renderExprAsText(leftIR);
|
|
2217
|
+
const rightText = renderExprAsText(rightIR);
|
|
2218
|
+
return { kind: "raw", value: `(${leftText}, ${rightText})` };
|
|
2219
|
+
}
|
|
2220
|
+
// Handle tagged template expressions (e.g., tag`template`)
|
|
2221
|
+
if (ts.isTaggedTemplateExpression(expr)) {
|
|
2222
|
+
return emitUnsupportedExpression("Tagged template expressions are unsupported in the C++ transpiler and were lowered to a placeholder.");
|
|
2223
|
+
}
|
|
2224
|
+
// Handle meta properties (e.g., import.meta, new.target)
|
|
2225
|
+
if (ts.isMetaProperty(expr)) {
|
|
2226
|
+
return emitUnsupportedExpression("Meta-property expressions are unsupported in the C++ transpiler and were lowered to a placeholder.");
|
|
2227
|
+
}
|
|
2228
|
+
if (ts.isNonNullExpression(expr)) {
|
|
2229
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
2230
|
+
}
|
|
2231
|
+
if (expr.kind === ts.SyntaxKind.SuperKeyword) {
|
|
2232
|
+
const extendsClass = getActiveExtendsClass();
|
|
2233
|
+
if (extendsClass) {
|
|
2234
|
+
return { kind: "raw", value: extendsClass };
|
|
2235
|
+
}
|
|
2236
|
+
return emitUnsupportedExpression("super keyword outside of class method");
|
|
2237
|
+
}
|
|
2238
|
+
if (ts.isPartiallyEmittedExpression(expr)) {
|
|
2239
|
+
return expressionToIR(expr.expression, sourceText, diagnostics, pointerVars);
|
|
2240
|
+
}
|
|
2241
|
+
if (expr.kind === ts.SyntaxKind.YieldExpression || ts.isYieldExpression?.(expr)) {
|
|
2242
|
+
const yieldExpr = expr;
|
|
2243
|
+
const value = yieldExpr.expression
|
|
2244
|
+
? expressionToIR(yieldExpr.expression, sourceText, diagnostics, pointerVars)
|
|
2245
|
+
: undefined;
|
|
2246
|
+
return { kind: "raw", value: value ? `co_yield ${renderExprAsText(value)}` : "co_yield" };
|
|
2247
|
+
}
|
|
2248
|
+
return emitUnsupportedExpression(`Unsupported expression kind '${ts.SyntaxKind[expr.kind] ?? expr.kind}' was lowered to a placeholder.`);
|
|
2249
|
+
}
|