@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,1477 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression Renderer for C++ code emission.
|
|
3
|
+
* Encapsulates all expression rendering logic with explicit dependencies.
|
|
4
|
+
* Extracted from cpp-emitter.ts
|
|
5
|
+
*/
|
|
6
|
+
import { routeHALOp } from "./route-hal-op.js";
|
|
7
|
+
import { extractPropertyChain } from "../ir/extract-property-chain.js";
|
|
8
|
+
import { escapeCppKeyword, escapeCppStringLiteral } from "../utils/strings.js";
|
|
9
|
+
import { accessorGetterName } from "./utils/cpp-helpers.js";
|
|
10
|
+
import { INTEGRAL_CPP_TYPE_RE } from "./utils/cpp-helpers.js";
|
|
11
|
+
import { renderPeripheralProperty } from "../mapping/peripheral-names.js";
|
|
12
|
+
import { parseCppType, renderCppType, bareType, parsedIsPointer, parsedIsVector, needsCStrForStringLike } from "../api/shared/cpp-type-ir.js";
|
|
13
|
+
/**
|
|
14
|
+
* Renders ExpressionIR nodes to C++ code strings.
|
|
15
|
+
*/
|
|
16
|
+
export class ExpressionRenderer {
|
|
17
|
+
constructor(context) {
|
|
18
|
+
/** Accumulated snprintf prelude lines (buffer declarations, dtostrf calls, snprintf calls). */
|
|
19
|
+
this._preludeLines = [];
|
|
20
|
+
this.strategy = context.strategy;
|
|
21
|
+
this.boardConstants = context.boardConstants;
|
|
22
|
+
this.classNameMap = context.classNameMap;
|
|
23
|
+
this.enumNames = context.enumNames;
|
|
24
|
+
this.stringEnumNames = context.stringEnumNames ?? new Set();
|
|
25
|
+
this.largeEnumNames = context.largeEnumNames;
|
|
26
|
+
this.knownFunctionReturnTypes = context.knownFunctionReturnTypes;
|
|
27
|
+
this.knownVariableTypes = context.knownVariableTypes;
|
|
28
|
+
this.pointerVarTypes = context.pointerVarTypes;
|
|
29
|
+
this.globalPointerVarTypes = context.globalPointerVarTypes;
|
|
30
|
+
this.stringVarNames = context.stringVarNames;
|
|
31
|
+
this.cArrayVarNames = context.cArrayVarNames;
|
|
32
|
+
this.namespaceNames = context.namespaceNames ?? new Set();
|
|
33
|
+
this.varAccessorNames = context.varAccessorNames ?? new Map();
|
|
34
|
+
this.typeAccessorNames = context.typeAccessorNames ?? new Map();
|
|
35
|
+
this.interfaceFieldTypes = context.interfaceFieldTypes ?? new Map();
|
|
36
|
+
this.knownTopLevelObjectTypes = context.knownTopLevelObjectTypes;
|
|
37
|
+
this._snprintfCounter = context.snprintfCounter ?? { value: 0 };
|
|
38
|
+
this._diagnostics = context.diagnostics ?? [];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Gets the board constants (for access by statement renderer).
|
|
42
|
+
*/
|
|
43
|
+
getBoardConstants() {
|
|
44
|
+
return this.boardConstants;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Clear accumulated prelude lines. Call before each top-level render.
|
|
48
|
+
*/
|
|
49
|
+
clearPrelude() {
|
|
50
|
+
this._preludeLines = [];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Drain accumulated prelude lines and clear. The caller is responsible
|
|
54
|
+
* for emitting these lines before the statement that uses the expression.
|
|
55
|
+
*/
|
|
56
|
+
drainPrelude() {
|
|
57
|
+
const lines = this._preludeLines;
|
|
58
|
+
this._preludeLines = [];
|
|
59
|
+
return lines;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Push lines directly into the prelude buffer. Used by the statement renderer
|
|
63
|
+
* to inject multi-statement expansions (e.g. Wire read setup) that must appear
|
|
64
|
+
* before the surrounding statement.
|
|
65
|
+
*/
|
|
66
|
+
pushPrelude(lines) {
|
|
67
|
+
this._preludeLines.push(...lines);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Renders an expression IR node to a C++ string.
|
|
71
|
+
*
|
|
72
|
+
* @param expr The expression to render
|
|
73
|
+
* @param exprTransformer Optional transformer for raw expressions
|
|
74
|
+
* @param knownVariableTypes Optional override for variable type mapping
|
|
75
|
+
* @returns The C++ code string
|
|
76
|
+
*/
|
|
77
|
+
render(expr, exprTransformer, knownVariableTypes) {
|
|
78
|
+
// Safety check
|
|
79
|
+
if (!expr || typeof expr !== 'object' || !expr.kind) {
|
|
80
|
+
return "/* invalid expression */";
|
|
81
|
+
}
|
|
82
|
+
let rendered;
|
|
83
|
+
switch (expr.kind) {
|
|
84
|
+
case "number": {
|
|
85
|
+
if (expr.cppType === "float" || !Number.isInteger(expr.value)) {
|
|
86
|
+
const str = `${expr.value}`;
|
|
87
|
+
rendered = str.includes('.') || str.includes('e') || str.includes('E')
|
|
88
|
+
? `${str}f`
|
|
89
|
+
: `${str}.0f`;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
rendered = `${expr.value}`;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "string":
|
|
96
|
+
rendered = `"${expr.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
97
|
+
break;
|
|
98
|
+
case "boolean":
|
|
99
|
+
rendered = expr.value ? "true" : "false";
|
|
100
|
+
break;
|
|
101
|
+
case "identifier":
|
|
102
|
+
rendered = this.renderIdentifier(expr.value);
|
|
103
|
+
break;
|
|
104
|
+
case "raw":
|
|
105
|
+
rendered = this.renderRaw(expr.value, exprTransformer);
|
|
106
|
+
break;
|
|
107
|
+
case "await":
|
|
108
|
+
rendered = this.render(expr.value, exprTransformer);
|
|
109
|
+
break;
|
|
110
|
+
case "ternary":
|
|
111
|
+
rendered = this.renderTernary(expr, exprTransformer);
|
|
112
|
+
break;
|
|
113
|
+
case "string_concat":
|
|
114
|
+
rendered = this.renderStringConcat(expr, exprTransformer, knownVariableTypes);
|
|
115
|
+
break;
|
|
116
|
+
case "template_string":
|
|
117
|
+
rendered = this.renderTemplateString(expr, exprTransformer, knownVariableTypes);
|
|
118
|
+
break;
|
|
119
|
+
case "array":
|
|
120
|
+
rendered = this.renderArray(expr, exprTransformer);
|
|
121
|
+
break;
|
|
122
|
+
case "object":
|
|
123
|
+
rendered = this.renderObject(expr, exprTransformer);
|
|
124
|
+
break;
|
|
125
|
+
case "instanceof":
|
|
126
|
+
rendered = this.renderInstanceof(expr, exprTransformer);
|
|
127
|
+
break;
|
|
128
|
+
case "spread_array":
|
|
129
|
+
rendered = this.renderSpreadArray(expr, exprTransformer);
|
|
130
|
+
break;
|
|
131
|
+
case "paren":
|
|
132
|
+
rendered = `(${this.render(expr.inner, exprTransformer)})`;
|
|
133
|
+
break;
|
|
134
|
+
case "binary":
|
|
135
|
+
rendered = this.renderBinary(expr, exprTransformer, knownVariableTypes);
|
|
136
|
+
break;
|
|
137
|
+
case "unary":
|
|
138
|
+
rendered = this.renderUnary(expr, exprTransformer);
|
|
139
|
+
break;
|
|
140
|
+
case "property-access":
|
|
141
|
+
rendered = this.renderPropertyAccess(expr, exprTransformer, knownVariableTypes);
|
|
142
|
+
break;
|
|
143
|
+
case "callback":
|
|
144
|
+
rendered = this.renderCallback(expr);
|
|
145
|
+
break;
|
|
146
|
+
case "lambda":
|
|
147
|
+
rendered = this.renderLambda(expr, exprTransformer);
|
|
148
|
+
break;
|
|
149
|
+
case "method-call":
|
|
150
|
+
rendered = this.renderMethodCall(expr, exprTransformer);
|
|
151
|
+
break;
|
|
152
|
+
case "element-access":
|
|
153
|
+
rendered = this.renderElementAccess(expr, exprTransformer, knownVariableTypes);
|
|
154
|
+
break;
|
|
155
|
+
case "hal-expr": {
|
|
156
|
+
const resolved = routeHALOp(expr.operation, this.strategy);
|
|
157
|
+
if (resolved?.expression) {
|
|
158
|
+
rendered = resolved.expression;
|
|
159
|
+
}
|
|
160
|
+
else if (resolved?.code) {
|
|
161
|
+
// Strip trailing semicolon and a leading `return ` for expression
|
|
162
|
+
// context. HAL rawCpp definitions (e.g. Preferences.getString) bake
|
|
163
|
+
// in `return X;` for statement context; in expression context the
|
|
164
|
+
// `return` keyword is invalid (avr-g++: "expected primary-expression
|
|
165
|
+
// before 'return'") and would leak as
|
|
166
|
+
// `strcmp(return Preferences.getString(...), ...)`. Demo #33 Finding C.
|
|
167
|
+
rendered = resolved.code.replace(/;\s*$/, "").replace(/^\s*return\s+/, "");
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Unregistered HAL op: surface as a warning so the user sees it, but
|
|
171
|
+
// keep HAL as an extensibility point. The bare comment is retained
|
|
172
|
+
// as a visual marker in the generated C++.
|
|
173
|
+
this._diagnostics.push({
|
|
174
|
+
severity: "warning",
|
|
175
|
+
code: "TS2CPP_UNHANDLED_HAL",
|
|
176
|
+
message: `HAL operation '${expr.operation.operation}' is not registered with the platform strategy; emitting a placeholder comment.`,
|
|
177
|
+
});
|
|
178
|
+
rendered = `/* unhandled hal-expr: ${expr.operation.operation} */`;
|
|
179
|
+
}
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
default: {
|
|
183
|
+
// An ExpressionIR kind the renderer doesn't know how to render is a
|
|
184
|
+
// transpiler bug — the renderer should cover every kind the IR builders
|
|
185
|
+
// produce. This arm is unreachable in the normal transpileFile path
|
|
186
|
+
// (IR lowering marks unsupported expressions as errors and the build
|
|
187
|
+
// aborts before emit). Throw so any path that does reach here fails
|
|
188
|
+
// loudly instead of emitting "0 /* unsupported_expr */" into the C++.
|
|
189
|
+
const kind = expr.kind ?? "<unknown>";
|
|
190
|
+
throw new Error(`ExpressionRenderer: unsupported ExpressionIR kind '${kind}' reached emission. ` +
|
|
191
|
+
`This is a transpiler bug; the renderer is missing a case for this kind.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return normalizeRawExpression(rendered, this.strategy, this.classNameMap);
|
|
195
|
+
}
|
|
196
|
+
renderIdentifier(value) {
|
|
197
|
+
const nullVal = this.strategy.nullValue();
|
|
198
|
+
// expression-to-ir lowers the TS `null` literal to the identifier sentinel
|
|
199
|
+
// "nullptr" and `undefined` to "CUTTLEFISH_UNDEFINED". Both must route
|
|
200
|
+
// through nullValue() so the platform's chosen null representation is used
|
|
201
|
+
// — otherwise "nullptr" is mangled to "nullptr_" by escapeCppKeyword (since
|
|
202
|
+
// nullptr is a C++ keyword) and emitted as an undefined token.
|
|
203
|
+
if (nullVal && (value === "null" || value === "undefined" || value === "nullptr")) {
|
|
204
|
+
return nullVal;
|
|
205
|
+
}
|
|
206
|
+
// Platform value/function-like macros (e.g. Arduino INPUT, OUTPUT, HIGH,
|
|
207
|
+
// LOW) must pass through verbatim — escaping them to `OUTPUT_` would emit
|
|
208
|
+
// an undefined symbol since the Arduino header defines them as macros.
|
|
209
|
+
if (this.strategy.passthroughMacroNames().has(value)) {
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
return escapeCppKeyword(value, this.strategy.reservedNames());
|
|
213
|
+
}
|
|
214
|
+
renderRaw(value, exprTransformer) {
|
|
215
|
+
const effectiveClassNameMap = this.classNameMap;
|
|
216
|
+
let result = exprTransformer
|
|
217
|
+
? normalizeRawExpression(exprTransformer(value), this.strategy, effectiveClassNameMap)
|
|
218
|
+
: normalizeRawExpression(value, this.strategy, effectiveClassNameMap);
|
|
219
|
+
const cArrayNames = this.cArrayVarNames ?? new Set();
|
|
220
|
+
result = result.replace(/\b([A-Za-z_][A-Za-z0-9_]*)\.(?:length|size)(?:\(\))?/g, (match, varName) => {
|
|
221
|
+
if (cArrayNames.has(varName))
|
|
222
|
+
return `(sizeof(${varName}) / sizeof(${varName}[0]))`;
|
|
223
|
+
// Only C-string pointers require strlen(); std::string vars keep member-call syntax.
|
|
224
|
+
const varInfo = this.knownVariableTypes?.get(varName);
|
|
225
|
+
if (varInfo?.cppType === "const char*" || varInfo?.cppType === "char*")
|
|
226
|
+
return `strlen(${varName})`;
|
|
227
|
+
return match;
|
|
228
|
+
});
|
|
229
|
+
// Rewrite getter property access: s->reading → s->getReading()
|
|
230
|
+
for (const [varName, accessors] of this.varAccessorNames) {
|
|
231
|
+
for (const [propName, kind] of accessors) {
|
|
232
|
+
if (kind === "getter" || kind === "both") {
|
|
233
|
+
const getterName = accessorGetterName(propName);
|
|
234
|
+
const pattern = new RegExp(`\\b${varName}->${propName}\\b(?!\\()`, "g");
|
|
235
|
+
result = result.replace(pattern, `${varName}->${getterName}()`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
renderTernary(expr, exprTransformer) {
|
|
242
|
+
return `(${this.render(expr.condition, exprTransformer)} ? ${this.render(expr.whenTrue, exprTransformer)} : ${this.render(expr.whenFalse, exprTransformer)})`;
|
|
243
|
+
}
|
|
244
|
+
normalizeRecordType(cppType) {
|
|
245
|
+
// Strip const/pointer/reference qualifiers and unwrap smart-pointer
|
|
246
|
+
// wrappers, returning the bare underlying type name. Used to look up
|
|
247
|
+
// interface field types by struct name.
|
|
248
|
+
let ir = parseCppType(cppType);
|
|
249
|
+
if (ir.kind === "smartPointer")
|
|
250
|
+
ir = ir.inner;
|
|
251
|
+
return renderCppType(bareType(ir));
|
|
252
|
+
}
|
|
253
|
+
inferExpressionCppType(expr, knownVariableTypes) {
|
|
254
|
+
const effectiveKnownVariableTypes = knownVariableTypes ?? this.knownVariableTypes;
|
|
255
|
+
switch (expr.kind) {
|
|
256
|
+
case "string":
|
|
257
|
+
case "string_concat":
|
|
258
|
+
case "template_string":
|
|
259
|
+
return "std::string";
|
|
260
|
+
case "number":
|
|
261
|
+
return expr.cppType ?? (Number.isInteger(expr.value) ? "int" : "double");
|
|
262
|
+
case "boolean":
|
|
263
|
+
return "bool";
|
|
264
|
+
case "identifier": {
|
|
265
|
+
const known = effectiveKnownVariableTypes?.get(expr.value)?.cppType;
|
|
266
|
+
if (known)
|
|
267
|
+
return known;
|
|
268
|
+
// A bare identifier that resolves to a module-scope (file-global)
|
|
269
|
+
// pointer variable — e.g. an ISR-captured `const btn = new Button()`
|
|
270
|
+
// accessed inside a hoisted callback, where the local type map is
|
|
271
|
+
// empty. Its C++ type is a pointer (e.g. `Button*`), so member access
|
|
272
|
+
// must use `->`. Previously patched by the output-finalizer file-wide
|
|
273
|
+
// text sweep; now resolved structurally here.
|
|
274
|
+
const globalPtrType = this.globalPointerVarTypes?.get(expr.value);
|
|
275
|
+
if (globalPtrType)
|
|
276
|
+
return globalPtrType;
|
|
277
|
+
if (this.stringVarNames?.has(expr.value))
|
|
278
|
+
return "std::string";
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
case "await":
|
|
282
|
+
return this.inferExpressionCppType(expr.value, knownVariableTypes);
|
|
283
|
+
case "paren":
|
|
284
|
+
return this.inferExpressionCppType(expr.inner, knownVariableTypes);
|
|
285
|
+
case "ternary": {
|
|
286
|
+
// A ternary of two string LITERALS renders as `(c ? "a" : "b")` — a
|
|
287
|
+
// const char*, which has no .c_str() member. inferExpressionCppType
|
|
288
|
+
// returns "std::string" for a `string` IR node, and the equality check
|
|
289
|
+
// below would then short-circuit to "std::string", so the
|
|
290
|
+
// concat/snprintf path wraps the ternary in an invalid `.c_str()`
|
|
291
|
+
// (demo #34 Finding D). Guard the both-branches-are-string-literals
|
|
292
|
+
// case FIRST. Only literal string operands are narrowed; a branch that
|
|
293
|
+
// is a real std::string variable keeps the std::string widening.
|
|
294
|
+
const bothStringLiterals = expr.whenTrue.kind === "string" && expr.whenFalse.kind === "string";
|
|
295
|
+
if (bothStringLiterals)
|
|
296
|
+
return "const char*";
|
|
297
|
+
const whenTrue = this.inferExpressionCppType(expr.whenTrue, knownVariableTypes);
|
|
298
|
+
const whenFalse = this.inferExpressionCppType(expr.whenFalse, knownVariableTypes);
|
|
299
|
+
if (whenTrue && whenFalse && whenTrue === whenFalse)
|
|
300
|
+
return whenTrue;
|
|
301
|
+
if (this.strategy.isStringLikeType(whenTrue ?? "") || this.strategy.isStringLikeType(whenFalse ?? ""))
|
|
302
|
+
return "std::string";
|
|
303
|
+
if (whenTrue === "double" || whenTrue === "float" || whenFalse === "double" || whenFalse === "float")
|
|
304
|
+
return "double";
|
|
305
|
+
return whenTrue ?? whenFalse;
|
|
306
|
+
}
|
|
307
|
+
case "property-access": {
|
|
308
|
+
// String enum member access (e.g. Color.Red) resolves to a const char*,
|
|
309
|
+
// because string enums are lowered to namespaces of constexpr const char*.
|
|
310
|
+
if ((expr.isEnum || (expr.object.kind === "identifier" && this.stringEnumNames.has(expr.object.value)))
|
|
311
|
+
&& expr.object.kind === "identifier"
|
|
312
|
+
&& this.stringEnumNames.has(expr.object.value)) {
|
|
313
|
+
return "const char*";
|
|
314
|
+
}
|
|
315
|
+
// Numeric enum member access (e.g. Cell.Dead) resolves to the enum
|
|
316
|
+
// type itself, so a wrapping expression (ternary, paren, call result)
|
|
317
|
+
// whose branches are enum members infers to the enum and the
|
|
318
|
+
// enum↔integral storage boundary can fire. Without this, `Cell.Dead`
|
|
319
|
+
// inferred to `undefined` (Cell is an enum name, not a variable, so
|
|
320
|
+
// neither the known-variable map nor interface-field map hit), and a
|
|
321
|
+
// ternary `(c ? Cell.Dead : Cell.Alive)` stored into `uint8_t`
|
|
322
|
+
// storage missed the boundary cast. Demo #32 Finding A.
|
|
323
|
+
if (expr.object.kind === "identifier"
|
|
324
|
+
&& this.enumNames.has(expr.object.value)
|
|
325
|
+
&& !this.stringEnumNames.has(expr.object.value)) {
|
|
326
|
+
return expr.object.value;
|
|
327
|
+
}
|
|
328
|
+
if (expr.property === "length" || expr.property === "size")
|
|
329
|
+
return "int";
|
|
330
|
+
if (expr.object.kind === "raw" && expr.object.value === "this") {
|
|
331
|
+
return effectiveKnownVariableTypes?.get(expr.property)?.cppType;
|
|
332
|
+
}
|
|
333
|
+
const objectType = this.inferExpressionCppType(expr.object, knownVariableTypes);
|
|
334
|
+
if (!objectType)
|
|
335
|
+
return undefined;
|
|
336
|
+
return this.interfaceFieldTypes.get(this.normalizeRecordType(objectType))?.get(expr.property);
|
|
337
|
+
}
|
|
338
|
+
case "element-access": {
|
|
339
|
+
if (expr.elementType && expr.elementType !== "auto")
|
|
340
|
+
return expr.elementType;
|
|
341
|
+
const objectType = this.inferExpressionCppType(expr.object, knownVariableTypes);
|
|
342
|
+
if (!objectType)
|
|
343
|
+
return undefined;
|
|
344
|
+
// Element type of vector/staticArray/cArray, detected structurally.
|
|
345
|
+
// The bare StaticArray<T,N> spelling (without __tc_ prefix) also flows
|
|
346
|
+
// through here as a named template; elementOf doesn't cover it, so
|
|
347
|
+
// fall back to rendering the first arg.
|
|
348
|
+
const objIr = parseCppType(objectType);
|
|
349
|
+
if (objIr.kind === "named" && objIr.name === "StaticArray" && objIr.args?.[0]) {
|
|
350
|
+
return renderCppType(objIr.args[0]);
|
|
351
|
+
}
|
|
352
|
+
const elemIr = objIr.kind === "vector" || objIr.kind === "staticArray" || objIr.kind === "cArray"
|
|
353
|
+
? objIr.element
|
|
354
|
+
: undefined;
|
|
355
|
+
return elemIr ? renderCppType(elemIr) : undefined;
|
|
356
|
+
}
|
|
357
|
+
case "array":
|
|
358
|
+
return `std::vector<${expr.elementType}>`;
|
|
359
|
+
case "method-call": {
|
|
360
|
+
const helper = expr.callee.match(/^__tc_(?:toUpperCase|toLowerCase|trim|replace|charAt|substring|slice|padStart|padEnd|repeat|jsonStringify)\b/);
|
|
361
|
+
if (helper)
|
|
362
|
+
return "std::string";
|
|
363
|
+
if (/^__tc_(?:startsWith|endsWith|includes)\b/.test(expr.callee))
|
|
364
|
+
return "bool";
|
|
365
|
+
if (/^__tc_(?:charCodeAt|indexOf|lastIndexOf)\b/.test(expr.callee))
|
|
366
|
+
return "int";
|
|
367
|
+
if (expr.cppType)
|
|
368
|
+
return expr.cppType;
|
|
369
|
+
// Look up the method's return type. The callee text may be a full
|
|
370
|
+
// receiver chain (`this->methodName`, `obj->methodName`), so strip
|
|
371
|
+
// any `->` / `.` prefix to get the bare method name, which is how
|
|
372
|
+
// class method return types are registered in setup.ts.
|
|
373
|
+
const bareName = expr.callee.replace(/^.*->|^.*\./, "");
|
|
374
|
+
return this.knownFunctionReturnTypes?.get(expr.callee)
|
|
375
|
+
?? this.knownFunctionReturnTypes?.get(bareName);
|
|
376
|
+
}
|
|
377
|
+
case "raw": {
|
|
378
|
+
if (/^std::string\(/.test(expr.value) || /^__tc_(?:toUpperCase|toLowerCase|trim|replace|charAt|substring|slice|padStart|padEnd|repeat|jsonStringify)\b/.test(expr.value)) {
|
|
379
|
+
return "std::string";
|
|
380
|
+
}
|
|
381
|
+
if (/^__tc_(?:startsWith|endsWith|includes)\b/.test(expr.value))
|
|
382
|
+
return "bool";
|
|
383
|
+
if (/^__tc_(?:charCodeAt|indexOf|lastIndexOf)\b/.test(expr.value))
|
|
384
|
+
return "int";
|
|
385
|
+
if (/^std::(floor|ceil|round|abs|sqrt|sin|cos|tan|atan2|log|exp|pow|fmod)\b/.test(expr.value)) {
|
|
386
|
+
return "double";
|
|
387
|
+
}
|
|
388
|
+
const callMatch = expr.value.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*\(/);
|
|
389
|
+
return callMatch ? this.knownFunctionReturnTypes?.get(callMatch[1]) : undefined;
|
|
390
|
+
}
|
|
391
|
+
case "binary": {
|
|
392
|
+
const booleanOperators = new Set(["==", "===", "!=", "!==", "<", "<=", ">", ">=", "&&", "||"]);
|
|
393
|
+
if (booleanOperators.has(expr.operator))
|
|
394
|
+
return "bool";
|
|
395
|
+
const leftType = this.inferExpressionCppType(expr.left, knownVariableTypes);
|
|
396
|
+
const rightType = this.inferExpressionCppType(expr.right, knownVariableTypes);
|
|
397
|
+
if (expr.operator === "+" && (this.isStringLikeCppType(leftType) || this.isStringLikeCppType(rightType))) {
|
|
398
|
+
return "std::string";
|
|
399
|
+
}
|
|
400
|
+
if (leftType === "double" || leftType === "float" || rightType === "double" || rightType === "float")
|
|
401
|
+
return "double";
|
|
402
|
+
return leftType ?? rightType;
|
|
403
|
+
}
|
|
404
|
+
case "unary":
|
|
405
|
+
return expr.operator === "!" ? "bool" : this.inferExpressionCppType(expr.operand, knownVariableTypes);
|
|
406
|
+
default:
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* True when a C++ type name denotes a string value: either the strategy's
|
|
412
|
+
* built-in string-like types (std::string, const char*, Arduino String, …)
|
|
413
|
+
* OR a TypeScript string-enum name, which is lowered to `const char*`.
|
|
414
|
+
*/
|
|
415
|
+
isStringLikeCppType(cppType) {
|
|
416
|
+
return !!cppType && (this.strategy.isStringLikeType(cppType) || this.stringEnumNames.has(cppType));
|
|
417
|
+
}
|
|
418
|
+
shouldSkipStringWrap(expr, knownVariableTypes) {
|
|
419
|
+
const cppType = this.inferExpressionCppType(expr, knownVariableTypes);
|
|
420
|
+
return this.isStringLikeCppType(cppType);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Resolve an expression to its C++ type when possible (consulting
|
|
424
|
+
* `knownVariableTypes`, interface/class field type maps, enum/string-enum
|
|
425
|
+
* sets, etc.). Returns `undefined` when the type cannot be determined.
|
|
426
|
+
*
|
|
427
|
+
* Public so emit-layer callers (e.g. the switch emitter in line-appender)
|
|
428
|
+
* can make type-aware lowering decisions — see demo #16 gap #1: a `switch`
|
|
429
|
+
* on a struct field of enum type must not be wrapped in `std::string(...)`.
|
|
430
|
+
*/
|
|
431
|
+
inferCppType(expr, knownVariableTypes) {
|
|
432
|
+
return this.inferExpressionCppType(expr, knownVariableTypes);
|
|
433
|
+
}
|
|
434
|
+
renderKnownStringValue(rendered, cppType) {
|
|
435
|
+
const normalized = this.strategy.normalizeCppType(cppType);
|
|
436
|
+
if (!this.strategy.useSnprintfForStrings() && (normalized === "const char*" || normalized === "char*")) {
|
|
437
|
+
return `std::string(${rendered})`;
|
|
438
|
+
}
|
|
439
|
+
return rendered;
|
|
440
|
+
}
|
|
441
|
+
renderStringConcat(expr, exprTransformer, knownVariableTypes) {
|
|
442
|
+
// When snprintf mode is active, build snprintf buffer instead of String() concatenation
|
|
443
|
+
if (this.strategy.useSnprintfForStrings()) {
|
|
444
|
+
const snprintfResult = this.buildSnprintfFromParts(expr.parts, exprTransformer, knownVariableTypes);
|
|
445
|
+
if (snprintfResult) {
|
|
446
|
+
return snprintfResult;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// Fallback: Build a String concatenation chain
|
|
450
|
+
const renderedParts = expr.parts.map(part => {
|
|
451
|
+
if (part.kind === "boolean") {
|
|
452
|
+
return `std::string(${part.value ? '"true"' : '"false"'})`;
|
|
453
|
+
}
|
|
454
|
+
const rendered = this.render(part, exprTransformer, knownVariableTypes);
|
|
455
|
+
if (part.kind === "string") {
|
|
456
|
+
return rendered;
|
|
457
|
+
}
|
|
458
|
+
if (part.kind === "template_string") {
|
|
459
|
+
return rendered;
|
|
460
|
+
}
|
|
461
|
+
if (this.shouldSkipStringWrap(part, knownVariableTypes)) {
|
|
462
|
+
return rendered;
|
|
463
|
+
}
|
|
464
|
+
if (part.kind === "identifier") {
|
|
465
|
+
const effectiveKnownVariableTypes = knownVariableTypes ?? this.knownVariableTypes;
|
|
466
|
+
const varInfo = effectiveKnownVariableTypes?.get(part.value);
|
|
467
|
+
if (varInfo?.cppType === "bool") {
|
|
468
|
+
return `(${part.value} ? "true" : "false")`;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Doubles/floats need JS-compatible formatting; std::to_string appends trailing zeros (3.000000).
|
|
472
|
+
const partType = this.inferExpressionCppType(part, knownVariableTypes);
|
|
473
|
+
if (partType === "double" || partType === "float") {
|
|
474
|
+
const bufferName = `__cuttlefish_str_${++this._snprintfCounter.value}`;
|
|
475
|
+
this._preludeLines.push(`char ${bufferName}[32];`, `snprintf(${bufferName}, sizeof(${bufferName}), "%.15g", ${rendered});`);
|
|
476
|
+
return `std::string(${bufferName})`;
|
|
477
|
+
}
|
|
478
|
+
return this.strategy.wrapStringObject(rendered);
|
|
479
|
+
});
|
|
480
|
+
return renderedParts.join(" + ");
|
|
481
|
+
}
|
|
482
|
+
renderTemplateString(expr, exprTransformer, knownVariableTypes) {
|
|
483
|
+
// When snprintf mode is active, build snprintf buffer for single interpolation
|
|
484
|
+
if (this.strategy.useSnprintfForStrings()) {
|
|
485
|
+
const argInfo = this.inferFormatSpecifier(expr.expression, exprTransformer, knownVariableTypes);
|
|
486
|
+
if (argInfo) {
|
|
487
|
+
if (argInfo.preludeLines) {
|
|
488
|
+
this._preludeLines.push(...argInfo.preludeLines);
|
|
489
|
+
}
|
|
490
|
+
const bufferName = `__cuttlefish_str_${++this._snprintfCounter.value}`;
|
|
491
|
+
const estimatedLength = Math.max(argInfo.estimatedLength + 1, 16);
|
|
492
|
+
this._preludeLines.push(`char ${bufferName}[${estimatedLength}];`, `snprintf(${bufferName}, sizeof(${bufferName}), "${argInfo.format}", ${argInfo.arg});`);
|
|
493
|
+
return bufferName;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const inferredType = this.inferExpressionCppType(expr.expression, knownVariableTypes);
|
|
497
|
+
const rendered = this.render(expr.expression, exprTransformer, knownVariableTypes);
|
|
498
|
+
if (this.isStringLikeCppType(inferredType)) {
|
|
499
|
+
return this.renderKnownStringValue(rendered, inferredType ?? "");
|
|
500
|
+
}
|
|
501
|
+
if (expr.expression.kind === "boolean") {
|
|
502
|
+
return `std::string(${expr.expression.value ? '"true"' : '"false"'})`;
|
|
503
|
+
}
|
|
504
|
+
if (inferredType === "bool") {
|
|
505
|
+
return `std::string(${rendered} ? "true" : "false")`;
|
|
506
|
+
}
|
|
507
|
+
if (inferredType === "double" || inferredType === "float") {
|
|
508
|
+
const bufferName = `__cuttlefish_str_${++this._snprintfCounter.value}`;
|
|
509
|
+
this._preludeLines.push(`char ${bufferName}[32];`, `snprintf(${bufferName}, sizeof(${bufferName}), "%.15g", ${rendered});`);
|
|
510
|
+
return `std::string(${bufferName})`;
|
|
511
|
+
}
|
|
512
|
+
return this.strategy.wrapStringObject(rendered);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Build snprintf format string and args from string_concat parts.
|
|
516
|
+
* Returns the buffer name on success, or undefined if snprintf can't handle it.
|
|
517
|
+
*/
|
|
518
|
+
buildSnprintfFromParts(parts, exprTransformer, knownVariableTypes) {
|
|
519
|
+
let formatString = "";
|
|
520
|
+
const args = [];
|
|
521
|
+
let estimatedLength = 1;
|
|
522
|
+
for (const part of parts) {
|
|
523
|
+
if (part.kind === "string") {
|
|
524
|
+
// Escape literal text for the snprintf format string. Backslash/quote
|
|
525
|
+
// escapes are for the C string literal; the % → %% escape is for
|
|
526
|
+
// snprintf itself, since a bare % in the format string starts a
|
|
527
|
+
// conversion specifier (e.g. `hp=${x}%` would otherwise emit
|
|
528
|
+
// "hp=%.15g%" — a dangling conversion). MUST run before the other
|
|
529
|
+
// escapes so the doubled %% isn't itself touched.
|
|
530
|
+
formatString += part.value.replace(/%/g, "%%").replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
531
|
+
estimatedLength += part.value.length;
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
const argInfo = this.inferFormatSpecifier(part, exprTransformer, knownVariableTypes);
|
|
535
|
+
if (argInfo) {
|
|
536
|
+
if (argInfo.preludeLines) {
|
|
537
|
+
this._preludeLines.push(...argInfo.preludeLines);
|
|
538
|
+
}
|
|
539
|
+
formatString += argInfo.format;
|
|
540
|
+
args.push(argInfo.arg);
|
|
541
|
+
estimatedLength += argInfo.estimatedLength;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
// Can't handle other part types with snprintf
|
|
545
|
+
return undefined;
|
|
546
|
+
}
|
|
547
|
+
const bufferName = `__cuttlefish_str_${++this._snprintfCounter.value}`;
|
|
548
|
+
estimatedLength = Math.max(estimatedLength, 16);
|
|
549
|
+
this._preludeLines.push(`char ${bufferName}[${estimatedLength}];`, `snprintf(${bufferName}, sizeof(${bufferName}), "${formatString}"${args.length > 0 ? `, ${args.join(", ")}` : ""});`);
|
|
550
|
+
return bufferName;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Infer printf format specifier for an expression.
|
|
554
|
+
* Returns format string, rendered arg, estimated length, and optional prelude lines, or undefined if unknown.
|
|
555
|
+
*/
|
|
556
|
+
inferFormatSpecifier(expr, exprTransformer, knownVariableTypes) {
|
|
557
|
+
const effectiveKnownVariableTypes = knownVariableTypes ?? this.knownVariableTypes;
|
|
558
|
+
if (expr.kind === "template_string") {
|
|
559
|
+
return this.inferFormatSpecifier(expr.expression, exprTransformer, knownVariableTypes);
|
|
560
|
+
}
|
|
561
|
+
// Namespace-const access (`Ns.MEMBER`) lowers to a `property-access` IR
|
|
562
|
+
// node whose `property` is the member name. Resolve the member's type from
|
|
563
|
+
// knownVariableTypes (namespace consts are registered there by bare name
|
|
564
|
+
// in setup.ts) so a namespace `const string` operand formats as %s with
|
|
565
|
+
// .c_str(), not the %d default (namespace stress test Finding 3b).
|
|
566
|
+
if (expr.kind === "property-access") {
|
|
567
|
+
const memberType = effectiveKnownVariableTypes?.get(expr.property);
|
|
568
|
+
if (memberType) {
|
|
569
|
+
const rendered = this.render(expr, exprTransformer, knownVariableTypes);
|
|
570
|
+
if (this.isStringLikeCppType(memberType.cppType)) {
|
|
571
|
+
const normalized = this.strategy.normalizeCppType(memberType.cppType);
|
|
572
|
+
const needsCStr = needsCStrForStringLike(normalized);
|
|
573
|
+
return { format: "%s", arg: needsCStr ? `${rendered}.c_str()` : rendered, estimatedLength: 128 };
|
|
574
|
+
}
|
|
575
|
+
if (memberType.cppType === "bool") {
|
|
576
|
+
return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5 };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (expr.kind !== "number" && expr.kind !== "boolean" && expr.kind !== "string") {
|
|
581
|
+
const inferredType = this.inferExpressionCppType(expr, knownVariableTypes);
|
|
582
|
+
if (inferredType) {
|
|
583
|
+
const rendered = this.render(expr, exprTransformer, knownVariableTypes);
|
|
584
|
+
const normalized = this.strategy.normalizeCppType(inferredType);
|
|
585
|
+
if (this.isStringLikeCppType(inferredType)) {
|
|
586
|
+
// .c_str() is needed for std::string-like types (which own a buffer)
|
|
587
|
+
// but NOT for const char* / char* (already C-strings). Detect via the
|
|
588
|
+
// structured IR: string/strPtr/string-enum need it; char pointers don't.
|
|
589
|
+
const needsCStr = needsCStrForStringLike(normalized);
|
|
590
|
+
// std::string operands can be arbitrarily long (a method like
|
|
591
|
+
// statusLine() may return a 100+ char string), so budget a generous
|
|
592
|
+
// estimate. The previous value (32) truncated output whenever a
|
|
593
|
+
// string-returning method was interpolated into a concat.
|
|
594
|
+
return { format: "%s", arg: needsCStr ? `${rendered}.c_str()` : rendered, estimatedLength: 128 };
|
|
595
|
+
}
|
|
596
|
+
if (normalized === "bool") {
|
|
597
|
+
return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5 };
|
|
598
|
+
}
|
|
599
|
+
if (normalized === "float" || normalized === "double") {
|
|
600
|
+
const knownPrecision = expr.kind === "identifier"
|
|
601
|
+
? effectiveKnownVariableTypes?.get(expr.value)?.floatPrecision
|
|
602
|
+
: undefined;
|
|
603
|
+
const floatArg = this.strategy.floatToSnprintfArg?.(rendered, knownPrecision, ++this._snprintfCounter.value);
|
|
604
|
+
if (floatArg !== undefined)
|
|
605
|
+
return floatArg;
|
|
606
|
+
// Use %.15g (not %g) so large integer-valued doubles don't collapse
|
|
607
|
+
// to scientific notation — matches JS Number.toString() more closely.
|
|
608
|
+
return { format: knownPrecision !== undefined ? `%.${knownPrecision}f` : "%.15g", arg: rendered, estimatedLength: 16 };
|
|
609
|
+
}
|
|
610
|
+
// Enum-typed value (struct field, variable, etc. whose type resolves
|
|
611
|
+
// to a known numeric enum name). C++ enum class values need
|
|
612
|
+
// static_cast<int>(...) for %d — without it, -Wformat= warns that
|
|
613
|
+
// the argument type (enum) doesn't match %d (int).
|
|
614
|
+
if (this.enumNames.has(normalized) && !this.stringEnumNames.has(normalized)) {
|
|
615
|
+
return { format: "%d", arg: `static_cast<int>(${rendered})`, estimatedLength: 12 };
|
|
616
|
+
}
|
|
617
|
+
if (/^(?:unsigned\s+)?(?:char|short|int|long|long long)$/.test(normalized) || /^(?:u?int(?:8|16|32|64)_t|size_t)$/.test(normalized)) {
|
|
618
|
+
if (normalized.includes("long long") || /64_t$/.test(normalized)) {
|
|
619
|
+
return { format: "%lld", arg: rendered, estimatedLength: 20 };
|
|
620
|
+
}
|
|
621
|
+
// `long` is its own C++ type and matches %ld regardless of width.
|
|
622
|
+
// int32_t/uint32_t are typedefs for int/unsigned int on every target
|
|
623
|
+
// cuttlefish supports (AVR/ARM), so they must use %d/%u — using %ld
|
|
624
|
+
// triggers -Wformat= ("expects long int, has int").
|
|
625
|
+
if (normalized.includes("long")) {
|
|
626
|
+
return { format: normalized.startsWith("unsigned") ? "%lu" : "%ld", arg: rendered, estimatedLength: 12 };
|
|
627
|
+
}
|
|
628
|
+
// 32_t/16_t/8_t and bare int/short/char. These are `int`-sized or
|
|
629
|
+
// narrower; unsigned variants are `unsigned int`-sized → %u. size_t
|
|
630
|
+
// is platform-dependent but unsigned int on the targets here → %u.
|
|
631
|
+
if (/^size_t$/.test(normalized) || normalized.startsWith("uint") || normalized.startsWith("unsigned")) {
|
|
632
|
+
return { format: "%u", arg: rendered, estimatedLength: 12 };
|
|
633
|
+
}
|
|
634
|
+
return { format: "%d", arg: rendered, estimatedLength: 12 };
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
switch (expr.kind) {
|
|
639
|
+
case "number": {
|
|
640
|
+
if (expr.cppType === "float" || !Number.isInteger(expr.value)) {
|
|
641
|
+
const str = `${expr.value}`;
|
|
642
|
+
const rendered = str.includes('.') || str.includes('e') || str.includes('E')
|
|
643
|
+
? `${str}f`
|
|
644
|
+
: `${str}.0f`;
|
|
645
|
+
return { format: "%.15g", arg: rendered, estimatedLength: 16 };
|
|
646
|
+
}
|
|
647
|
+
return { format: "%d", arg: `${expr.value}`, estimatedLength: 12 };
|
|
648
|
+
}
|
|
649
|
+
case "boolean":
|
|
650
|
+
return { format: "%s", arg: expr.value ? '"true"' : '"false"', estimatedLength: 5 };
|
|
651
|
+
case "string":
|
|
652
|
+
// Route through the shared `escapeCppStringLiteral` (handles `\`, `"`,
|
|
653
|
+
// `\n`, `\r`, `\t`) so a string-literal template/concat part carrying a
|
|
654
|
+
// control char renders as the escape sequence, not a raw char inside the
|
|
655
|
+
// C++ string literal. Demo #29 Finding A sibling — this path was reached
|
|
656
|
+
// by a standalone `${'x\ny'}` template part: the partial escape here
|
|
657
|
+
// (only `\` and `"`) emitted a raw newline → an unterminated C++ literal.
|
|
658
|
+
return { format: "%s", arg: `"${escapeCppStringLiteral(expr.value)}"`, estimatedLength: Math.max(expr.value.length, 1) };
|
|
659
|
+
case "identifier": {
|
|
660
|
+
const knownVar = effectiveKnownVariableTypes?.get(expr.value);
|
|
661
|
+
const cppType = knownVar?.cppType ?? this.knownFunctionReturnTypes?.get(expr.value);
|
|
662
|
+
if (this.isStringLikeCppType(cppType)) {
|
|
663
|
+
const normalized = this.strategy.normalizeCppType(cppType ?? "");
|
|
664
|
+
const arg = normalized === "__tc_str_ptr" ? `${expr.value}.c_str()` : expr.value;
|
|
665
|
+
return { format: "%s", arg, estimatedLength: 128 };
|
|
666
|
+
}
|
|
667
|
+
if (cppType === "bool") {
|
|
668
|
+
return { format: "%s", arg: `(${expr.value} ? "true" : "false")`, estimatedLength: 5 };
|
|
669
|
+
}
|
|
670
|
+
if (cppType === "float" || cppType === "double") {
|
|
671
|
+
const floatArg = this.strategy.floatToSnprintfArg?.(expr.value, knownVar?.floatPrecision, ++this._snprintfCounter.value);
|
|
672
|
+
if (floatArg !== undefined)
|
|
673
|
+
return floatArg;
|
|
674
|
+
return { format: knownVar?.floatPrecision !== undefined ? `%.${knownVar.floatPrecision}f` : "%.15g", arg: expr.value, estimatedLength: 16 };
|
|
675
|
+
}
|
|
676
|
+
if (cppType === "int" || cppType === "short" || cppType === "int16_t" || cppType === "uint16_t" || cppType === "int32_t") {
|
|
677
|
+
return { format: "%d", arg: expr.value, estimatedLength: 12 };
|
|
678
|
+
}
|
|
679
|
+
if (cppType === "uint32_t") {
|
|
680
|
+
return { format: "%u", arg: expr.value, estimatedLength: 12 };
|
|
681
|
+
}
|
|
682
|
+
// auto-deduced local: infer the real type from the retained initializer
|
|
683
|
+
// before falling back. Treating `auto` as `%ld` is wrong for locals
|
|
684
|
+
// deduced from a string/enum/float expression (e.g.
|
|
685
|
+
// `const name = loot.name` → std::string). Re-dispatch through the
|
|
686
|
+
// general inference path so the proper specifier is chosen.
|
|
687
|
+
if (cppType === "auto" && knownVar?.initializer) {
|
|
688
|
+
const inferredFromInit = this.inferFormatSpecifier(knownVar.initializer, exprTransformer, knownVariableTypes);
|
|
689
|
+
if (inferredFromInit) {
|
|
690
|
+
// Replace the placeholder arg with the local variable name, since
|
|
691
|
+
// the initializer's rendered form would re-evaluate side effects.
|
|
692
|
+
return { ...inferredFromInit, arg: expr.value };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (cppType === "long") {
|
|
696
|
+
return { format: "%ld", arg: expr.value, estimatedLength: 12 };
|
|
697
|
+
}
|
|
698
|
+
if (cppType === "unsigned long") {
|
|
699
|
+
return { format: "%lu", arg: expr.value, estimatedLength: 12 };
|
|
700
|
+
}
|
|
701
|
+
if (cppType === "long long" || cppType === "unsigned long long" || cppType === "int64_t" || cppType === "uint64_t") {
|
|
702
|
+
return { format: "%lld", arg: expr.value, estimatedLength: 20 };
|
|
703
|
+
}
|
|
704
|
+
if (this.stringVarNames?.has(expr.value)) {
|
|
705
|
+
return { format: "%s", arg: expr.value, estimatedLength: 32 };
|
|
706
|
+
}
|
|
707
|
+
// Numeric enum-typed variable: static_cast<int> for snprintf (%d).
|
|
708
|
+
// String enums are const char* (handled by isStringLikeType above).
|
|
709
|
+
if (cppType && this.enumNames.has(cppType) && !this.stringEnumNames.has(cppType)) {
|
|
710
|
+
return { format: "%d", arg: `static_cast<int>(${expr.value})`, estimatedLength: 12 };
|
|
711
|
+
}
|
|
712
|
+
// Default to %d for integers and unknowns
|
|
713
|
+
return { format: "%d", arg: expr.value, estimatedLength: 12 };
|
|
714
|
+
}
|
|
715
|
+
case "hal-expr": {
|
|
716
|
+
const rendered = this.render(expr, exprTransformer);
|
|
717
|
+
// HAL expressions that resolve to string-like outputs use %s
|
|
718
|
+
if (rendered.startsWith('"') || this.stringVarNames?.has(rendered)) {
|
|
719
|
+
return { format: "%s", arg: rendered, estimatedLength: 32 };
|
|
720
|
+
}
|
|
721
|
+
return { format: "%d", arg: rendered, estimatedLength: 12 };
|
|
722
|
+
}
|
|
723
|
+
case "method-call":
|
|
724
|
+
case "property-access":
|
|
725
|
+
case "binary":
|
|
726
|
+
case "unary":
|
|
727
|
+
case "ternary":
|
|
728
|
+
case "raw": {
|
|
729
|
+
const rendered = this.render(expr, exprTransformer);
|
|
730
|
+
// String-returning helpers (__tc_toUpperCase, etc.) use %s.
|
|
731
|
+
// On hosted targets these return std::string, so snprintf needs
|
|
732
|
+
// .c_str(). On targets WITHOUT std::string (e.g. Arduino AVR) the
|
|
733
|
+
// helpers already return const char*, so .c_str() would be a redundant
|
|
734
|
+
// member access on a non-class type (avr-g++: "request for member
|
|
735
|
+
// 'c_str' in '__tc_trim(...)', which is of non-class type 'const
|
|
736
|
+
// char*'"). Demo #35 Finding A.
|
|
737
|
+
if (/^__tc_(toUpperCase|toLowerCase|trim|replace|charAt|substring|slice|padStart|padEnd|repeat|jsonStringify)\b/.test(rendered)) {
|
|
738
|
+
const needsCStr = this.strategy.needsStdString();
|
|
739
|
+
return { format: "%s", arg: needsCStr ? `${rendered}.c_str()` : rendered, estimatedLength: 32 };
|
|
740
|
+
}
|
|
741
|
+
// Check if it's a property access on a string (e.g. s.length)
|
|
742
|
+
if (expr.kind === "property-access" && (expr.property === "length" || expr.property === "size")) {
|
|
743
|
+
// Demo #30 Finding B — `.length`/`.size` now ALWAYS render as
|
|
744
|
+
// `static_cast<long long>(...)` (the array/vector `.size()` path was
|
|
745
|
+
// already cast; the std::string `.length()` path was cast in the same
|
|
746
|
+
// demo so the two are uniform). The rendered arg is therefore a
|
|
747
|
+
// signed `long long`, so the format specifier MUST be `%lld`. The
|
|
748
|
+
// previous hardcoded `%d` mismatched the unsigned `size_type` returned
|
|
749
|
+
// by `std::string::length()`, triggering g++ -Wformat=
|
|
750
|
+
// ("expects int, has size_type").
|
|
751
|
+
return { format: "%lld", arg: rendered, estimatedLength: 20 };
|
|
752
|
+
}
|
|
753
|
+
// Demo #30 Finding B (raw-node sibling) — `.length`/`.size` on an
|
|
754
|
+
// array/vector/string receiver lowers to a `raw` IR node whose text is
|
|
755
|
+
// `static_cast<long long>(x.size())` / `static_cast<long long>(s.length())`
|
|
756
|
+
// (the lowering in resolveLengthProperty casts to long long). That raw
|
|
757
|
+
// text is a signed `long long`, so the snprintf specifier must be `%lld`
|
|
758
|
+
// — the previous fall-through to the `%d` default mismatched the cast
|
|
759
|
+
// type and tripped g++ -Wformat=. This catches the lowered form; the
|
|
760
|
+
// `property-access` branch above catches the (rare) un-lowered form.
|
|
761
|
+
if (expr.kind === "raw" && /^static_cast<long long>\(.*\.(?:size|length)\(\)\)$/.test(rendered)) {
|
|
762
|
+
return { format: "%lld", arg: rendered, estimatedLength: 20 };
|
|
763
|
+
}
|
|
764
|
+
// String enum member access → const char* → %s with c_str() for snprintf.
|
|
765
|
+
if (expr.kind === "property-access" && expr.object.kind === "identifier"
|
|
766
|
+
&& this.stringEnumNames.has(expr.object.value)) {
|
|
767
|
+
return { format: "%s", arg: `${rendered}.c_str()`, estimatedLength: 32 };
|
|
768
|
+
}
|
|
769
|
+
// Numeric enum member access → static_cast<int> for %d.
|
|
770
|
+
if (expr.kind === "property-access" && expr.object.kind === "identifier"
|
|
771
|
+
&& this.enumNames.has(expr.object.value) && !this.stringEnumNames.has(expr.object.value)) {
|
|
772
|
+
return { format: "%d", arg: `static_cast<int>(${rendered})`, estimatedLength: 12 };
|
|
773
|
+
}
|
|
774
|
+
// Fallback for expressions that render as string-like pointers
|
|
775
|
+
if (this.stringVarNames?.has(rendered)) {
|
|
776
|
+
return { format: "%s", arg: rendered, estimatedLength: 32 };
|
|
777
|
+
}
|
|
778
|
+
// Default to %d for other complex expressions (mostly numeric)
|
|
779
|
+
return { format: "%d", arg: rendered, estimatedLength: 12 };
|
|
780
|
+
}
|
|
781
|
+
default:
|
|
782
|
+
return undefined;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
renderArray(expr, exprTransformer) {
|
|
786
|
+
const elements = expr.elements.map((e) => this.render(e, exprTransformer)).join(", ");
|
|
787
|
+
return `{ ${elements} }`;
|
|
788
|
+
}
|
|
789
|
+
renderObject(expr, exprTransformer) {
|
|
790
|
+
const fields = expr.fields.map((f) => `${this.render(f.value, exprTransformer)}`).join(", ");
|
|
791
|
+
return `{ ${fields} }`;
|
|
792
|
+
}
|
|
793
|
+
renderSpreadArray(expr, exprTransformer) {
|
|
794
|
+
const spreadText = this.render(expr.spreadExpr, exprTransformer);
|
|
795
|
+
const elementType = expr.elementType === "auto" ? "auto" : expr.elementType;
|
|
796
|
+
const extras = expr.additionalElements.map(e => this.render(e, exprTransformer)).join(", ");
|
|
797
|
+
if (extras.length === 0)
|
|
798
|
+
return spreadText;
|
|
799
|
+
const combined = [spreadText, extras].join(", ");
|
|
800
|
+
return `std::vector<${elementType}>({ ${combined} })`;
|
|
801
|
+
}
|
|
802
|
+
renderInstanceof(expr, exprTransformer) {
|
|
803
|
+
return `(typeid(*${this.render(expr.object, exprTransformer)}) == typeid(${expr.className}))`;
|
|
804
|
+
}
|
|
805
|
+
renderElementAccess(expr, exprTransformer, knownVariableTypes) {
|
|
806
|
+
const objectText = this.render(expr.object, exprTransformer);
|
|
807
|
+
// An enum-typed index (e.g. `GLYPHS[op]` where `op: Op`, a `const enum`)
|
|
808
|
+
// must be cast to an integral type — a C++ `enum class` does not
|
|
809
|
+
// implicitly convert to `size_t`, so `vector[enumValue]` fails to compile.
|
|
810
|
+
// `renderEnumSafeValue` already wraps numeric-enum operands in
|
|
811
|
+
// `static_cast<int>(...)` (SUPPORT_MATRIX §1.7); route the index through it
|
|
812
|
+
// (with the in-scope variable types so a bare enum-typed identifier
|
|
813
|
+
// resolves) so enum indices lower correctly. Non-enum indices are passed
|
|
814
|
+
// through unchanged. Demo #28 Finding E.
|
|
815
|
+
const indexText = this.renderEnumSafeValue(expr.index, knownVariableTypes);
|
|
816
|
+
return `${objectText}[${indexText}]`;
|
|
817
|
+
}
|
|
818
|
+
isEnumComparisonOperand(expr, inferredType) {
|
|
819
|
+
if (inferredType && this.enumNames.has(inferredType))
|
|
820
|
+
return true;
|
|
821
|
+
// Property/element access on an enum name (e.g. Color::Green) is also enum-valued.
|
|
822
|
+
if (expr.kind === "property-access" && expr.object.kind === "identifier" && this.enumNames.has(expr.object.value))
|
|
823
|
+
return true;
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Render an expression value that will be assigned to a non-enum lvalue,
|
|
828
|
+
* passed as a non-enum argument, or otherwise consumed where C++ `enum class`
|
|
829
|
+
* won't implicitly convert. If the value is a numeric-enum operand, wrap it
|
|
830
|
+
* in `static_cast<int>(...)` so it composes with int-typed targets (e.g.
|
|
831
|
+
* `links[0] = LinkFlag.Wired` → `links[0] = static_cast<int>(LinkFlag::Wired)`).
|
|
832
|
+
* String-enum operands are passed through unchanged (they're `const char*`).
|
|
833
|
+
*
|
|
834
|
+
* This is the assignment/argument-site counterpart to the operator-level
|
|
835
|
+
* enum wrapping in renderBinary. SUPPORT_MATRIX §1.7: enum values used as
|
|
836
|
+
* integers need an explicit cast in C++.
|
|
837
|
+
*/
|
|
838
|
+
renderEnumSafeValue(expr, knownVariableTypes) {
|
|
839
|
+
const rendered = this.render(expr, undefined, knownVariableTypes);
|
|
840
|
+
const inferredType = this.inferExpressionCppType(expr, knownVariableTypes);
|
|
841
|
+
if (!this.isStringEnumOperand(expr, inferredType) &&
|
|
842
|
+
this.isEnumComparisonOperand(expr, inferredType)) {
|
|
843
|
+
const enumName = this.getNumericEnumOperandName(expr, inferredType);
|
|
844
|
+
const castType = enumName
|
|
845
|
+
? (this.strategy.enumCastType(enumName) ?? (this.largeEnumNames.has(enumName) ? "long" : "int"))
|
|
846
|
+
: "int";
|
|
847
|
+
if (/^static_cast<[^>]+>\(/.test(rendered)) {
|
|
848
|
+
return rendered;
|
|
849
|
+
}
|
|
850
|
+
return `static_cast<${castType}>(${rendered})`;
|
|
851
|
+
}
|
|
852
|
+
return rendered;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Render a value that will be stored into an lvalue of a known C++ type
|
|
856
|
+
* (an `assign` target, a `var_decl` initializer, a `push_back` argument,
|
|
857
|
+
* ...). Centralizes the **enum ↔ integral storage boundary** so every
|
|
858
|
+
* assignment/initialization site lowers consistently:
|
|
859
|
+
*
|
|
860
|
+
* - target is a numeric C++ type, value is a numeric-enum operand →
|
|
861
|
+
* `static_cast<int>(value)` (enum → int). This is the existing
|
|
862
|
+
* `renderEnumSafeValue` direction, now driven by the target type
|
|
863
|
+
* instead of duplicated per site.
|
|
864
|
+
* - target is a C++ `enum class`, value is a numeric/element-access
|
|
865
|
+
* operand → `static_cast<EnumType>(value)` (int → enum). C++ `enum
|
|
866
|
+
* class` does not implicitly convert FROM an integral storage type
|
|
867
|
+
* either, so reading an `int`/`uint8_t` cell back into an enum-typed
|
|
868
|
+
* local needs the reverse cast. SUPPORT_MATRIX §1.7.
|
|
869
|
+
*
|
|
870
|
+
* This closes the "enum↔integral storage boundary" family — the previous
|
|
871
|
+
* point-specific casts handled enum-as-array-index (demo #28 E) and
|
|
872
|
+
* enum-as-Map-key (demo #28 E review) but NOT enum stored into integral
|
|
873
|
+
* storage, integral storage read back into an enum, or an enum value
|
|
874
|
+
* passed to `push_back` on an integral-element vector. Demo #32 Finding A.
|
|
875
|
+
*
|
|
876
|
+
* String enums (`const char*`) and string-like targets are passed through
|
|
877
|
+
* unchanged. Unknown/auto target types are passed through unchanged
|
|
878
|
+
* (safer than a wrong cast).
|
|
879
|
+
*/
|
|
880
|
+
renderValueForTarget(expr, targetType, knownVariableTypes, exprTransformer) {
|
|
881
|
+
const rendered = this.render(expr, exprTransformer, knownVariableTypes);
|
|
882
|
+
if (!targetType)
|
|
883
|
+
return rendered;
|
|
884
|
+
const normalizedTarget = targetType.trim();
|
|
885
|
+
// Never cast into string-like storage — enums that lower to const char*
|
|
886
|
+
// (string enums) already match, and a string target is never an enum boundary.
|
|
887
|
+
if (this.isStringLikeCppType(normalizedTarget))
|
|
888
|
+
return rendered;
|
|
889
|
+
const valueIsEnum = !this.isStringEnumOperand(expr, undefined)
|
|
890
|
+
&& this.isEnumComparisonOperand(expr, this.inferExpressionCppType(expr, knownVariableTypes));
|
|
891
|
+
const targetIsEnum = this.enumNames.has(normalizedTarget) && !this.stringEnumNames.has(normalizedTarget);
|
|
892
|
+
const targetIsIntegral = INTEGRAL_CPP_TYPE_RE.test(normalizedTarget);
|
|
893
|
+
// enum value → integral storage: cast the value to int.
|
|
894
|
+
if (targetIsIntegral && valueIsEnum && !/^static_cast<[^>]+>\(/.test(rendered)) {
|
|
895
|
+
return this.renderEnumSafeValue(expr, knownVariableTypes);
|
|
896
|
+
}
|
|
897
|
+
// integral value → enum storage: cast the value to the enum type.
|
|
898
|
+
if (targetIsEnum && !valueIsEnum) {
|
|
899
|
+
const valueType = this.inferExpressionCppType(expr, knownVariableTypes);
|
|
900
|
+
const valueIsIntegral = valueType !== undefined && INTEGRAL_CPP_TYPE_RE.test(valueType);
|
|
901
|
+
if (valueIsIntegral && !/^static_cast<[^>]+>\(/.test(rendered)) {
|
|
902
|
+
return `static_cast<${normalizedTarget}>(${rendered})`;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return rendered;
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Resolve a rendered C++ lvalue STRING (the `target` carried by an `assign`
|
|
909
|
+
* IR node, e.g. `this->nxt[r][c]`, `cells[i]`, `flag`) to its declared C++
|
|
910
|
+
* element/value type. This is the string-target counterpart to
|
|
911
|
+
* `inferExpressionCppType`'s element-access / property-access / identifier
|
|
912
|
+
* branches: the assign target is already rendered to text at IR-build time,
|
|
913
|
+
* so the emit layer must recover its type from the text + the shared type
|
|
914
|
+
* maps (the same maps `inferExpressionCppType` consults).
|
|
915
|
+
*
|
|
916
|
+
* Structural, not regex-rewrite: it splits the base from trailing `[...]`
|
|
917
|
+
* subscripts, resolves the base via the class-field / local / global-pointer
|
|
918
|
+
* maps, then unwraps one `std::vector<T>` / `StaticArray<T,N>` layer per
|
|
919
|
+
* subscript. Returns `undefined` when the type can't be resolved confidently
|
|
920
|
+
* — callers fall through to plain rendering (never an incorrect cast).
|
|
921
|
+
* Demo #32 Finding A.
|
|
922
|
+
*/
|
|
923
|
+
inferLvalueCppType(target, knownVariableTypes) {
|
|
924
|
+
let depth = 0;
|
|
925
|
+
let base = target.trim();
|
|
926
|
+
// Strip trailing `[...]` subscripts (balanced: a subscript body holds no
|
|
927
|
+
// nested brackets in practice because element-access lowers one level at a
|
|
928
|
+
// time; a `this->m[a[b]]` would render `this->m[a[b]]` whose outermost
|
|
929
|
+
// `[a[b]]` we still strip correctly via the greedy-non-bracket regex below
|
|
930
|
+
// — the inner `[b]` is left on `a` and resolved as a separate base, which
|
|
931
|
+
// is fine because we only need the OUTER element type here).
|
|
932
|
+
while (/\[[^\[\]]*\]\s*$/.test(base)) {
|
|
933
|
+
base = base.replace(/\[[^\[\]]*\]\s*$/, "").trim();
|
|
934
|
+
depth++;
|
|
935
|
+
}
|
|
936
|
+
const baseType = this.resolveLvalueBaseType(base, knownVariableTypes);
|
|
937
|
+
if (!baseType)
|
|
938
|
+
return undefined;
|
|
939
|
+
if (depth === 0)
|
|
940
|
+
return baseType;
|
|
941
|
+
// Unwrap `depth` layers of std::vector<T> / StaticArray<T,N>.
|
|
942
|
+
let inner = baseType;
|
|
943
|
+
for (let i = 0; i < depth; i++) {
|
|
944
|
+
const parsed = parseCppType(inner);
|
|
945
|
+
if (parsed.kind === "vector" || parsed.kind === "staticArray" || parsed.kind === "cArray") {
|
|
946
|
+
inner = renderCppType(parsed.element);
|
|
947
|
+
}
|
|
948
|
+
else if (parsed.kind === "named" && parsed.name === "StaticArray" && parsed.args?.[0]) {
|
|
949
|
+
inner = renderCppType(parsed.args[0]);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
return undefined;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return inner;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Resolve a subscript-free rendered base (`this->field`, `obj->field`,
|
|
959
|
+
* `obj.field`, or a bare local name) to its declared C++ type. The string
|
|
960
|
+
* counterpart of `inferExpressionCppType`'s identifier / property-access
|
|
961
|
+
* branches. Demo #32 Finding A.
|
|
962
|
+
*/
|
|
963
|
+
resolveLvalueBaseType(base, knownVariableTypes) {
|
|
964
|
+
const memberMatch = base.match(/^(.+?)(?:->|\.)(\w+)$/);
|
|
965
|
+
if (memberMatch) {
|
|
966
|
+
const [, objStr, propName] = memberMatch;
|
|
967
|
+
const objBase = objStr.trim();
|
|
968
|
+
// `this->field` / `(*this).field`: class fields are seeded into the
|
|
969
|
+
// known-variable-types map under their bare name (mirrors how
|
|
970
|
+
// inferExpressionCppType's property-access `this` branch resolves), so a
|
|
971
|
+
// `this->prop` lookup hits `knownVariableTypes.get(prop)`.
|
|
972
|
+
if (objBase === "this" || objBase === "(*this)") {
|
|
973
|
+
return knownVariableTypes?.get(propName)?.cppType;
|
|
974
|
+
}
|
|
975
|
+
// `obj->field` / `obj.field`: resolve the object's type, then look the
|
|
976
|
+
// field up in the interface-field-type map for that struct.
|
|
977
|
+
const objType = this.resolveLvalueBaseType(objBase, knownVariableTypes);
|
|
978
|
+
if (objType) {
|
|
979
|
+
const structName = this.normalizeRecordType(objType).replace(/\s*\*$/, "").trim();
|
|
980
|
+
const fieldType = this.interfaceFieldTypes.get(structName)?.get(propName);
|
|
981
|
+
if (fieldType)
|
|
982
|
+
return fieldType;
|
|
983
|
+
}
|
|
984
|
+
return undefined;
|
|
985
|
+
}
|
|
986
|
+
// Bare local / parameter / global.
|
|
987
|
+
if (/^\w+$/.test(base)) {
|
|
988
|
+
const local = knownVariableTypes?.get(base)?.cppType;
|
|
989
|
+
if (local)
|
|
990
|
+
return local;
|
|
991
|
+
const ptr = this.globalPointerVarTypes?.get(base);
|
|
992
|
+
if (ptr)
|
|
993
|
+
return ptr;
|
|
994
|
+
if (this.stringVarNames?.has(base))
|
|
995
|
+
return "std::string";
|
|
996
|
+
}
|
|
997
|
+
return undefined;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Shared enum-operand wrapping for binary operators. Detects whether each
|
|
1001
|
+
* side is a numeric-enum operand (excluding string enums), casts it to int,
|
|
1002
|
+
* and applies defensive symmetry: if exactly one side is an enum, the other
|
|
1003
|
+
* side is also cast so the operator sees `int op int`. Returns the
|
|
1004
|
+
* (possibly rewrapped) left and right strings.
|
|
1005
|
+
*
|
|
1006
|
+
* Used by the arithmetic, bitwise, and comparison branches of renderBinary
|
|
1007
|
+
* so the enum-cast logic lives in one place rather than three.
|
|
1008
|
+
*/
|
|
1009
|
+
castEnumOperandsForOperator(leftExpr, rightExpr, leftType, rightType, leftRendered, rightRendered) {
|
|
1010
|
+
const leftIsEnum = !this.isStringEnumOperand(leftExpr, leftType) && this.isEnumComparisonOperand(leftExpr, leftType);
|
|
1011
|
+
const rightIsEnum = !this.isStringEnumOperand(rightExpr, rightType) && this.isEnumComparisonOperand(rightExpr, rightType);
|
|
1012
|
+
let left = leftRendered;
|
|
1013
|
+
let right = rightRendered;
|
|
1014
|
+
if (leftIsEnum) {
|
|
1015
|
+
left = `static_cast<int>(${leftRendered})`;
|
|
1016
|
+
}
|
|
1017
|
+
if (rightIsEnum) {
|
|
1018
|
+
right = `static_cast<int>(${rightRendered})`;
|
|
1019
|
+
}
|
|
1020
|
+
// Defensive symmetry: static_cast<int> is always safe on a scalar (enum,
|
|
1021
|
+
// int, double, bool all convert) and produces the matching-type `int op
|
|
1022
|
+
// int` form C++ requires when one side is enum and the other isn't.
|
|
1023
|
+
if (leftIsEnum && !rightIsEnum) {
|
|
1024
|
+
right = `static_cast<int>(${rightRendered})`;
|
|
1025
|
+
}
|
|
1026
|
+
if (rightIsEnum && !leftIsEnum) {
|
|
1027
|
+
left = `static_cast<int>(${leftRendered})`;
|
|
1028
|
+
}
|
|
1029
|
+
return { left, right, leftIsEnum, rightIsEnum };
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Returns true when an expression is a string-enum member access, i.e. it
|
|
1033
|
+
* resolves to a `constexpr const char*` (e.g. `Color.Red` where Color is a
|
|
1034
|
+
* TS string enum). Such operands must NOT be static_cast<int>'d in
|
|
1035
|
+
* comparisons — they compare as C-strings instead.
|
|
1036
|
+
*/
|
|
1037
|
+
isStringEnumOperand(expr, inferredType) {
|
|
1038
|
+
if (inferredType && this.stringEnumNames.has(inferredType))
|
|
1039
|
+
return true;
|
|
1040
|
+
if (expr.kind === "property-access" && expr.object.kind === "identifier" && this.stringEnumNames.has(expr.object.value))
|
|
1041
|
+
return true;
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
getNumericEnumOperandName(expr, inferredType) {
|
|
1045
|
+
if (inferredType && this.enumNames.has(inferredType) && !this.stringEnumNames.has(inferredType)) {
|
|
1046
|
+
return inferredType;
|
|
1047
|
+
}
|
|
1048
|
+
if (expr.kind === "property-access" &&
|
|
1049
|
+
expr.object.kind === "identifier" &&
|
|
1050
|
+
this.enumNames.has(expr.object.value) &&
|
|
1051
|
+
!this.stringEnumNames.has(expr.object.value)) {
|
|
1052
|
+
return expr.object.value;
|
|
1053
|
+
}
|
|
1054
|
+
return undefined;
|
|
1055
|
+
}
|
|
1056
|
+
renderBinary(expr, exprTransformer, knownVariableTypes) {
|
|
1057
|
+
const leftRendered = this.render(expr.left, exprTransformer, knownVariableTypes);
|
|
1058
|
+
const rightRendered = this.render(expr.right, exprTransformer, knownVariableTypes);
|
|
1059
|
+
// Platform-specific string concat wrapping (Arduino: String())
|
|
1060
|
+
if (expr.operator === "+") {
|
|
1061
|
+
const wrapped = this.strategy.wrapStringConcat(leftRendered, rightRendered, expr.left.kind === "string");
|
|
1062
|
+
if (wrapped !== undefined)
|
|
1063
|
+
return wrapped;
|
|
1064
|
+
}
|
|
1065
|
+
// C++ doesn't define % for double — use fmod
|
|
1066
|
+
const modLeftType = this.inferExpressionCppType(expr.left, knownVariableTypes);
|
|
1067
|
+
const modRightType = this.inferExpressionCppType(expr.right, knownVariableTypes);
|
|
1068
|
+
if (expr.operator === "%" && (modLeftType === "double" || modLeftType === "float" || modRightType === "double" || modRightType === "float")) {
|
|
1069
|
+
const left = expr.left.kind === "binary" ? `(${leftRendered})` : leftRendered;
|
|
1070
|
+
const right = expr.right.kind === "binary" ? `(${rightRendered})` : rightRendered;
|
|
1071
|
+
return `fmod(${left}, ${right})`;
|
|
1072
|
+
}
|
|
1073
|
+
// Promote integer division to double to match JavaScript semantics
|
|
1074
|
+
if (expr.operator === "/" && this.strategy.promoteDivisionToDouble?.()) {
|
|
1075
|
+
const left = expr.left.kind === "binary" ? `(${leftRendered})` : leftRendered;
|
|
1076
|
+
const right = expr.right.kind === "binary" ? `(${rightRendered})` : rightRendered;
|
|
1077
|
+
return `static_cast<double>(${left}) / static_cast<double>(${right})`;
|
|
1078
|
+
}
|
|
1079
|
+
if (expr.operator === "**") {
|
|
1080
|
+
const left = expr.left.kind === "binary" ? `(${leftRendered})` : leftRendered;
|
|
1081
|
+
const right = expr.right.kind === "binary" ? `(${rightRendered})` : rightRendered;
|
|
1082
|
+
return `pow(${left}, ${right})`;
|
|
1083
|
+
}
|
|
1084
|
+
// Wrap enum-class operands in static_cast<int>() for arithmetic operators
|
|
1085
|
+
// (+, -, *, /) and bitwise operators (&, |, ^, <<, >>). C++ enum class
|
|
1086
|
+
// values don't interoperate with int implicitly — `trophic - 1`, `a | b`,
|
|
1087
|
+
// `x & MASK` all fail without the cast. SUPPORT_MATRIX §1.7/§5.1. The
|
|
1088
|
+
// shared castEnumOperandsForOperator helper handles detection + defensive
|
|
1089
|
+
// symmetry in one place.
|
|
1090
|
+
const numericOps = new Set(["+", "-", "*", "/", "&", "|", "^", "<<", ">>"]);
|
|
1091
|
+
let preLeft = leftRendered;
|
|
1092
|
+
let preRight = rightRendered;
|
|
1093
|
+
if (numericOps.has(expr.operator)) {
|
|
1094
|
+
// For "+", only cast when neither side is string-like — "+" may be
|
|
1095
|
+
// string concatenation (handled by the wrapStringConcat path above),
|
|
1096
|
+
// and casting a string operand to int would be wrong. Numeric "+"
|
|
1097
|
+
// (e.g. `power + rarity`) still needs the enum-side cast. The other
|
|
1098
|
+
// numeric ops are never string concat, so they always qualify.
|
|
1099
|
+
const leftIsStringLike = this.isStringLikeCppType(modLeftType) || expr.left.kind === "string";
|
|
1100
|
+
const rightIsStringLike = this.isStringLikeCppType(modRightType) || expr.right.kind === "string";
|
|
1101
|
+
const isNumericContext = expr.operator !== "+" || (!leftIsStringLike && !rightIsStringLike);
|
|
1102
|
+
if (isNumericContext) {
|
|
1103
|
+
const cast = this.castEnumOperandsForOperator(expr.left, expr.right, modLeftType, modRightType, leftRendered, rightRendered);
|
|
1104
|
+
preLeft = cast.left;
|
|
1105
|
+
preRight = cast.right;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
// Wrap enum-class operands in static_cast<int>() for comparisons against
|
|
1109
|
+
// integers, since C++ enum class values don't compare with int implicitly.
|
|
1110
|
+
const comparisonOps = new Set(["==", "===", "!=", "!==", "<", "<=", ">", ">="]);
|
|
1111
|
+
let finalLeft = preLeft;
|
|
1112
|
+
let finalRight = preRight;
|
|
1113
|
+
if (comparisonOps.has(expr.operator)) {
|
|
1114
|
+
const leftIsStringEnum = this.isStringEnumOperand(expr.left, modLeftType);
|
|
1115
|
+
const rightIsStringEnum = this.isStringEnumOperand(expr.right, modRightType);
|
|
1116
|
+
const leftIsStrLiteral = expr.left.kind === "string";
|
|
1117
|
+
const rightIsStrLiteral = expr.right.kind === "string";
|
|
1118
|
+
// Detect operands that render to a raw C-string value (char buffer from
|
|
1119
|
+
// snprintf concat, or a string enum member) so equality against a string
|
|
1120
|
+
// literal uses strcmp instead of pointer ==.
|
|
1121
|
+
// Only snprintf buffers and string enums need this — std::string has
|
|
1122
|
+
// operator==, and const char* variables retain their pre-existing ==
|
|
1123
|
+
// behavior. Including std::string here would break valid comparisons.
|
|
1124
|
+
const isRawCString = (e, t) => {
|
|
1125
|
+
if (e.kind === "string_concat" || e.kind === "template_string")
|
|
1126
|
+
return true;
|
|
1127
|
+
if (e.kind === "string")
|
|
1128
|
+
return true;
|
|
1129
|
+
return this.isStringEnumOperand(e, t);
|
|
1130
|
+
};
|
|
1131
|
+
const leftIsCStringValue = isRawCString(expr.left, modLeftType);
|
|
1132
|
+
const rightIsCStringValue = isRawCString(expr.right, modRightType);
|
|
1133
|
+
// String equality: any C-string operand compared with == / != must use
|
|
1134
|
+
// strcmp (C++ pointer == on char* compares addresses, not contents).
|
|
1135
|
+
// But std::string/String variables have operator==, so skip strcmp when
|
|
1136
|
+
// an operand is a managed-string *variable* (would break valid ==).
|
|
1137
|
+
// Note: string_concat/template_string render to char[] buffers even
|
|
1138
|
+
// though their inferred type is std::string, so they still need strcmp.
|
|
1139
|
+
const isManagedStringVar = (e, t) => {
|
|
1140
|
+
if (e.kind === "string_concat" || e.kind === "template_string" || e.kind === "string")
|
|
1141
|
+
return false;
|
|
1142
|
+
return needsCStrForStringLike(this.strategy.normalizeCppType(t ?? ""));
|
|
1143
|
+
};
|
|
1144
|
+
if ((expr.operator === "===" || expr.operator === "==" || expr.operator === "!==" || expr.operator === "!=") &&
|
|
1145
|
+
(leftIsCStringValue || rightIsCStringValue) &&
|
|
1146
|
+
!isManagedStringVar(expr.left, modLeftType) && !isManagedStringVar(expr.right, modRightType)) {
|
|
1147
|
+
const wantEqual = expr.operator === "===" || expr.operator === "==";
|
|
1148
|
+
finalLeft = `strcmp(${leftRendered}, ${rightRendered})`;
|
|
1149
|
+
return `${finalLeft} ${wantEqual ? "==" : "!="} 0`;
|
|
1150
|
+
}
|
|
1151
|
+
// After the strcmp path, apply the shared enum-operand cast (with
|
|
1152
|
+
// defensive symmetry). The strcmp path above handles string-valued
|
|
1153
|
+
// operands; this handles numeric-enum operands compared against ints.
|
|
1154
|
+
const cast = this.castEnumOperandsForOperator(expr.left, expr.right, modLeftType, modRightType, leftRendered, rightRendered);
|
|
1155
|
+
finalLeft = cast.left;
|
|
1156
|
+
finalRight = cast.right;
|
|
1157
|
+
}
|
|
1158
|
+
// Precedence-aware parenthesization to preserve TS semantics in C++.
|
|
1159
|
+
const cppOp = expr.operator === "===" ? "==" : expr.operator === "!==" ? "!=" : expr.operator;
|
|
1160
|
+
const myPrec = operatorPrecedence(expr.operator);
|
|
1161
|
+
const leftNeedsParens = expr.left.kind === "binary" && operatorPrecedence(expr.left.operator) < myPrec;
|
|
1162
|
+
const rightNeedsParens = expr.right.kind === "binary" && operatorPrecedence(expr.right.operator) <= myPrec;
|
|
1163
|
+
const left = leftNeedsParens ? `(${finalLeft})` : finalLeft;
|
|
1164
|
+
const right = rightNeedsParens ? `(${finalRight})` : finalRight;
|
|
1165
|
+
return `${left} ${cppOp} ${right}`;
|
|
1166
|
+
}
|
|
1167
|
+
renderUnary(expr, exprTransformer) {
|
|
1168
|
+
const rendered = this.render(expr.operand, exprTransformer);
|
|
1169
|
+
// Postfix operators (e.g. i++, i--) render after the operand.
|
|
1170
|
+
if (expr.postfix) {
|
|
1171
|
+
return `${rendered}${expr.operator}`;
|
|
1172
|
+
}
|
|
1173
|
+
return `${expr.operator}${rendered}`;
|
|
1174
|
+
}
|
|
1175
|
+
renderPropertyAccess(expr, exprTransformer, knownVariableTypes) {
|
|
1176
|
+
const chain = extractPropertyChain(expr);
|
|
1177
|
+
if (chain) {
|
|
1178
|
+
// Check for Board.definition.* access first
|
|
1179
|
+
const boardDef = this.strategy.renderBoardDefinitionAccess(chain, this.boardConstants);
|
|
1180
|
+
if (boardDef !== undefined)
|
|
1181
|
+
return boardDef;
|
|
1182
|
+
// Check for peripheral stub property access (I2C0.isInitialized, SPI0.isInitialized, Serial.isInitialized)
|
|
1183
|
+
const peripheralProperty = renderPeripheralProperty(chain, this.strategy);
|
|
1184
|
+
if (peripheralProperty !== undefined)
|
|
1185
|
+
return peripheralProperty;
|
|
1186
|
+
}
|
|
1187
|
+
const objStr = this.render(expr.object, exprTransformer);
|
|
1188
|
+
// In C++, 'this' is a pointer — always use -> for member access.
|
|
1189
|
+
// But first: if `this` has a getter for the accessed property, rewrite
|
|
1190
|
+
// to a getter call (`this->getX()`). The class emitter registers the
|
|
1191
|
+
// current class's accessors under the "this" key in varAccessorNames
|
|
1192
|
+
// while emitting each non-static method. This must run before the
|
|
1193
|
+
// plain `this->x` early-return below, otherwise getter access on
|
|
1194
|
+
// `this` is never rewritten (the call site stays as `this->x` and
|
|
1195
|
+
// fails to compile against the generated `getX()` method).
|
|
1196
|
+
if (expr.object.kind === "raw" && expr.object.value === "this") {
|
|
1197
|
+
const thisAccessors = this.varAccessorNames.get("this");
|
|
1198
|
+
if (thisAccessors?.has(expr.property)) {
|
|
1199
|
+
const getterName = accessorGetterName(expr.property);
|
|
1200
|
+
return `this->${getterName}()`;
|
|
1201
|
+
}
|
|
1202
|
+
const safeProperty = escapeCppKeyword(expr.property, this.strategy.reservedNames());
|
|
1203
|
+
return `this->${safeProperty}`;
|
|
1204
|
+
}
|
|
1205
|
+
// Use C++ scope-resolution operator (::) for enum class member access.
|
|
1206
|
+
if (expr.isEnum || (expr.object.kind === "identifier" && this.enumNames.has(expr.object.value))) {
|
|
1207
|
+
const enumName = expr.object.kind === "identifier" ? expr.object.value : objStr;
|
|
1208
|
+
const enumMember = this.strategy.renameEnumMember(enumName, expr.property);
|
|
1209
|
+
const enumAccess = `${objStr}::${enumMember}`;
|
|
1210
|
+
const castType = this.strategy.enumCastType(enumName);
|
|
1211
|
+
if (castType !== undefined) {
|
|
1212
|
+
return `static_cast<${castType}>(${enumAccess})`;
|
|
1213
|
+
}
|
|
1214
|
+
return enumAccess;
|
|
1215
|
+
}
|
|
1216
|
+
// Use C++ scope-resolution operator (::) for namespace or static member access.
|
|
1217
|
+
// BUT: a top-level const object (e.g. `const CONFIG = {...}` lowered to an
|
|
1218
|
+
// `extern _CONFIG_t CONFIG` variable) is an instance, not a namespace/class,
|
|
1219
|
+
// even though the IR may flag it isStatic. Use `.` for those.
|
|
1220
|
+
if (expr.object.kind === "identifier" && this.knownTopLevelObjectTypes?.has(expr.object.value)) {
|
|
1221
|
+
const accessor = expr.isPointer ? "->" : ".";
|
|
1222
|
+
const safeProperty = escapeCppKeyword(expr.property, this.strategy.reservedNames());
|
|
1223
|
+
return `${objStr}${accessor}${safeProperty}`;
|
|
1224
|
+
}
|
|
1225
|
+
// Multi-level static access through a namespace: `Ns.Class.member`. The
|
|
1226
|
+
// outer `Ns.Class` renders via the namespace branch below as `Ns::Class`
|
|
1227
|
+
// (a namespace-qualified class path). The INNER `.member` access has an
|
|
1228
|
+
// `expr.object` that is itself a property-access (not an identifier), so
|
|
1229
|
+
// the `namespaceNames.has(...)` check below misses it — it fell through to
|
|
1230
|
+
// the instance `.` default and emitted `Ns::Class.member` (mixed), failing
|
|
1231
|
+
// at g++ time for a static member (needs `Ns::Class::member`). When the
|
|
1232
|
+
// object rendered to a `::`-form it names a namespace/class, not an
|
|
1233
|
+
// instance, so the member access is also `::`.
|
|
1234
|
+
if (objStr.includes("::") && expr.object.kind === "property-access") {
|
|
1235
|
+
const safeProperty = escapeCppKeyword(expr.property, this.strategy.reservedNames());
|
|
1236
|
+
return `${objStr}::${safeProperty}`;
|
|
1237
|
+
}
|
|
1238
|
+
if (expr.isNamespace || expr.isStatic || (expr.object.kind === "identifier" && this.namespaceNames.has(expr.object.value))) {
|
|
1239
|
+
// Static getter access: `Counter.total` where `total` is a static getter
|
|
1240
|
+
// lowers to `Counter::getTotal()`, not the raw field `Counter::total`.
|
|
1241
|
+
// (Instance getters are handled below; this branch runs first for static
|
|
1242
|
+
// access and would otherwise bypass the getter rewrite — demo #14 E.)
|
|
1243
|
+
if (expr.isStatic && expr.object.kind === "identifier") {
|
|
1244
|
+
const staticAccessors = this.typeAccessorNames.get(expr.object.value);
|
|
1245
|
+
if (staticAccessors?.has(expr.property)) {
|
|
1246
|
+
const getterName = accessorGetterName(expr.property);
|
|
1247
|
+
return `${objStr}::${getterName}()`;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const safeProperty = escapeCppKeyword(expr.property, this.strategy.reservedNames());
|
|
1251
|
+
return `${objStr}::${safeProperty}`;
|
|
1252
|
+
}
|
|
1253
|
+
if (expr.object.kind === "identifier" && (expr.property === "length" || expr.property === "size")) {
|
|
1254
|
+
const varName = expr.object.value;
|
|
1255
|
+
if (this.cArrayVarNames?.has(varName)) {
|
|
1256
|
+
return `(sizeof(${objStr}) / sizeof(${objStr}[0]))`;
|
|
1257
|
+
}
|
|
1258
|
+
const varInfo = knownVariableTypes?.get(varName) ?? this.knownVariableTypes?.get(varName);
|
|
1259
|
+
if (varInfo && parseCppType(varInfo.cppType).kind === "staticArray") {
|
|
1260
|
+
return `${objStr}.${expr.property}()`;
|
|
1261
|
+
}
|
|
1262
|
+
// C-style strings (const char*, char*) require strlen().
|
|
1263
|
+
if (varInfo && (varInfo.cppType === "const char*" || varInfo.cppType === "char*")) {
|
|
1264
|
+
return `strlen(${objStr})`;
|
|
1265
|
+
}
|
|
1266
|
+
// std::vector<T> exposes .size() (not .length).
|
|
1267
|
+
if (varInfo && parsedIsVector(varInfo.cppType)) {
|
|
1268
|
+
return `${objStr}.size()`;
|
|
1269
|
+
}
|
|
1270
|
+
// String variable detected via IR scan (Issue 2): std::string exposes length()/size().
|
|
1271
|
+
if (this.stringVarNames?.has(varName)) {
|
|
1272
|
+
return `${objStr}.${expr.property}()`;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
// Passthrough enums: members render as bare identifiers (e.g. INTERNAL, not AnalogReference::INTERNAL).
|
|
1276
|
+
if (expr.object.kind === "identifier" && this.strategy.passthroughEnumNames?.().has(expr.object.value)) {
|
|
1277
|
+
return expr.property;
|
|
1278
|
+
}
|
|
1279
|
+
// Fallback for general size/length accessors if not handled above
|
|
1280
|
+
if (expr.property === "size" || expr.property === "length") {
|
|
1281
|
+
// (Handled above for identified strings/arrays)
|
|
1282
|
+
}
|
|
1283
|
+
// Rewrite property access to getter call if the property is an accessor.
|
|
1284
|
+
// Primary lookup: the receiver is an explicitly-registered variable
|
|
1285
|
+
// (var_decl / param / top-level binding) in varAccessorNames. Fallback:
|
|
1286
|
+
// the receiver's resolved C++ type names a class with getters
|
|
1287
|
+
// (typeAccessorNames). The fallback covers for-of loop variables, function
|
|
1288
|
+
// returns, and chained access — receivers whose variable name was never
|
|
1289
|
+
// registered but whose type is a known class with accessors.
|
|
1290
|
+
if (expr.object.kind === "identifier") {
|
|
1291
|
+
let accessors = this.varAccessorNames.get(expr.object.value);
|
|
1292
|
+
if (!accessors) {
|
|
1293
|
+
const resolvedType = this.inferExpressionCppType(expr.object, knownVariableTypes);
|
|
1294
|
+
if (resolvedType) {
|
|
1295
|
+
// Bare class name (pointer/const stripped) via structured IR.
|
|
1296
|
+
const bareTypeStr = renderCppType(bareType(parseCppType(resolvedType)));
|
|
1297
|
+
accessors = this.typeAccessorNames.get(bareTypeStr);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
if (accessors?.has(expr.property)) {
|
|
1301
|
+
const getterName = accessorGetterName(expr.property);
|
|
1302
|
+
return `${objStr}->${getterName}()`;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
// Decide -> vs "." for the member access. Prefer the IR's isPointer flag
|
|
1306
|
+
// (set during IR build for pointer variables / this). When that's absent,
|
|
1307
|
+
// fall back to resolving the object's C++ type: if it's a pointer (ends
|
|
1308
|
+
// with '*'), the access must use ->. This catches struct/interface fields
|
|
1309
|
+
// of class-pointer type (e.g. `dungeon.monster.name` where monster is a
|
|
1310
|
+
// Monster* field) that the IR-build-time isPointer detection misses,
|
|
1311
|
+
// because interface/struct field types aren't visible during IR build.
|
|
1312
|
+
let accessor = expr.isPointer ? "->" : ".";
|
|
1313
|
+
if (accessor === ".") {
|
|
1314
|
+
const objectType = this.inferExpressionCppType(expr.object, knownVariableTypes);
|
|
1315
|
+
if (objectType && parsedIsPointer(objectType)) {
|
|
1316
|
+
accessor = "->";
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
const safeProperty = escapeCppKeyword(expr.property, this.strategy.reservedNames());
|
|
1320
|
+
const rendered = `${objStr}${accessor}${safeProperty}`;
|
|
1321
|
+
return rendered;
|
|
1322
|
+
}
|
|
1323
|
+
renderCallback(expr) {
|
|
1324
|
+
// Callbacks are rendered by the statement emitter which tracks them globally
|
|
1325
|
+
// Here we just return a marker that gets replaced with the actual function name
|
|
1326
|
+
return `/* callback:${expr.sourceSpan.startLine}:${expr.sourceSpan.startColumn} */`;
|
|
1327
|
+
}
|
|
1328
|
+
renderLambda(expr, exprTransformer) {
|
|
1329
|
+
const params = expr.params.map(p => `${p.cppType} ${p.name}`).join(", ");
|
|
1330
|
+
const ret = expr.returnType && expr.returnType !== "auto" ? ` -> ${expr.returnType}` : "";
|
|
1331
|
+
if (expr.isExpressionBody && expr.body.length === 1 && expr.body[0].kind === "return" && "value" in expr.body[0]) {
|
|
1332
|
+
return `[&](${params})${ret} { return ${this.render(expr.body[0].value, exprTransformer)}; }`;
|
|
1333
|
+
}
|
|
1334
|
+
const bodyStr = expr.body.map(s => {
|
|
1335
|
+
if (s.kind === "return" && s.value)
|
|
1336
|
+
return ` return ${this.render(s.value, exprTransformer)};`;
|
|
1337
|
+
if (s.kind === "var_decl") {
|
|
1338
|
+
const safeName = s.name;
|
|
1339
|
+
const init = s.initializer ? this.render(s.initializer, exprTransformer) : "";
|
|
1340
|
+
return ` auto ${safeName} = ${init};`;
|
|
1341
|
+
}
|
|
1342
|
+
if (s.kind === "assign")
|
|
1343
|
+
return ` ${s.target} ${s.operator} ${s.value ? this.render(s.value, exprTransformer) : ""};`;
|
|
1344
|
+
if (s.kind === "call")
|
|
1345
|
+
return ` ${this.render(s, exprTransformer)};`;
|
|
1346
|
+
return ` /* ${s.kind} */`;
|
|
1347
|
+
}).join("\n");
|
|
1348
|
+
return `[&](${params})${ret} {\n${bodyStr}\n}`;
|
|
1349
|
+
}
|
|
1350
|
+
renderMethodCall(expr, exprTransformer) {
|
|
1351
|
+
const argsText = expr.args.map(a => this.render(a, exprTransformer)).join(", ");
|
|
1352
|
+
let callee = exprTransformer ? exprTransformer(expr.callee) : expr.callee;
|
|
1353
|
+
// Convert . to :: for static or namespace method calls if the IR flagged them.
|
|
1354
|
+
if (expr.isStatic || expr.isNamespace) {
|
|
1355
|
+
callee = callee.replace(/\./g, "::");
|
|
1356
|
+
}
|
|
1357
|
+
const escapedStringVarNames = new Set(Array.from(this.stringVarNames ?? []).map(name => escapeCppKeyword(name, this.strategy.reservedNames())));
|
|
1358
|
+
const stringVarNames = this.stringVarNames ?? new Set();
|
|
1359
|
+
const cArrayNames = this.cArrayVarNames ?? new Set();
|
|
1360
|
+
// Convert . to -> for pointer method calls if the IR flagged them.
|
|
1361
|
+
if (expr.isPointer && !callee.includes("->")) {
|
|
1362
|
+
callee = callee.replace(/\./g, "->");
|
|
1363
|
+
}
|
|
1364
|
+
else if (!callee.includes("->")) {
|
|
1365
|
+
// Fallback: the IR's isPointer flag is unset when the receiver is a
|
|
1366
|
+
// module-scope (file-global) pointer variable accessed from a scope
|
|
1367
|
+
// where its type wasn't visible at IR build time — notably an
|
|
1368
|
+
// ISR-captured `const btn = new Button()` referenced inside the hoisted
|
|
1369
|
+
// callback. Consult globalPointerVarTypes (threaded from EmitterContext)
|
|
1370
|
+
// to recover the pointer-ness and arrow the leading `obj.` → `obj->`.
|
|
1371
|
+
// This is the structural replacement for the file-wide text sweep that
|
|
1372
|
+
// formerly lived in output-finalizer.ts. Only the leading receiver is
|
|
1373
|
+
// rewritten: a chain like `btn->field.method` keeps its inner `.`.
|
|
1374
|
+
const m = callee.match(/^([A-Za-z_$][\w$]*)\./);
|
|
1375
|
+
if (m && this.globalPointerVarTypes?.has(m[1])) {
|
|
1376
|
+
callee = callee.replace(/^([A-Za-z_$][\w$]*)\./, "$1->");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (/\b([A-Za-z_][A-Za-z0-9_]*)\.(?:length|size)$/g.test(callee)) {
|
|
1380
|
+
return callee;
|
|
1381
|
+
}
|
|
1382
|
+
callee = this.escapeFinalMemberName(callee);
|
|
1383
|
+
return `${callee}(${argsText})`;
|
|
1384
|
+
}
|
|
1385
|
+
escapeFinalMemberName(callee) {
|
|
1386
|
+
if (callee.startsWith("std::")) {
|
|
1387
|
+
return callee;
|
|
1388
|
+
}
|
|
1389
|
+
return callee.replace(/(->|::|\.)([A-Za-z_][A-Za-z0-9_]*)$/, (_match, separator, memberName) => {
|
|
1390
|
+
return `${separator}${escapeCppKeyword(memberName, this.strategy.reservedNames())}`;
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Returns the C++ operator precedence for precedence-aware parenthesization.
|
|
1396
|
+
* Higher number = higher precedence (binds tighter).
|
|
1397
|
+
*/
|
|
1398
|
+
function operatorPrecedence(op) {
|
|
1399
|
+
switch (op) {
|
|
1400
|
+
case "**": return 6;
|
|
1401
|
+
case "*":
|
|
1402
|
+
case "/":
|
|
1403
|
+
case "%": return 5;
|
|
1404
|
+
case "+":
|
|
1405
|
+
case "-": return 4;
|
|
1406
|
+
case "<<":
|
|
1407
|
+
case ">>":
|
|
1408
|
+
case ">>>": return 3;
|
|
1409
|
+
case "<":
|
|
1410
|
+
case "<=":
|
|
1411
|
+
case ">":
|
|
1412
|
+
case ">=": return 2;
|
|
1413
|
+
case "==":
|
|
1414
|
+
case "!=": return 1;
|
|
1415
|
+
case "&": return 0;
|
|
1416
|
+
case "^": return -1;
|
|
1417
|
+
case "|": return -2;
|
|
1418
|
+
case "&&": return -3;
|
|
1419
|
+
case "||": return -4;
|
|
1420
|
+
default: return 0;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Transform class names in expressions to their fully qualified names.
|
|
1425
|
+
* E.g., "new SHT3x()" -> "new Microfire::SHT3x()"
|
|
1426
|
+
*/
|
|
1427
|
+
function transformClassNames(value, classNameMap) {
|
|
1428
|
+
if (classNameMap.size === 0) {
|
|
1429
|
+
return value;
|
|
1430
|
+
}
|
|
1431
|
+
// Transform "new ClassName(" patterns
|
|
1432
|
+
let result = value;
|
|
1433
|
+
for (const [simpleName, fullName] of classNameMap) {
|
|
1434
|
+
// Only transform if the full name is different (has namespace)
|
|
1435
|
+
if (fullName !== simpleName && fullName.includes("::")) {
|
|
1436
|
+
const pattern = new RegExp(`\\bnew\\s+${simpleName}\\b`, "g");
|
|
1437
|
+
result = result.replace(pattern, `new ${fullName}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
return result;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* Normalizes a raw expression string for C++ output.
|
|
1444
|
+
* Handles === to == conversion, enum access, and class name transformation.
|
|
1445
|
+
*/
|
|
1446
|
+
export function normalizeRawExpression(value, strategy, classNameMap) {
|
|
1447
|
+
let normalized = value
|
|
1448
|
+
.replace(/\?\?/g, "/* ?? */"); // Fallback for raw expressions; usually handled at IR level
|
|
1449
|
+
// Apply platform-specific expression normalisation
|
|
1450
|
+
normalized = strategy.normalizeRawExpression(normalized);
|
|
1451
|
+
// Transform Arduino library class names to their fully qualified names
|
|
1452
|
+
if (classNameMap) {
|
|
1453
|
+
normalized = transformClassNames(normalized, classNameMap);
|
|
1454
|
+
}
|
|
1455
|
+
return normalized;
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Transform type names to their fully qualified names (with namespaces).
|
|
1459
|
+
* E.g., "SHT3x*" -> "Microfire::SHT3x*", "SHT3x" -> "Microfire::SHT3x"
|
|
1460
|
+
*/
|
|
1461
|
+
export function transformTypeName(cppType, classNameMap) {
|
|
1462
|
+
if (!classNameMap || classNameMap.size === 0) {
|
|
1463
|
+
return cppType;
|
|
1464
|
+
}
|
|
1465
|
+
let result = cppType;
|
|
1466
|
+
for (const [simpleName, fullName] of classNameMap) {
|
|
1467
|
+
// Only transform if the full name is different (has namespace)
|
|
1468
|
+
if (fullName !== simpleName && fullName.includes("::")) {
|
|
1469
|
+
// Match the simple name as a word boundary (not already qualified with ::)
|
|
1470
|
+
// Handle patterns like "SHT3x", "SHT3x*", "SHT3x&", etc.
|
|
1471
|
+
// Use negative lookbehind to avoid matching already-qualified names
|
|
1472
|
+
const pattern = new RegExp(`(?<![a-zA-Z0-9_:])${simpleName}\\b`, "g");
|
|
1473
|
+
result = result.replace(pattern, fullName);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
return result;
|
|
1477
|
+
}
|