@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,948 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statement Renderer for C++ code emission.
|
|
3
|
+
* Encapsulates all statement rendering logic with explicit dependencies.
|
|
4
|
+
* Extracted from cpp-emitter.ts
|
|
5
|
+
*/
|
|
6
|
+
import { routeHALOp } from "./route-hal-op.js";
|
|
7
|
+
import { activeNamespaceNames } from "../ir/build-ir-state.js";
|
|
8
|
+
import { ExpressionRenderer, transformTypeName, normalizeRawExpression } from "./expression-renderer.js";
|
|
9
|
+
import { isConsoleCall, getConsoleMethod, inferObjectFieldType, collectNestedStructDefs } from "./utils/index.js";
|
|
10
|
+
import { escapeCppKeyword, escapeTrailingMember } from "../utils/strings.js";
|
|
11
|
+
import { accessorGetterName, accessorSetterName } from "./utils/cpp-helpers.js";
|
|
12
|
+
import { parseCppType, bareType, parsedIsPointer, parsedIsVector, parsedElementString, parsedIsPlainStructType } from "../api/shared/cpp-type-ir.js";
|
|
13
|
+
/**
|
|
14
|
+
* Returns true for C++ scalar/primitive types that are cheaply passed by value.
|
|
15
|
+
* Non-primitives (std::vector, String, structs, arrays) should be passed by reference
|
|
16
|
+
* when borrowed via Shared<T> or Mutable<T> to avoid deep copies.
|
|
17
|
+
*/
|
|
18
|
+
function isPrimitiveCppType(cppType) {
|
|
19
|
+
const t = cppType.trim();
|
|
20
|
+
const primitives = new Set([
|
|
21
|
+
'int', 'float', 'double', 'bool', 'char', 'long', 'void',
|
|
22
|
+
'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
|
|
23
|
+
'int8_t', 'int16_t', 'int32_t', 'int64_t',
|
|
24
|
+
'size_t', 'byte', 'word',
|
|
25
|
+
'unsigned int', 'unsigned long', 'unsigned char',
|
|
26
|
+
'signed int', 'signed long', 'signed char',
|
|
27
|
+
]);
|
|
28
|
+
return primitives.has(t);
|
|
29
|
+
}
|
|
30
|
+
function isIndirectType(cppType, strategy) {
|
|
31
|
+
// Pointer or C-array. Detected structurally rather than by regex.
|
|
32
|
+
if (strategy.isPointerType(cppType))
|
|
33
|
+
return true;
|
|
34
|
+
const ir = parseCppType(cppType);
|
|
35
|
+
return ir.kind === "cArray" || ir.kind === "staticArray";
|
|
36
|
+
}
|
|
37
|
+
/** True for any `std::`-prefixed type (vector/map/set/tuple/variant/function/string).
|
|
38
|
+
* Replaces the historical `cppType.startsWith('std::')` check. */
|
|
39
|
+
function parsedIsStdContainer(cppType) {
|
|
40
|
+
const ir = parseCppType(cppType);
|
|
41
|
+
switch (ir.kind) {
|
|
42
|
+
case "vector":
|
|
43
|
+
case "map":
|
|
44
|
+
case "set":
|
|
45
|
+
case "tuple":
|
|
46
|
+
case "variant":
|
|
47
|
+
case "function":
|
|
48
|
+
case "string":
|
|
49
|
+
case "smartPointer":
|
|
50
|
+
return true;
|
|
51
|
+
default:
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* True if a C++ return type denotes a STRUCT (value class/interface) rather
|
|
57
|
+
* than a primitive, pointer, void, container, or enum. Used to decide whether
|
|
58
|
+
* `return null` must lower to `return {};` (value-init) instead of a nullish
|
|
59
|
+
* literal. Demo #14 Finding A.
|
|
60
|
+
*/
|
|
61
|
+
function isStructReturnType(cppType, strategy) {
|
|
62
|
+
const t = cppType.trim();
|
|
63
|
+
if (!t || t === "void" || t === "auto")
|
|
64
|
+
return false;
|
|
65
|
+
if (isPrimitiveCppType(t))
|
|
66
|
+
return false;
|
|
67
|
+
if (isIndirectType(t, strategy))
|
|
68
|
+
return false;
|
|
69
|
+
// Containers and pseudo-types (std::vector, std::string, __tc_StaticArray,
|
|
70
|
+
// __tc_str_ptr, std::function, …) already value-initialize from {} or their
|
|
71
|
+
// own defaults. Restrict this path to plain struct names. Detected by "the
|
|
72
|
+
// parsed IR is a bare named type (no std::/__tc_ wrapper)".
|
|
73
|
+
const ir = parseCppType(t);
|
|
74
|
+
const bare = bareType(ir);
|
|
75
|
+
if (bare.kind !== "named")
|
|
76
|
+
return false;
|
|
77
|
+
// Exclude the pseudo-type markers themselves (which parse to their own kinds,
|
|
78
|
+
// not `named`, so this is belt-and-braces).
|
|
79
|
+
return !t.startsWith("__tc_");
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* True if a return-value expression lowers to a nullish literal — `null`
|
|
83
|
+
* (`nullptr`), `undefined` (`CUTTLEFISH_UNDEFINED`), or a nullish-coalescing
|
|
84
|
+
* `cuttlefish_nullish(x, nullptr)`/`typecad_nullish(...)` whose fallback is
|
|
85
|
+
* nullish. Such a value cannot convert to a struct return type. Demo #14 A.
|
|
86
|
+
*/
|
|
87
|
+
function isNullishReturnValue(value) {
|
|
88
|
+
if (value.kind === "identifier") {
|
|
89
|
+
// `null` → "nullptr" (escaped to nullptr_ at render), `undefined` → the
|
|
90
|
+
// literal identifier "undefined" or the CUTTLEFISH_UNDEFINED macro.
|
|
91
|
+
return value.value === "nullptr" || value.value === "nullptr_" || value.value === "undefined" || value.value === "CUTTLEFISH_UNDEFINED";
|
|
92
|
+
}
|
|
93
|
+
// `x ?? null` lowers to a raw `cuttlefish_nullish(x, nullptr)` /
|
|
94
|
+
// `typecad_nullish(x, nullptr)`. The fallback is the struct-incompatible
|
|
95
|
+
// part; treat the whole expression as nullish for return purposes.
|
|
96
|
+
if (value.kind === "raw") {
|
|
97
|
+
const v = value.value;
|
|
98
|
+
return /(?:cuttlefish|typecad)_nullish\([^,]*,\s*(?:nullptr|CUTTLEFISH_UNDEFINED)\s*\)/.test(v);
|
|
99
|
+
}
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Renders StatementIR nodes to C++ code strings.
|
|
104
|
+
*/
|
|
105
|
+
export class StatementRenderer {
|
|
106
|
+
constructor(context) {
|
|
107
|
+
this.strategy = context.strategy;
|
|
108
|
+
this.knownFunctionReturnTypes = context.knownFunctionReturnTypes;
|
|
109
|
+
this.pointerVarTypes = context.pointerVarTypes;
|
|
110
|
+
this.globalPointerVarTypes = context.globalPointerVarTypes;
|
|
111
|
+
this.classNameMap = context.classNameMap;
|
|
112
|
+
this.varAccessorNames = context.varAccessorNames ?? new Map();
|
|
113
|
+
this.crossModuleClassNames = context.crossModuleClassNames;
|
|
114
|
+
this.enumNames = context.enumNames;
|
|
115
|
+
this.stringEnumNames = context.stringEnumNames ?? new Set();
|
|
116
|
+
this.namespaceNames = context.namespaceNames ?? new Set();
|
|
117
|
+
this.interfaceFieldTypes = context.interfaceFieldTypes ?? new Map();
|
|
118
|
+
this._diagnostics = context.diagnostics ?? [];
|
|
119
|
+
// Create expression renderer with shared context
|
|
120
|
+
this.expressionRenderer = new ExpressionRenderer({
|
|
121
|
+
strategy: context.strategy,
|
|
122
|
+
boardConstants: context.boardConstants,
|
|
123
|
+
classNameMap: context.classNameMap,
|
|
124
|
+
enumNames: context.enumNames,
|
|
125
|
+
stringEnumNames: context.stringEnumNames,
|
|
126
|
+
largeEnumNames: context.largeEnumNames,
|
|
127
|
+
knownFunctionReturnTypes: context.knownFunctionReturnTypes,
|
|
128
|
+
knownVariableTypes: context.knownVariableTypes,
|
|
129
|
+
pointerVarTypes: context.pointerVarTypes,
|
|
130
|
+
stringVarNames: context.stringVarNames,
|
|
131
|
+
cArrayVarNames: context.cArrayVarNames,
|
|
132
|
+
namespaceNames: context.namespaceNames,
|
|
133
|
+
varAccessorNames: context.varAccessorNames,
|
|
134
|
+
typeAccessorNames: context.typeAccessorNames,
|
|
135
|
+
crossModuleClassNames: context.crossModuleClassNames,
|
|
136
|
+
snprintfCounter: context.snprintfCounter,
|
|
137
|
+
interfaceFieldTypes: this.interfaceFieldTypes,
|
|
138
|
+
diagnostics: this._diagnostics,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Gets the underlying expression renderer for direct use.
|
|
143
|
+
*/
|
|
144
|
+
getExprRenderer() {
|
|
145
|
+
return this.expressionRenderer;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Renders a statement IR node to a C++ string, including any snprintf
|
|
149
|
+
* prelude lines accumulated during expression rendering.
|
|
150
|
+
*
|
|
151
|
+
* @param statement The statement to render
|
|
152
|
+
* @param forHeader Whether this is for a header file (no semicolons)
|
|
153
|
+
* @param calleeTransformer Optional transformer for callee names
|
|
154
|
+
* @param knownVariableTypes Optional override for variable type mapping
|
|
155
|
+
* @returns Object with prelude lines and the rendered statement
|
|
156
|
+
*/
|
|
157
|
+
renderWithPrelude(statement, forHeader = false, calleeTransformer, knownVariableTypes) {
|
|
158
|
+
this.expressionRenderer.clearPrelude();
|
|
159
|
+
const rendered = this.render(statement, forHeader, calleeTransformer, knownVariableTypes);
|
|
160
|
+
let prelude = this.expressionRenderer.drainPrelude();
|
|
161
|
+
// When returning a snprintf buffer, make it static so the pointer remains
|
|
162
|
+
// valid after the function returns (avoids dangling pointer to local stack).
|
|
163
|
+
if (statement.kind === "return" && prelude.length > 0) {
|
|
164
|
+
prelude = prelude.map(line => line.replace(/^char /, "static char "));
|
|
165
|
+
}
|
|
166
|
+
return { prelude, statement: rendered };
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Renders a statement IR node to a C++ string.
|
|
170
|
+
*
|
|
171
|
+
* @param statement The statement to render
|
|
172
|
+
* @param forHeader Whether this is for a header file (no semicolons)
|
|
173
|
+
* @param calleeTransformer Optional transformer for callee names
|
|
174
|
+
* @param knownVariableTypes Optional override for variable type mapping
|
|
175
|
+
* @returns The C++ code string
|
|
176
|
+
*/
|
|
177
|
+
render(statement, forHeader = false, calleeTransformer, knownVariableTypes) {
|
|
178
|
+
const rendered = (() => {
|
|
179
|
+
if (statement.kind === "call") {
|
|
180
|
+
return this.renderCall(statement, forHeader, calleeTransformer, knownVariableTypes);
|
|
181
|
+
}
|
|
182
|
+
if (statement.kind === "assign") {
|
|
183
|
+
// `statement.target` may be a compound member-access string
|
|
184
|
+
// (`this.field`, `obj->field`, `obj.field`) — NOT a bare identifier.
|
|
185
|
+
// escapeCppKeyword renames only WHOLE identifiers that are in the
|
|
186
|
+
// reserved set, so applying it to `this.min` leaves `min` untouched
|
|
187
|
+
// even when `min` is a reserved Arduino macro. That diverged from
|
|
188
|
+
// renderPropertyAccess (which escapes just the trailing property
|
|
189
|
+
// name), so a field declared `min_` (via renameStructField) was
|
|
190
|
+
// written as `this->min = ...` on assignment but read as
|
|
191
|
+
// `this->min_` on access — a declaration/access rename mismatch that
|
|
192
|
+
// fails at g++ time. Escape the trailing member name uniformly here.
|
|
193
|
+
let target = escapeTrailingMember(statement.target, this.strategy.reservedNames());
|
|
194
|
+
target = this.arrowGlobalPointerTarget(target);
|
|
195
|
+
// A namespace member used as an ASSIGNMENT TARGET must use `::`
|
|
196
|
+
// (scope resolution), not `.` — a namespace is not an object. The
|
|
197
|
+
// property-READ path already does this via namespaceNames; the
|
|
198
|
+
// assign-target path did not, so `Devices.total = ...` emitted with
|
|
199
|
+
// `.` and failed at g++ time (namespace stress test Finding 3).
|
|
200
|
+
const nsTargetMatch = target.match(/^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)/);
|
|
201
|
+
if (nsTargetMatch && this.namespaceNames.has(nsTargetMatch[1])) {
|
|
202
|
+
target = target.replace(/^([A-Za-z_$][\w$]*)\./, "$1::");
|
|
203
|
+
}
|
|
204
|
+
// Multi-level static access target: `Ns::Class.member` (a namespace,
|
|
205
|
+
// then a static class member). After the single-level rewrite above,
|
|
206
|
+
// a `Devices.Registry.count = ...` target is `Devices::Registry.count`.
|
|
207
|
+
// `Registry.count` is a static class member, so the second access is
|
|
208
|
+
// also `::` → `Devices::Registry::count` (namespace stress test
|
|
209
|
+
// Finding 4). Only fires when the target begins with a `::`-qualified
|
|
210
|
+
// namespace path (so a genuine `obj.field` instance access is
|
|
211
|
+
// untouched).
|
|
212
|
+
const nsClassTargetMatch = target.match(/^([A-Za-z_$][\w$]*)::([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)/);
|
|
213
|
+
if (nsClassTargetMatch && this.namespaceNames.has(nsClassTargetMatch[1])) {
|
|
214
|
+
target = target.replace(/^([A-Za-z_$][\w$]*)::([A-Za-z_$][\w$]*)\./, "$1::$2::");
|
|
215
|
+
}
|
|
216
|
+
// Rewrite setter assignments: c->count = val → c->setCount(val)
|
|
217
|
+
if (statement.operator === "=" || statement.operator === "+=" || statement.operator === "-=") {
|
|
218
|
+
const setterMatch = target.match(/^(.+?)(->|\.)(\w+)$/);
|
|
219
|
+
if (setterMatch) {
|
|
220
|
+
const [, objStr, sep, propName] = setterMatch;
|
|
221
|
+
const varName = objStr.trim();
|
|
222
|
+
const accessors = this.varAccessorNames.get(varName);
|
|
223
|
+
if (accessors?.has(propName)) {
|
|
224
|
+
const kind = accessors.get(propName);
|
|
225
|
+
if (kind === "setter" || kind === "both") {
|
|
226
|
+
const setterName = accessorSetterName(propName);
|
|
227
|
+
const renderedValue = this.expressionRenderer.render(statement.value, undefined, knownVariableTypes);
|
|
228
|
+
if (statement.operator === "=") {
|
|
229
|
+
return forHeader
|
|
230
|
+
? `${varName}${sep}${setterName}(${renderedValue})`
|
|
231
|
+
: `${varName}${sep}${setterName}(${renderedValue});`;
|
|
232
|
+
}
|
|
233
|
+
const getterName = accessorGetterName(propName);
|
|
234
|
+
const op = statement.operator.replace("=", "");
|
|
235
|
+
return forHeader
|
|
236
|
+
? `${varName}${sep}${setterName}(${varName}${sep}${getterName}() ${op} ${renderedValue})`
|
|
237
|
+
: `${varName}${sep}${setterName}(${varName}${sep}${getterName}() ${op} ${renderedValue});`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Render the assigned value type-aware: when the target's resolved
|
|
243
|
+
// C++ type sits on the other side of an enum↔integral boundary from
|
|
244
|
+
// the value (e.g. `this->nxt[r][c] = next` where `nxt` is
|
|
245
|
+
// `vector<vector<uint8_t>>` and `next` is an `enum class Cell`),
|
|
246
|
+
// `renderValueForTarget` inserts the required `static_cast`. Falls
|
|
247
|
+
// through to plain rendering when the target type is unknown. Demo #32 A.
|
|
248
|
+
const assignTargetType = this.expressionRenderer.inferLvalueCppType(statement.target, knownVariableTypes);
|
|
249
|
+
const renderedAssignValue = assignTargetType
|
|
250
|
+
? this.expressionRenderer.renderValueForTarget(statement.value, assignTargetType, knownVariableTypes)
|
|
251
|
+
: this.expressionRenderer.render(statement.value, undefined, knownVariableTypes);
|
|
252
|
+
return forHeader
|
|
253
|
+
? `${target} ${statement.operator} ${renderedAssignValue}`
|
|
254
|
+
: `${target} ${statement.operator} ${renderedAssignValue};`;
|
|
255
|
+
}
|
|
256
|
+
if (statement.kind === "update") {
|
|
257
|
+
let target = escapeCppKeyword(statement.target, this.strategy.reservedNames());
|
|
258
|
+
target = this.arrowGlobalPointerTarget(target);
|
|
259
|
+
return statement.prefix
|
|
260
|
+
? `${statement.operator}${target}${forHeader ? "" : ";"}`
|
|
261
|
+
: `${target}${statement.operator}${forHeader ? "" : ";"}`;
|
|
262
|
+
}
|
|
263
|
+
if (statement.kind === "return") {
|
|
264
|
+
if (!statement.value)
|
|
265
|
+
return "return;";
|
|
266
|
+
// When the enclosing function returns a STRUCT (a non-primitive,
|
|
267
|
+
// non-pointer value type such as an interface), a `return null` /
|
|
268
|
+
// `return undefined` (or `return map.get(k) ?? null`) cannot lower to
|
|
269
|
+
// `return nullptr;` / `return CUTTLEFISH_UNDEFINED;` — those are
|
|
270
|
+
// integer/pointer literals that won't convert to the struct type.
|
|
271
|
+
// Lower to a value-initialized `return {};` instead. The `T | null`
|
|
272
|
+
// union already strips to `T` (SUPPORT_MATRIX §1.8), so the function
|
|
273
|
+
// signature really does return the struct. Demo #14 Finding A.
|
|
274
|
+
const retType = statement.functionReturnType;
|
|
275
|
+
if (retType && isStructReturnType(retType, this.strategy) && isNullishReturnValue(statement.value)) {
|
|
276
|
+
return `return {};`;
|
|
277
|
+
}
|
|
278
|
+
const renderedValue = retType && this.enumNames.has(retType)
|
|
279
|
+
? this.expressionRenderer.render(statement.value, undefined, knownVariableTypes)
|
|
280
|
+
: this.expressionRenderer.renderEnumSafeValue(statement.value, knownVariableTypes);
|
|
281
|
+
return `return ${renderedValue};`;
|
|
282
|
+
}
|
|
283
|
+
if (statement.kind === "while") {
|
|
284
|
+
return `while (${this.expressionRenderer.render(statement.condition, undefined, knownVariableTypes)})`;
|
|
285
|
+
}
|
|
286
|
+
if (statement.kind === "if") {
|
|
287
|
+
return `if (${this.expressionRenderer.render(statement.condition, undefined, knownVariableTypes)})`;
|
|
288
|
+
}
|
|
289
|
+
if (statement.kind === "for") {
|
|
290
|
+
let init = "";
|
|
291
|
+
if (statement.initializer) {
|
|
292
|
+
if (statement.initializer.kind === "var_decl" && statement.initializer.storage === "var") {
|
|
293
|
+
const declaredType = this.normalizeCppType(statement.initializer.cppType);
|
|
294
|
+
this.expressionRenderer.pushPrelude([`${declaredType} ${escapeCppKeyword(statement.initializer.name, this.strategy.reservedNames())};`]);
|
|
295
|
+
const safeName = escapeCppKeyword(statement.initializer.name, this.strategy.reservedNames());
|
|
296
|
+
init = statement.initializer.initializer
|
|
297
|
+
? `${safeName} = ${this.expressionRenderer.render(statement.initializer.initializer, calleeTransformer, knownVariableTypes)}`
|
|
298
|
+
: safeName;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
init = this.render(statement.initializer, true, calleeTransformer, knownVariableTypes);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const cond = statement.condition ? this.expressionRenderer.render(statement.condition, undefined, knownVariableTypes) : "";
|
|
305
|
+
const incr = statement.increment ? this.render(statement.increment, true, calleeTransformer, knownVariableTypes) : "";
|
|
306
|
+
return `for (${init}; ${cond}; ${incr})`;
|
|
307
|
+
}
|
|
308
|
+
if (statement.kind === "for_of") {
|
|
309
|
+
const varDecl = statement.variable;
|
|
310
|
+
if (varDecl.kind === "var_decl") {
|
|
311
|
+
// Range-for by reference for non-primitive element types (structs,
|
|
312
|
+
// classes, strings) to avoid the per-iteration copy g++ warns about
|
|
313
|
+
// (-Wrange-loop-construct). Primitives stay by value.
|
|
314
|
+
const isRef = !isPrimitiveCppType(varDecl.cppType) && !isIndirectType(varDecl.cppType, this.strategy);
|
|
315
|
+
return `for (${this.renderTypedName(varDecl.cppType, varDecl.name, varDecl.storage === "const", isRef)} : ${this.expressionRenderer.render(statement.iterable, undefined, knownVariableTypes)})`;
|
|
316
|
+
}
|
|
317
|
+
return `for (auto item : ${this.expressionRenderer.render(statement.iterable, undefined, knownVariableTypes)})`;
|
|
318
|
+
}
|
|
319
|
+
if (statement.kind === "for_in") {
|
|
320
|
+
if (statement.keys && statement.keys.length > 0) {
|
|
321
|
+
const keysArr = statement.keys.map(k => `"${k}"`).join(", ");
|
|
322
|
+
const objName = statement.object.kind === "identifier" ? statement.object.value : "_obj";
|
|
323
|
+
const idxVar = `_ki_${objName}`;
|
|
324
|
+
this.expressionRenderer.pushPrelude([
|
|
325
|
+
`const char* ${idxVar}_keys[] = { ${keysArr} };`,
|
|
326
|
+
]);
|
|
327
|
+
return `for (${this.strategy.defaultNumericType()} ${idxVar} = 0; ${idxVar} < ${statement.keys.length}; ${idxVar}++)`;
|
|
328
|
+
}
|
|
329
|
+
const varDecl = statement.variable;
|
|
330
|
+
if (varDecl.kind === "var_decl") {
|
|
331
|
+
const isRef = !isPrimitiveCppType(varDecl.cppType) && !isIndirectType(varDecl.cppType, this.strategy);
|
|
332
|
+
return `for (${this.renderTypedName(varDecl.cppType, varDecl.name, varDecl.storage === "const", isRef)} : ${this.expressionRenderer.render(statement.object, undefined, knownVariableTypes)})`;
|
|
333
|
+
}
|
|
334
|
+
return `for (auto key : ${this.expressionRenderer.render(statement.object, undefined, knownVariableTypes)})`;
|
|
335
|
+
}
|
|
336
|
+
if (statement.kind === "break") {
|
|
337
|
+
return statement.label ? `goto __break_${statement.label};` : "break;";
|
|
338
|
+
}
|
|
339
|
+
if (statement.kind === "continue") {
|
|
340
|
+
return statement.label ? `continue /* ${statement.label}: labeled continue uses normal continue in C++ */;` : "continue;";
|
|
341
|
+
}
|
|
342
|
+
if (statement.kind === "do_while") {
|
|
343
|
+
return `do`;
|
|
344
|
+
}
|
|
345
|
+
if (statement.kind === "switch") {
|
|
346
|
+
const discExpr = statement.expression;
|
|
347
|
+
const discText = this.expressionRenderer.render(discExpr, undefined, knownVariableTypes);
|
|
348
|
+
// C++ switch requires an integral discriminant. Since `number` vars are now
|
|
349
|
+
// `double`/`float` (Issue 5), cast floating-point discriminants to int.
|
|
350
|
+
let needsIntCast = false;
|
|
351
|
+
if (discExpr.kind === "identifier") {
|
|
352
|
+
const t = knownVariableTypes?.get(discExpr.value)?.cppType;
|
|
353
|
+
needsIntCast = t === "double" || t === "float" || t === "long double";
|
|
354
|
+
}
|
|
355
|
+
else if (discExpr.kind === "number" && !Number.isInteger(discExpr.value)) {
|
|
356
|
+
needsIntCast = true;
|
|
357
|
+
}
|
|
358
|
+
return `switch (${needsIntCast ? `static_cast<int>(${discText})` : discText})`;
|
|
359
|
+
}
|
|
360
|
+
if (statement.kind === "try") {
|
|
361
|
+
return "try";
|
|
362
|
+
}
|
|
363
|
+
if (statement.kind === "throw") {
|
|
364
|
+
return this.strategy.renderThrow(this.expressionRenderer.render(statement.value, undefined, knownVariableTypes));
|
|
365
|
+
}
|
|
366
|
+
if (statement.kind === "labeled") {
|
|
367
|
+
// The user's TS label is documentary only — actual control flow
|
|
368
|
+
// uses `goto __break_<label>`. Emitting it as a bare C++ label
|
|
369
|
+
// (`label:`) triggers -Wunused-label because no goto targets it
|
|
370
|
+
// directly. Emit as a comment to preserve the source mapping
|
|
371
|
+
// without the warning.
|
|
372
|
+
return `/* ${statement.label}: */`;
|
|
373
|
+
}
|
|
374
|
+
if (statement.kind === "yield") {
|
|
375
|
+
if (statement.isDelegate && statement.value) {
|
|
376
|
+
const val = this.expressionRenderer.render(statement.value, undefined, knownVariableTypes);
|
|
377
|
+
return `for (auto& __elem : ${val}) co_yield __elem;`;
|
|
378
|
+
}
|
|
379
|
+
if (statement.value) {
|
|
380
|
+
return `co_yield ${this.expressionRenderer.render(statement.value, undefined, knownVariableTypes)};`;
|
|
381
|
+
}
|
|
382
|
+
return "co_yield;";
|
|
383
|
+
}
|
|
384
|
+
if (statement.kind === "block") {
|
|
385
|
+
return `{`;
|
|
386
|
+
}
|
|
387
|
+
if (statement.kind === "hal-op") {
|
|
388
|
+
const resolved = routeHALOp(statement.operation, this.strategy);
|
|
389
|
+
if (resolved?.code) {
|
|
390
|
+
// Strip leading 'return ' from raw hal-op code when emitted as a
|
|
391
|
+
// standalone statement. The HAL definition includes `return` because
|
|
392
|
+
// the TypeScript stub returns a value, but the C++ statement context
|
|
393
|
+
// (e.g. inside void setup()) does not expect it.
|
|
394
|
+
let code = resolved.code;
|
|
395
|
+
if (code.startsWith('return '))
|
|
396
|
+
code = code.slice('return '.length);
|
|
397
|
+
// A raw hal-op statement (e.g. `Async.sleep(10)` →
|
|
398
|
+
// `__cuttlefish_async_sleep(10)`) is a complete C++ statement and
|
|
399
|
+
// needs a terminating semicolon, unless it already ends with one or
|
|
400
|
+
// with `}` (a compound block).
|
|
401
|
+
if (!forHeader && !code.endsWith(';') && !code.endsWith('}')) {
|
|
402
|
+
code = code + ';';
|
|
403
|
+
}
|
|
404
|
+
return code;
|
|
405
|
+
}
|
|
406
|
+
if (resolved?.expression) {
|
|
407
|
+
return forHeader ? resolved.expression : `${resolved.expression};`;
|
|
408
|
+
}
|
|
409
|
+
// Unregistered HAL op: surface as a warning so the user sees it, but
|
|
410
|
+
// keep HAL as an extensibility point. The bare comment is retained as
|
|
411
|
+
// a visual marker in the generated C++.
|
|
412
|
+
this._diagnostics.push({
|
|
413
|
+
severity: "warning",
|
|
414
|
+
code: "TS2CPP_UNHANDLED_HAL",
|
|
415
|
+
message: `HAL operation '${statement.operation.operation}' is not registered with the platform strategy; emitting a placeholder comment.`,
|
|
416
|
+
});
|
|
417
|
+
return `/* unhandled hal-op: ${statement.operation.operation} */`;
|
|
418
|
+
}
|
|
419
|
+
if (statement.kind !== "var_decl") {
|
|
420
|
+
// A StatementIR kind the renderer doesn't know how to render is a
|
|
421
|
+
// transpiler bug — the renderer should cover every kind the IR
|
|
422
|
+
// statement builders produce. This arm is unreachable in the normal
|
|
423
|
+
// transpileFile path (IR lowering marks unsupported statements as
|
|
424
|
+
// errors and the build aborts before emit). Throw so any path that
|
|
425
|
+
// does reach here fails loudly instead of emitting
|
|
426
|
+
// "/* unsupported_statement */" into the C++.
|
|
427
|
+
const kind = statement.kind ?? "<unknown>";
|
|
428
|
+
throw new Error(`StatementRenderer: unsupported StatementIR kind '${kind}' reached emission. ` +
|
|
429
|
+
`This is a transpiler bug; the renderer is missing a case for this kind.`);
|
|
430
|
+
}
|
|
431
|
+
return this.renderVarDecl(statement, forHeader, calleeTransformer, knownVariableTypes);
|
|
432
|
+
})();
|
|
433
|
+
return normalizeRawExpression(rendered, this.strategy, this.classNameMap);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Arrow the leading receiver of an assign/update target when it is a
|
|
437
|
+
* module-scope (file-global) pointer variable: `btn.lastPress` →
|
|
438
|
+
* `btn->lastPress`. This covers ISR-captured globals whose pointer type
|
|
439
|
+
* wasn't visible at IR build time (the target is a plain string here, not a
|
|
440
|
+
* property-access IR node). Only the leading receiver is rewritten — a
|
|
441
|
+
* chain like `btn->field.x` is left alone. Replaces the file-wide text sweep
|
|
442
|
+
* formerly in output-finalizer.ts.
|
|
443
|
+
*/
|
|
444
|
+
arrowGlobalPointerTarget(target) {
|
|
445
|
+
if (!this.globalPointerVarTypes || this.globalPointerVarTypes.size === 0)
|
|
446
|
+
return target;
|
|
447
|
+
const m = target.match(/^([A-Za-z_$][\w$]*)\./);
|
|
448
|
+
if (m && this.globalPointerVarTypes.has(m[1])) {
|
|
449
|
+
return target.replace(/^([A-Za-z_$][\w$]*)\./, "$1->");
|
|
450
|
+
}
|
|
451
|
+
return target;
|
|
452
|
+
}
|
|
453
|
+
fixCrossModuleMethodCall(callee) {
|
|
454
|
+
const lastDot = callee.lastIndexOf(".");
|
|
455
|
+
if (lastDot === -1) {
|
|
456
|
+
return callee;
|
|
457
|
+
}
|
|
458
|
+
const receiverCallee = callee.slice(0, lastDot);
|
|
459
|
+
const memberName = callee.slice(lastDot + 1);
|
|
460
|
+
const receiverMatch = receiverCallee.match(/^([A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\s*\(/);
|
|
461
|
+
if (!receiverMatch) {
|
|
462
|
+
return callee;
|
|
463
|
+
}
|
|
464
|
+
const receiverCallName = receiverMatch[1];
|
|
465
|
+
const receiverClassName = receiverCallName.split(".")[0];
|
|
466
|
+
const hasKnown = this.knownFunctionReturnTypes?.has(receiverCallName);
|
|
467
|
+
const returnType = this.knownFunctionReturnTypes?.get(receiverCallName);
|
|
468
|
+
const isCrossModuleClass = this.crossModuleClassNames?.has(receiverClassName);
|
|
469
|
+
if (!hasKnown && !isCrossModuleClass) {
|
|
470
|
+
return callee;
|
|
471
|
+
}
|
|
472
|
+
const callPrefix = receiverCallee.replace(new RegExp(`^${receiverCallName.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")}\\s*\\(`), `${receiverCallName.replace(/\./g, "::")}(`);
|
|
473
|
+
if ((returnType && this.strategy.isPointerType(returnType)) || isCrossModuleClass) {
|
|
474
|
+
return `${callPrefix}->${memberName}`;
|
|
475
|
+
}
|
|
476
|
+
return `${callPrefix}.${memberName}`;
|
|
477
|
+
}
|
|
478
|
+
renderCall(statement, forHeader, calleeTransformer, knownVariableTypes) {
|
|
479
|
+
// HAL bus ownership markers (take/release) — compile-time only, no C++ emission.
|
|
480
|
+
if (statement.args.length === 0 && /\.(take|release)$/.test(statement.callee)) {
|
|
481
|
+
return "";
|
|
482
|
+
}
|
|
483
|
+
// Handle raw statements from setupInitCode
|
|
484
|
+
if (statement.callee.startsWith('__RAW_STMT__')) {
|
|
485
|
+
const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
|
|
486
|
+
return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
|
|
487
|
+
}
|
|
488
|
+
// Handle emit() — compile-time C++ injection
|
|
489
|
+
if (statement.callee === "__EMIT__") {
|
|
490
|
+
const rawText = statement.args.map(arg => this.renderEmitArg(arg)).join("");
|
|
491
|
+
return forHeader ? rawText : `${rawText.endsWith(';') ? rawText : rawText + ';'}`;
|
|
492
|
+
}
|
|
493
|
+
// Handle console.* calls specially
|
|
494
|
+
if (isConsoleCall(statement.callee)) {
|
|
495
|
+
return this.transformConsoleCall(statement.callee, statement.args, forHeader, knownVariableTypes);
|
|
496
|
+
}
|
|
497
|
+
let callee = statement.callee;
|
|
498
|
+
if (callee.startsWith("this.")) {
|
|
499
|
+
callee = `this->${callee.slice("this.".length)}`;
|
|
500
|
+
}
|
|
501
|
+
if (calleeTransformer) {
|
|
502
|
+
callee = calleeTransformer(callee);
|
|
503
|
+
}
|
|
504
|
+
// Apply the strategy's user-function rename to BARE free-function callees
|
|
505
|
+
// (no `.`/`->`/`::` — those are method/qualified calls, not free fns). This
|
|
506
|
+
// is the single chokepoint through which every call statement is rendered
|
|
507
|
+
// (function bodies, class methods, AND top-level executables spliced into
|
|
508
|
+
// the auto-generated entrypoint), so a rename like Arduino's `main` →
|
|
509
|
+
// `cuttlefish_main` propagates to every call site uniformly, including the
|
|
510
|
+
// `main()` call a top-level executable flows into `setup()`. Routed through
|
|
511
|
+
// mapFunctionName (which the entrypoint sentinel and the strategy both
|
|
512
|
+
// weigh in on), so it stays a no-op on targets with no rename.
|
|
513
|
+
if (!callee.includes('.') && !callee.includes("->") && !callee.includes("::")) {
|
|
514
|
+
callee = this.mapFunctionName(callee);
|
|
515
|
+
}
|
|
516
|
+
callee = this.fixCrossModuleMethodCall(callee);
|
|
517
|
+
callee = this.arrowGlobalPointerTarget(callee);
|
|
518
|
+
const renderedArgs = statement.args.map((arg) => this.expressionRenderer.render(arg, undefined, knownVariableTypes)).join(", ");
|
|
519
|
+
return forHeader ? `${callee}(${renderedArgs})` : `${callee}(${renderedArgs});`;
|
|
520
|
+
}
|
|
521
|
+
renderVarDecl(statement, forHeader, calleeTransformer, knownVariableTypes) {
|
|
522
|
+
// Register this declaration's type in the active scope so that downstream
|
|
523
|
+
// expression rendering (e.g. `arr.length` → sizeof for C arrays) can look
|
|
524
|
+
// up the variable's C++ type. Without this, locals in top-level/generated
|
|
525
|
+
// functions aren't visible to renderPropertyAccess.
|
|
526
|
+
if (knownVariableTypes && statement.cppType) {
|
|
527
|
+
knownVariableTypes.set(statement.name, { cppType: statement.cppType });
|
|
528
|
+
}
|
|
529
|
+
const declaredType = this.normalizeCppType(statement.cppType);
|
|
530
|
+
const volatilePrefix = statement.isVolatile ? "volatile " : "";
|
|
531
|
+
// Transform type name for Arduino library classes (add namespace prefix)
|
|
532
|
+
const transformedType = transformTypeName(statement.cppType, this.classNameMap);
|
|
533
|
+
const ownershipKind = statement.ownershipKind;
|
|
534
|
+
// Emit const for Shared<T> ownership annotations (ownershipKind === 'shared')
|
|
535
|
+
const isConst = statement.storage === "const" || ownershipKind === 'shared';
|
|
536
|
+
// Emit C++ reference for non-primitive Shared<T>/Mutable<T> from named variables.
|
|
537
|
+
// Primitives pass by value (no overhead). Temporary/literal initializers fall back to copy.
|
|
538
|
+
const isRef = (ownershipKind === 'shared' || ownershipKind === 'mutable')
|
|
539
|
+
&& !isPrimitiveCppType(statement.cppType)
|
|
540
|
+
&& !isIndirectType(statement.cppType, this.strategy)
|
|
541
|
+
&& statement.initializer?.kind === 'identifier';
|
|
542
|
+
const declaration = `${volatilePrefix}${this.renderTypedName(transformedType, statement.name, isConst, isRef)}`;
|
|
543
|
+
if (statement.initializer) {
|
|
544
|
+
// Handle lambda initializers: const fn = (x) => expr
|
|
545
|
+
if (statement.initializer.kind === "lambda") {
|
|
546
|
+
const params = statement.initializer.params.map(p => `${p.cppType} ${p.name}`).join(", ");
|
|
547
|
+
const ret = statement.initializer.returnType && statement.initializer.returnType !== "auto"
|
|
548
|
+
? ` -> ${statement.initializer.returnType}` : "";
|
|
549
|
+
const bodyStr = statement.initializer.body.map(s => " " + this.render(s, false, calleeTransformer, knownVariableTypes)).join("\n");
|
|
550
|
+
const safeName = escapeCppKeyword(statement.name, this.strategy.reservedNames());
|
|
551
|
+
return `auto ${safeName} = [&](${params})${ret} {\n${bodyStr}\n};`;
|
|
552
|
+
}
|
|
553
|
+
// Handle array initializers
|
|
554
|
+
if (statement.initializer.kind === "array") {
|
|
555
|
+
const safeArrName = escapeCppKeyword(statement.name, this.strategy.reservedNames());
|
|
556
|
+
const rawType = statement.cppType;
|
|
557
|
+
const hasObjectElements = statement.initializer.elements.some(e => e.kind === "object");
|
|
558
|
+
if (hasObjectElements) {
|
|
559
|
+
// Check if the declared array type's element is a known named type
|
|
560
|
+
// (e.g. `const pts: Point[]` → std::vector<Point>). If so, use Point
|
|
561
|
+
// directly instead of generating a shadow `_{name}_t` struct (which
|
|
562
|
+
// collides when the same shape appears at multiple sites — demo #11
|
|
563
|
+
// Finding D).
|
|
564
|
+
let elementTypeName = null;
|
|
565
|
+
// Element type of std::vector<T> or T[], detected structurally.
|
|
566
|
+
const elemType = parsedElementString(rawType) ?? null;
|
|
567
|
+
if (elemType && /^[A-Z]/.test(elemType)) {
|
|
568
|
+
elementTypeName = elemType;
|
|
569
|
+
}
|
|
570
|
+
if (elementTypeName) {
|
|
571
|
+
// Use the named element type directly — no shadow struct needed.
|
|
572
|
+
const elements = statement.initializer.elements.map((e) => {
|
|
573
|
+
if (e.kind === "object") {
|
|
574
|
+
const initValues = e.fields.map((f) => {
|
|
575
|
+
const val = this.expressionRenderer.render(f.value, undefined);
|
|
576
|
+
return val;
|
|
577
|
+
}).join(", ");
|
|
578
|
+
return `{ ${initValues} }`;
|
|
579
|
+
}
|
|
580
|
+
return this.expressionRenderer.render(e, undefined);
|
|
581
|
+
});
|
|
582
|
+
const vecType = `std::vector<${elementTypeName}>`;
|
|
583
|
+
const safeArrName2 = escapeCppKeyword(statement.name, this.strategy.reservedNames());
|
|
584
|
+
const constPrefix2 = isConst ? "const " : "";
|
|
585
|
+
return `${constPrefix2}${vecType} ${safeArrName2} = { ${elements} };`;
|
|
586
|
+
}
|
|
587
|
+
const structName = `_${statement.name}_t`;
|
|
588
|
+
// Compile-time-only namespace values (e.g. the `ui` authoring handle
|
|
589
|
+
// from @typecad/ui) are intercepted at IR-build time; their calls
|
|
590
|
+
// lower to IR but the binding itself must emit nothing. Skip the
|
|
591
|
+
// struct synthesis + initializer for these.
|
|
592
|
+
if (activeNamespaceNames.has(statement.name)) {
|
|
593
|
+
return "";
|
|
594
|
+
}
|
|
595
|
+
const firstObj = statement.initializer.elements.find(e => e.kind === "object");
|
|
596
|
+
const nestedStructs = collectNestedStructDefs(firstObj, statement.name, this.pointerVarTypes, this.knownFunctionReturnTypes);
|
|
597
|
+
if (nestedStructs.length > 0) {
|
|
598
|
+
for (const nested of nestedStructs) {
|
|
599
|
+
this.interfaceFieldTypes.set(nested.structName, new Map(nested.fields.map((field) => [field.name, field.type])));
|
|
600
|
+
}
|
|
601
|
+
const nestedDefs = nestedStructs.map((ns) => `struct ${ns.structName} { ${ns.fields.map((f) => `${f.type} ${f.name};`).join(" ")} };`);
|
|
602
|
+
this.expressionRenderer.pushPrelude(nestedDefs);
|
|
603
|
+
}
|
|
604
|
+
const fieldTypes = new Map(firstObj.fields.map((field) => [
|
|
605
|
+
field.name,
|
|
606
|
+
this.inferFieldType(field.value, statement.name, field.name),
|
|
607
|
+
]));
|
|
608
|
+
this.interfaceFieldTypes.set(structName, fieldTypes);
|
|
609
|
+
const fieldDefs = firstObj.fields
|
|
610
|
+
.map((f) => `${fieldTypes.get(f.name)} ${f.name};`)
|
|
611
|
+
.join(" ");
|
|
612
|
+
this.expressionRenderer.pushPrelude([`struct ${structName} { ${fieldDefs} };`]);
|
|
613
|
+
const streamFields = firstObj.fields
|
|
614
|
+
.map((f) => `"${f.name}: " << obj.${f.name}`)
|
|
615
|
+
.join(' << ", " << ');
|
|
616
|
+
this.expressionRenderer.pushPrelude([
|
|
617
|
+
`std::ostream& operator<<(std::ostream& os, const ${structName}& obj) { os << ${streamFields}; return os; }`
|
|
618
|
+
]);
|
|
619
|
+
const elements = statement.initializer.elements.map((e) => {
|
|
620
|
+
if (e.kind === "object") {
|
|
621
|
+
const initValues = e.fields.map((f) => {
|
|
622
|
+
const renderExpr = (expr) => this.expressionRenderer.render(expr, calleeTransformer, knownVariableTypes);
|
|
623
|
+
const overridden = this.strategy.objectFieldInitializer(f.value, renderExpr);
|
|
624
|
+
if (overridden !== undefined)
|
|
625
|
+
return overridden;
|
|
626
|
+
return renderExpr(f.value);
|
|
627
|
+
}).join(", ");
|
|
628
|
+
return `{ ${initValues} }`;
|
|
629
|
+
}
|
|
630
|
+
return this.expressionRenderer.render(e, undefined, knownVariableTypes);
|
|
631
|
+
}).join(", ");
|
|
632
|
+
const vecType = `std::vector<${structName}>`;
|
|
633
|
+
return forHeader
|
|
634
|
+
? `${vecType} ${safeArrName} = { ${elements} }`
|
|
635
|
+
: `${vecType} ${safeArrName} = { ${elements} };`;
|
|
636
|
+
}
|
|
637
|
+
const elements = statement.initializer.elements.map((e) => this.expressionRenderer.render(e, undefined, knownVariableTypes)).join(", ");
|
|
638
|
+
if (this.strategy.needsStdVector() && parsedIsVector(rawType)) {
|
|
639
|
+
return forHeader
|
|
640
|
+
? `${declaration} = { ${elements} }`
|
|
641
|
+
: `${declaration} = { ${elements} };`;
|
|
642
|
+
}
|
|
643
|
+
if (!this.strategy.needsStdVector() && parsedIsVector(rawType)) {
|
|
644
|
+
// Non-mutable arrays annotated as Array<T> or ReadonlyArray<T> on platforms
|
|
645
|
+
// that don't support std::vector → emit as a plain C-style array.
|
|
646
|
+
// Mutable arrays (.push/.pop/.indexOf) are rewritten to StaticArray<int> in
|
|
647
|
+
// the IR builder (via mutableArrayVars) and never reach this branch.
|
|
648
|
+
const elementType = parsedElementString(rawType) ?? this.strategy.defaultNumericType();
|
|
649
|
+
return forHeader
|
|
650
|
+
? `${elementType} ${safeArrName}[] = { ${elements} }`
|
|
651
|
+
: `${elementType} ${safeArrName}[] = { ${elements} };`;
|
|
652
|
+
}
|
|
653
|
+
// Use "int" for "auto" element type since C arrays need explicit types
|
|
654
|
+
const arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
|
|
655
|
+
return forHeader
|
|
656
|
+
? `${arrayType} ${safeArrName}[] = { ${elements} }`
|
|
657
|
+
: `${arrayType} ${safeArrName}[] = { ${elements} };`;
|
|
658
|
+
}
|
|
659
|
+
// Handle object initializers with inline struct definition
|
|
660
|
+
if (statement.initializer.kind === "object") {
|
|
661
|
+
const placeholderStructName = `_${statement.name}_t`;
|
|
662
|
+
// When the source annotation names a concrete type (e.g. an exported
|
|
663
|
+
// interface referenced across modules: `const cfg: ThresholdConfig = {...}`),
|
|
664
|
+
// cppType already carries that interface/struct name. The struct is declared
|
|
665
|
+
// elsewhere (the interface declaration), so we must NOT emit an inline
|
|
666
|
+
// `struct _name_t {...}` here — that would conflict with the real type.
|
|
667
|
+
// Only synthesize when no explicit named type was provided.
|
|
668
|
+
const declaredCppType = statement.cppType;
|
|
669
|
+
const hasExplicitNamedType = !!declaredCppType &&
|
|
670
|
+
declaredCppType !== "auto" &&
|
|
671
|
+
declaredCppType !== placeholderStructName &&
|
|
672
|
+
parsedIsPlainStructType(declaredCppType);
|
|
673
|
+
const fieldTypes = new Map(statement.initializer.fields.map((field) => [
|
|
674
|
+
field.name,
|
|
675
|
+
this.inferFieldType(field.value, statement.name, field.name),
|
|
676
|
+
]));
|
|
677
|
+
const initValues = statement.initializer.fields
|
|
678
|
+
.map((f) => {
|
|
679
|
+
const renderExpr = (e) => this.expressionRenderer.render(e, calleeTransformer, knownVariableTypes);
|
|
680
|
+
const overridden = this.strategy.objectFieldInitializer(f.value, renderExpr);
|
|
681
|
+
if (overridden !== undefined)
|
|
682
|
+
return overridden;
|
|
683
|
+
return renderExpr(f.value);
|
|
684
|
+
})
|
|
685
|
+
.join(", ");
|
|
686
|
+
const safeObjName = escapeCppKeyword(statement.name, this.strategy.reservedNames());
|
|
687
|
+
if (hasExplicitNamedType) {
|
|
688
|
+
// Emit the definition using the declared named type; no inline struct.
|
|
689
|
+
// const-ness follows the declared storage so it matches the extern.
|
|
690
|
+
const namedType = this.normalizeCppType(declaredCppType);
|
|
691
|
+
const isConst = statement.storage === "const";
|
|
692
|
+
const constPrefix = isConst ? "const " : "";
|
|
693
|
+
// Demo #18 Finding C: do NOT overwrite an existing authoritative
|
|
694
|
+
// field-type map for this named type (registered in setup.ts from the
|
|
695
|
+
// interface/class declaration). The inferred fieldTypes here come from
|
|
696
|
+
// the initializer VALUES and, after strategy normalization, can be
|
|
697
|
+
// wider/incorrect (e.g. int32_t + name -> all "long long" under the
|
|
698
|
+
// native strategy). Clobbering the correct declared types then makes
|
|
699
|
+
// downstream snprintf format inference pick wrong specifiers
|
|
700
|
+
// (e.g. "%lld" + missing .c_str() for a std::string field). Only seed
|
|
701
|
+
// the map when no declared entry exists (e.g. an anonymous struct).
|
|
702
|
+
if (!this.interfaceFieldTypes.has(namedType)) {
|
|
703
|
+
this.interfaceFieldTypes.set(namedType, fieldTypes);
|
|
704
|
+
}
|
|
705
|
+
return forHeader
|
|
706
|
+
? `${constPrefix}${namedType} ${safeObjName} = { ${initValues} }`
|
|
707
|
+
: `${constPrefix}${namedType} ${safeObjName} = { ${initValues} };`;
|
|
708
|
+
}
|
|
709
|
+
const structName = placeholderStructName;
|
|
710
|
+
// Collect nested struct definitions (deepest first) and emit as prelude
|
|
711
|
+
const nestedStructs = collectNestedStructDefs(statement.initializer, statement.name, this.pointerVarTypes, this.knownFunctionReturnTypes);
|
|
712
|
+
if (nestedStructs.length > 0) {
|
|
713
|
+
for (const nested of nestedStructs) {
|
|
714
|
+
this.interfaceFieldTypes.set(nested.structName, new Map(nested.fields.map((field) => [field.name, field.type])));
|
|
715
|
+
}
|
|
716
|
+
const nestedDefs = nestedStructs.map((ns) => `struct ${ns.structName} { ${ns.fields.map((f) => `${f.type} ${f.name};`).join(" ")} };`);
|
|
717
|
+
this.expressionRenderer.pushPrelude(nestedDefs);
|
|
718
|
+
}
|
|
719
|
+
this.interfaceFieldTypes.set(structName, fieldTypes);
|
|
720
|
+
const fieldDefs = statement.initializer.fields
|
|
721
|
+
.map((f) => `${fieldTypes.get(f.name)} ${f.name};`)
|
|
722
|
+
.join(" ");
|
|
723
|
+
return forHeader
|
|
724
|
+
? `struct ${structName} { ${fieldDefs} } ${safeObjName} = { ${initValues} }`
|
|
725
|
+
: `struct ${structName} { ${fieldDefs} } ${safeObjName} = { ${initValues} };`;
|
|
726
|
+
}
|
|
727
|
+
// Handle spread array initializers
|
|
728
|
+
if (statement.initializer.kind === "spread_array") {
|
|
729
|
+
let arrayType = statement.initializer.elementType === "auto" ? this.strategy.defaultNumericType() : statement.initializer.elementType;
|
|
730
|
+
const spreadName = this.expressionRenderer.render(statement.initializer.spreadExpr, calleeTransformer, knownVariableTypes);
|
|
731
|
+
if (statement.initializer.elementType === "auto" && knownVariableTypes && statement.initializer.spreadExpr.kind === "identifier") {
|
|
732
|
+
const srcInfo = knownVariableTypes.get(statement.initializer.spreadExpr.value);
|
|
733
|
+
if (srcInfo) {
|
|
734
|
+
const vectorElem = parsedElementString(srcInfo.cppType);
|
|
735
|
+
if (vectorElem) {
|
|
736
|
+
arrayType = vectorElem;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const renderedExtraElements = statement.initializer.additionalElements
|
|
741
|
+
.map(e => this.expressionRenderer.render(e, calleeTransformer, knownVariableTypes));
|
|
742
|
+
const safeSpreadArrName = escapeCppKeyword(statement.name, this.strategy.reservedNames());
|
|
743
|
+
if (this.strategy.needsStdVector()) {
|
|
744
|
+
const parts = [`std::vector<${arrayType}> ${safeSpreadArrName}(${spreadName})`];
|
|
745
|
+
for (const elem of renderedExtraElements) {
|
|
746
|
+
parts.push(`${safeSpreadArrName}.push_back(${elem})`);
|
|
747
|
+
}
|
|
748
|
+
return forHeader
|
|
749
|
+
? parts[0]
|
|
750
|
+
: parts.join("; ") + ";";
|
|
751
|
+
}
|
|
752
|
+
const initializerParts = [`/* spread from ${spreadName} */`, ...renderedExtraElements];
|
|
753
|
+
const initializerText = initializerParts.join(', ');
|
|
754
|
+
return forHeader
|
|
755
|
+
? `${arrayType} ${safeSpreadArrName}[] = { ${initializerText} }`
|
|
756
|
+
: `${arrayType} ${safeSpreadArrName}[] = { ${initializerText} };`;
|
|
757
|
+
}
|
|
758
|
+
// Enum ↔ integral storage boundary (SUPPORT_MATRIX §1.7). TS lets an
|
|
759
|
+
// enum and a number flow into each other freely (enums ARE numbers at
|
|
760
|
+
// runtime), but the lowered C++ `enum class` has NO implicit conversion
|
|
761
|
+
// in EITHER direction: `const n: int = Color.Red` AND
|
|
762
|
+
// `const c: Color = intCell` both fail. Route the initializer through
|
|
763
|
+
// the shared target-type-aware helper so both directions lower with the
|
|
764
|
+
// right `static_cast`, replacing the prior point-specific enum→int-only
|
|
765
|
+
// inline check. Demo #32 Finding A.
|
|
766
|
+
const finalInit = this.expressionRenderer.renderValueForTarget(statement.initializer, declaredType, knownVariableTypes, calleeTransformer);
|
|
767
|
+
return forHeader
|
|
768
|
+
? `${declaration} = ${finalInit}`
|
|
769
|
+
: `${declaration} = ${finalInit};`;
|
|
770
|
+
}
|
|
771
|
+
return forHeader ? declaration : `${declaration};`;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Renders a typed name with proper C++ syntax.
|
|
775
|
+
*/
|
|
776
|
+
renderTypedName(cppType, name, isConst = false, isRef = false) {
|
|
777
|
+
const safeName = escapeCppKeyword(name, this.strategy.reservedNames());
|
|
778
|
+
const normalizedType = this.normalizeCppType(cppType);
|
|
779
|
+
const fnPtrMatch = normalizedType.match(/^(.+?)\s*\(\*\)\((.*)\)$/);
|
|
780
|
+
if (fnPtrMatch) {
|
|
781
|
+
const returnType = fnPtrMatch[1].trim();
|
|
782
|
+
const params = fnPtrMatch[2].trim();
|
|
783
|
+
const constPrefix = isConst ? "const " : "";
|
|
784
|
+
return `${constPrefix}${returnType} (*${safeName})(${params})`;
|
|
785
|
+
}
|
|
786
|
+
const alreadyConstQualified = /^const\s+/.test(normalizedType);
|
|
787
|
+
const arrayMatch = normalizedType.match(/^(.+?)\s*\[(\d*)\]$/);
|
|
788
|
+
if (arrayMatch) {
|
|
789
|
+
const baseType = arrayMatch[1].trim();
|
|
790
|
+
const size = arrayMatch[2];
|
|
791
|
+
const constPrefix = isConst && !alreadyConstQualified ? "const " : "";
|
|
792
|
+
return `${constPrefix}${baseType} ${safeName}[${size}]`;
|
|
793
|
+
}
|
|
794
|
+
if (isConst && !alreadyConstQualified && parsedIsPointer(normalizedType)) {
|
|
795
|
+
return `${normalizedType} ${safeName}`;
|
|
796
|
+
}
|
|
797
|
+
const constPrefix = isConst && !alreadyConstQualified ? "const " : "";
|
|
798
|
+
const refMark = isRef ? "& " : " ";
|
|
799
|
+
return `${constPrefix}${normalizedType}${refMark}${safeName}`;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Renders function parameters.
|
|
803
|
+
* Emits `const` for parameters annotated with `Shared<T>` (ownershipKind === 'shared').
|
|
804
|
+
*/
|
|
805
|
+
renderParameters(parameters, forHeader = false) {
|
|
806
|
+
if (parameters.length === 0) {
|
|
807
|
+
return "";
|
|
808
|
+
}
|
|
809
|
+
return parameters
|
|
810
|
+
.map((parameter) => {
|
|
811
|
+
const paramOwnershipKind = parameter.ownershipKind;
|
|
812
|
+
const hasOwnership = paramOwnershipKind === 'shared' || paramOwnershipKind === 'mutable';
|
|
813
|
+
const isNonPrimitiveNonPointer = !isPrimitiveCppType(parameter.cppType)
|
|
814
|
+
&& !isIndirectType(parameter.cppType, this.strategy);
|
|
815
|
+
const paramTypeName = parameter.cppType.trim();
|
|
816
|
+
// User-defined struct/interface types (not std:: containers, not enums)
|
|
817
|
+
// default to pass-by-const-reference to avoid expensive copies and surface
|
|
818
|
+
// accidental local mutations as errors. Shared<T>/Mutable<T> keep borrowing
|
|
819
|
+
// any non-primitive, non-pointer type (including std:: containers).
|
|
820
|
+
const isUserStructType = isNonPrimitiveNonPointer
|
|
821
|
+
&& !parsedIsStdContainer(paramTypeName)
|
|
822
|
+
&& !this.enumNames.has(paramTypeName);
|
|
823
|
+
const isRef = (hasOwnership && isNonPrimitiveNonPointer) || isUserStructType;
|
|
824
|
+
// Refs default to a const borrow; Mutable<T> unlocks a mutable reference.
|
|
825
|
+
// Primitives only gain const when explicitly annotated with Shared<T>.
|
|
826
|
+
const isConst = isRef
|
|
827
|
+
? (!hasOwnership || paramOwnershipKind === 'shared')
|
|
828
|
+
: paramOwnershipKind === 'shared';
|
|
829
|
+
let cppType = parameter.cppType;
|
|
830
|
+
if (parameter.isRest) {
|
|
831
|
+
const elementType = cppType.replace(/^std::vector<(.+)>$/, '$1') || 'auto';
|
|
832
|
+
cppType = `const std::vector<${elementType}>&`;
|
|
833
|
+
const safeName = escapeCppKeyword(parameter.name, this.strategy.reservedNames());
|
|
834
|
+
let result = `${cppType} ${safeName}`;
|
|
835
|
+
if (forHeader && parameter.defaultValue) {
|
|
836
|
+
result += ` = ${this.expressionRenderer.render(parameter.defaultValue)}`;
|
|
837
|
+
}
|
|
838
|
+
return result;
|
|
839
|
+
}
|
|
840
|
+
let result = this.renderTypedName(cppType, parameter.name, isConst, isRef);
|
|
841
|
+
if (forHeader && parameter.defaultValue) {
|
|
842
|
+
result += ` = ${this.expressionRenderer.render(parameter.defaultValue)}`;
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
})
|
|
846
|
+
.join(", ");
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Public type-mapping entry point for emitters that emit types directly
|
|
850
|
+
* (function return types, forward declarations) rather than via
|
|
851
|
+
* renderTypedName. Applies the same string-enum → const char* substitution.
|
|
852
|
+
*/
|
|
853
|
+
mapTypeForEmit(typeName) {
|
|
854
|
+
return this.normalizeCppType(typeName);
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Map a function's return type for emission. Unlike {@link mapTypeForEmit},
|
|
858
|
+
* this respects the strategy's {@code mapReturnType} contract: the value
|
|
859
|
+
* produced by {@code mapReturnType} is already final and must NOT be
|
|
860
|
+
* re-normalised. This matters for entrypoints like {@code main}, whose
|
|
861
|
+
* mapped {@code int} return type would otherwise be turned back into
|
|
862
|
+
* {@code long long} by {@code normalizeCppType}.
|
|
863
|
+
*
|
|
864
|
+
* For non-entrypoint functions we still apply the string-enum → const char*
|
|
865
|
+
* substitution (which {@code mapReturnType} does not perform), preserving
|
|
866
|
+
* existing behaviour for ordinary functions.
|
|
867
|
+
*/
|
|
868
|
+
mapReturnTypeForEmit(fnName, returnType) {
|
|
869
|
+
if (fnName === this.strategy.entrypointFunctionName()) {
|
|
870
|
+
return returnType;
|
|
871
|
+
}
|
|
872
|
+
return this.mapTypeForEmit(returnType);
|
|
873
|
+
}
|
|
874
|
+
normalizeCppType(typeName) {
|
|
875
|
+
// A TS string-enum type name (e.g. `Color`) is lowered to a namespace of
|
|
876
|
+
// constexpr const char* constants, so values of that type are const char*.
|
|
877
|
+
// Substitute the type so declarations like `const Color c` become
|
|
878
|
+
// `const const char* c` (valid: const pointer to const char).
|
|
879
|
+
if (this.stringEnumNames.has(typeName)) {
|
|
880
|
+
return this.strategy.normalizeCppType("const char*");
|
|
881
|
+
}
|
|
882
|
+
return this.strategy.normalizeCppType(typeName);
|
|
883
|
+
}
|
|
884
|
+
inferFieldType(value, parentName, fieldName) {
|
|
885
|
+
return inferObjectFieldType(value, this.pointerVarTypes, this.knownFunctionReturnTypes, undefined, undefined, undefined, // largeEnumNames - would need to pass through
|
|
886
|
+
parentName, fieldName, this.strategy.defaultNumericType(), (o, n) => this.strategy.resolvePinType?.(o, n));
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Transform console.log/error/warn calls based on target platform.
|
|
890
|
+
*/
|
|
891
|
+
transformConsoleCall(callee, args, forHeader, knownVariableTypes) {
|
|
892
|
+
const method = getConsoleMethod(callee);
|
|
893
|
+
const renderedArgs = args.map((arg) => {
|
|
894
|
+
let rendered = this.expressionRenderer.render(arg, undefined, knownVariableTypes);
|
|
895
|
+
if (arg.kind === "identifier" && knownVariableTypes) {
|
|
896
|
+
const varInfo = knownVariableTypes.get(arg.value);
|
|
897
|
+
if (varInfo && this.enumNames.has(varInfo.cppType)) {
|
|
898
|
+
rendered = `static_cast<int>(${rendered})`;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (arg.kind === "property-access" && arg.isEnum) {
|
|
902
|
+
rendered = `static_cast<int>(${rendered})`;
|
|
903
|
+
}
|
|
904
|
+
return rendered;
|
|
905
|
+
}).join(" << ");
|
|
906
|
+
return this.strategy.transformConsoleCall(method, renderedArgs, forHeader);
|
|
907
|
+
}
|
|
908
|
+
renderEmitArg(arg) {
|
|
909
|
+
if (arg.kind === "string")
|
|
910
|
+
return arg.value;
|
|
911
|
+
if (arg.kind === "string_concat")
|
|
912
|
+
return arg.parts.map(p => this.renderEmitArg(p)).join("");
|
|
913
|
+
// For template_string inside emit(), the inner expression is a
|
|
914
|
+
// string_concat of literal parts and interpolated identifiers. We render
|
|
915
|
+
// each part directly as C++ text — string literals contribute their raw
|
|
916
|
+
// text, identifiers contribute their variable name — bypassing the
|
|
917
|
+
// snprintf machinery that normal string rendering would use.
|
|
918
|
+
if (arg.kind === "template_string")
|
|
919
|
+
return this.renderEmitArg(arg.expression);
|
|
920
|
+
return this.expressionRenderer.render(arg);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Map a function name to its platform-specific name.
|
|
924
|
+
*
|
|
925
|
+
* The entrypoint sentinel `__cuttlefish_entrypoint__` maps to the strategy's
|
|
926
|
+
* entrypoint name (`main` on native, `setup` on Arduino). For every OTHER
|
|
927
|
+
* name we delegate to `strategy.mapFunctionName`, which is where a target
|
|
928
|
+
* can rename a user function that would otherwise collide with a C++/
|
|
929
|
+
* framework reserved name — e.g. the Arduino strategy renames a user
|
|
930
|
+
* `function main()` to `cuttlefish_main`, because Arduino has no `main()`
|
|
931
|
+
* (the entrypoints are the auto-generated `setup()`/`loop()`), and a
|
|
932
|
+
* file-scope `static void main()` collides with C++'s required `int main()`
|
|
933
|
+
* signature. Routing every name through the strategy here is the single
|
|
934
|
+
* place that makes such renames actually take effect at emit time.
|
|
935
|
+
*/
|
|
936
|
+
mapFunctionName(originalName) {
|
|
937
|
+
if (originalName === "__cuttlefish_entrypoint__") {
|
|
938
|
+
return this.strategy.entrypointFunctionName();
|
|
939
|
+
}
|
|
940
|
+
return this.strategy.mapFunctionName(originalName);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Map a TypeScript return type to its C++ equivalent for a specific function.
|
|
944
|
+
*/
|
|
945
|
+
mapReturnType(fnName, tsType) {
|
|
946
|
+
return this.strategy.mapReturnType(fnName, tsType);
|
|
947
|
+
}
|
|
948
|
+
}
|