@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,735 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { requiredIncludes, registeredCallbacks, activeStringVars, TYPED_ARRAY_ELEMENT_MAP, getContext, floatVariables, halInstances, getCurrentBoardConstants } from "../build-ir-state.js";
|
|
3
|
+
import { getCurrentIrTypeScope } from "../symbol-types.js";
|
|
4
|
+
import { renderExprAsText } from "../render-expr.js";
|
|
5
|
+
import { escapeCppKeyword } from "../../utils/strings.js";
|
|
6
|
+
import { halClassRegistry, halGlobalFunctions } from "./hal-parser.js";
|
|
7
|
+
import { tryResolveSemanticCall, tryResolveBoardResolveArg, tryResolveCompoundSemanticReturn, resolveConcatPath } from "./hal-plugins.js";
|
|
8
|
+
/** Escape C++ keywords in resolved text, but only when the text looks like a
|
|
9
|
+
* variable reference (not a literal like "false", "true", "42", or a string). */
|
|
10
|
+
export function maybeEscapeResolvedText(text) {
|
|
11
|
+
// Skip escaping for boolean literals, numeric literals, and string literals
|
|
12
|
+
if (text === "true" || text === "false" || text === "null" || text === "undefined")
|
|
13
|
+
return text;
|
|
14
|
+
if (/^-?\d+(\.\d+)?$/.test(text))
|
|
15
|
+
return text;
|
|
16
|
+
if (text.startsWith('"') || text.startsWith("'"))
|
|
17
|
+
return text;
|
|
18
|
+
return escapeCppKeyword(text);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Strip a leading `.c_str()` from the literal text that follows a template span
|
|
22
|
+
* when the resolved value is a C++ string literal (e.g. `"label"`).
|
|
23
|
+
*
|
|
24
|
+
* HAL authors write `${param}.c_str()` to convert a `String`/`std::string`
|
|
25
|
+
* variable to `const char*` for C APIs. That is correct when `param` is a
|
|
26
|
+
* variable (renders as `myVar` → `myVar.c_str()`). But when `param` is a string
|
|
27
|
+
* literal it renders as `"label"`, and `"label".c_str()` is invalid C++ — a
|
|
28
|
+
* string literal has no `.c_str()` member. This drops the conversion in that
|
|
29
|
+
* case so `"label".c_str()` becomes just `"label"`, while leaving the variable
|
|
30
|
+
* path untouched.
|
|
31
|
+
*/
|
|
32
|
+
export function stripCStrAfterStringLiteral(followingLiteralText, resolvedValue) {
|
|
33
|
+
if (resolvedValue.startsWith('"') && followingLiteralText.startsWith(".c_str()")) {
|
|
34
|
+
return followingLiteralText.slice(".c_str()".length);
|
|
35
|
+
}
|
|
36
|
+
return followingLiteralText;
|
|
37
|
+
}
|
|
38
|
+
/** Map a TypeScript PrefixUnaryExpression operator SyntaxKind to C++ text. */
|
|
39
|
+
function prefixOperatorText(operator) {
|
|
40
|
+
switch (operator) {
|
|
41
|
+
case ts.SyntaxKind.ExclamationToken: return "!";
|
|
42
|
+
case ts.SyntaxKind.PlusToken: return "+";
|
|
43
|
+
case ts.SyntaxKind.MinusToken: return "-";
|
|
44
|
+
case ts.SyntaxKind.TildeToken: return "~";
|
|
45
|
+
case ts.SyntaxKind.PlusPlusToken: return "++";
|
|
46
|
+
case ts.SyntaxKind.MinusMinusToken: return "--";
|
|
47
|
+
default: return "";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Inline a `this.<method>()` getter call used inside a compound expression
|
|
52
|
+
* (e.g. `!this.read()` in InputPin.isLow()). The InputPin boolean-query
|
|
53
|
+
* methods all reduce to a semantic read over `this._pin`; mapping them here
|
|
54
|
+
* keeps `this` out of the emitted free function. Returns null if the method
|
|
55
|
+
* is not a recognized inlinable getter.
|
|
56
|
+
*/
|
|
57
|
+
function inlineThisGetterCall(methodName, pin) {
|
|
58
|
+
switch (methodName) {
|
|
59
|
+
case "read":
|
|
60
|
+
return `digitalRead(${pin})`;
|
|
61
|
+
case "readAnalog":
|
|
62
|
+
return `analogRead(${pin})`;
|
|
63
|
+
case "isHigh":
|
|
64
|
+
return `digitalRead(${pin})`;
|
|
65
|
+
case "isLow":
|
|
66
|
+
return `(!digitalRead(${pin}))`;
|
|
67
|
+
default:
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Resolve an arbitrary expression to its text form, with this/param substitution. */
|
|
72
|
+
export function resolveExpressionText(expr, instance, paramNames, callArgTexts, paramDefaults) {
|
|
73
|
+
// this._field → look up in instance
|
|
74
|
+
// OtherInstance._field → look up in tracked halInstances (cross-instance reference)
|
|
75
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
76
|
+
const isThis = expr.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
77
|
+
|| (ts.isIdentifier(expr.expression) && expr.expression.text === "this")
|
|
78
|
+
|| expr.expression.getText() === "this";
|
|
79
|
+
if (isThis) {
|
|
80
|
+
const fieldName = expr.name.text;
|
|
81
|
+
const val = instance.fieldValues.get(fieldName) ?? instance.fieldValues.get(fieldName.startsWith("_") ? fieldName.slice(1) : "_" + fieldName);
|
|
82
|
+
if (val !== undefined && val !== null)
|
|
83
|
+
return val;
|
|
84
|
+
// Fallback: try to see if it's a known field that should be mapped
|
|
85
|
+
if (fieldName === "_pin" && instance.fieldValues.has("pin"))
|
|
86
|
+
return instance.fieldValues.get("pin");
|
|
87
|
+
if (fieldName === "_bus" && instance.fieldValues.has("bus"))
|
|
88
|
+
return instance.fieldValues.get("bus");
|
|
89
|
+
return `this->${fieldName}`;
|
|
90
|
+
}
|
|
91
|
+
// Cross-instance field reference: e.g. ADC._reference → look up tracked instance
|
|
92
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
93
|
+
let crossInst = halInstances.get(expr.expression.text);
|
|
94
|
+
// Fall back to bare-name defaults if not yet cached
|
|
95
|
+
if (!crossInst && expr.expression.text === "ADC") {
|
|
96
|
+
crossInst = { className: "ADCClass", fieldValues: new Map([["_reference", "DEFAULT"]]) };
|
|
97
|
+
}
|
|
98
|
+
if (crossInst) {
|
|
99
|
+
const val = crossInst.fieldValues.get(expr.name.text);
|
|
100
|
+
if (val !== undefined)
|
|
101
|
+
return val;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const obj = resolveExpressionText(expr.expression, instance, paramNames, callArgTexts, paramDefaults);
|
|
105
|
+
if (obj === null)
|
|
106
|
+
return null;
|
|
107
|
+
return `${obj}.${expr.name.text}`;
|
|
108
|
+
}
|
|
109
|
+
// Identifier → parameter or constant
|
|
110
|
+
if (ts.isIdentifier(expr)) {
|
|
111
|
+
const paramIdx = paramNames.indexOf(expr.text);
|
|
112
|
+
if (paramIdx !== -1) {
|
|
113
|
+
if (paramIdx < callArgTexts.length) {
|
|
114
|
+
const isSpread = expr.text === instance._spreadParamName;
|
|
115
|
+
if (isSpread) {
|
|
116
|
+
const spreadArgs = callArgTexts.slice(paramIdx);
|
|
117
|
+
return spreadArgs.join(", ");
|
|
118
|
+
}
|
|
119
|
+
return maybeEscapeResolvedText(callArgTexts[paramIdx]);
|
|
120
|
+
}
|
|
121
|
+
else if (paramDefaults?.has(expr.text)) {
|
|
122
|
+
return maybeEscapeResolvedText(paramDefaults.get(expr.text));
|
|
123
|
+
}
|
|
124
|
+
// Optional parameter that was omitted at the call site and has no
|
|
125
|
+
// default: its runtime value is `undefined`. Returning the literal name
|
|
126
|
+
// would leak a dangling identifier into the emitted C++ (e.g.
|
|
127
|
+
// `asOutput(initial?: ...)` referenced as `(initial) ? HIGH : LOW`).
|
|
128
|
+
return "undefined";
|
|
129
|
+
}
|
|
130
|
+
return escapeCppKeyword(expr.text);
|
|
131
|
+
}
|
|
132
|
+
if (ts.isNumericLiteral(expr))
|
|
133
|
+
return expr.text;
|
|
134
|
+
if (ts.isStringLiteral(expr))
|
|
135
|
+
return expr.text;
|
|
136
|
+
// Call expression (e.g., digitalRead(this._pin), board("path"))
|
|
137
|
+
if (ts.isCallExpression(expr)) {
|
|
138
|
+
// callback(param) → return the patched placeholder text
|
|
139
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "callback") {
|
|
140
|
+
const cbArg = expr.arguments[0];
|
|
141
|
+
if (cbArg && ts.isIdentifier(cbArg)) {
|
|
142
|
+
const paramIdx = paramNames.indexOf(cbArg.text);
|
|
143
|
+
if (paramIdx !== -1 && paramIdx < callArgTexts.length) {
|
|
144
|
+
return callArgTexts[paramIdx];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
// String(x) → return resolved text of x (identity in compile-time string context)
|
|
150
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "String") {
|
|
151
|
+
const inner = expr.arguments[0];
|
|
152
|
+
if (inner) {
|
|
153
|
+
return resolveExpressionText(inner, instance, paramNames, callArgTexts, paramDefaults);
|
|
154
|
+
}
|
|
155
|
+
return "";
|
|
156
|
+
}
|
|
157
|
+
// board("path") → look up in current board constants
|
|
158
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "board") {
|
|
159
|
+
const pathArg = expr.arguments[0];
|
|
160
|
+
// Static string literal path: board("peripherals.adc.0.resolution")
|
|
161
|
+
if (pathArg && ts.isStringLiteral(pathArg)) {
|
|
162
|
+
const bc = getCurrentBoardConstants();
|
|
163
|
+
if (bc) {
|
|
164
|
+
const val = bc.get(pathArg.text);
|
|
165
|
+
if (val !== undefined)
|
|
166
|
+
return String(val);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Dynamic path via string concat or template literal:
|
|
170
|
+
// board("prefix." + this._field) / board(`prefix.${this._field}`)
|
|
171
|
+
if (pathArg && (ts.isBinaryExpression(pathArg) || ts.isTemplateExpression(pathArg) || ts.isStringLiteral(pathArg) || ts.isNoSubstitutionTemplateLiteral(pathArg))) {
|
|
172
|
+
const fullPath = resolveConcatPath(pathArg, instance, paramNames, callArgTexts, paramDefaults);
|
|
173
|
+
if (fullPath !== null) {
|
|
174
|
+
const bc = getCurrentBoardConstants();
|
|
175
|
+
if (bc) {
|
|
176
|
+
const val = bc.get(fullPath);
|
|
177
|
+
if (val !== undefined)
|
|
178
|
+
return String(val);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const callee = resolveExpressionText(expr.expression, instance, paramNames, callArgTexts, paramDefaults);
|
|
185
|
+
if (callee === null)
|
|
186
|
+
return null;
|
|
187
|
+
const args = expr.arguments.map(arg => resolveExpressionText(arg, instance, paramNames, callArgTexts, paramDefaults));
|
|
188
|
+
if (args.some(a => a === null))
|
|
189
|
+
return null;
|
|
190
|
+
// this.<method>() — a HAL method called on the same instance inside a
|
|
191
|
+
// compound expression (e.g. InputPin.isLow() returns `!this.read()`).
|
|
192
|
+
// Inline the known InputPin boolean-query getters over the resolved pin
|
|
193
|
+
// so `this` never leaks into the emitted free function.
|
|
194
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
195
|
+
const isThisAccess = expr.expression.expression.kind === ts.SyntaxKind.ThisKeyword
|
|
196
|
+
|| (ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "this")
|
|
197
|
+
|| expr.expression.expression.getText() === "this";
|
|
198
|
+
if (isThisAccess) {
|
|
199
|
+
const methodName = expr.expression.name.text;
|
|
200
|
+
const pin = instance.fieldValues.get("_pin") ?? instance.fieldValues.get("pin");
|
|
201
|
+
if (pin !== undefined) {
|
|
202
|
+
const inlined = inlineThisGetterCall(methodName, pin);
|
|
203
|
+
if (inlined !== null)
|
|
204
|
+
return inlined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return `${callee}(${args.join(", ")})`;
|
|
209
|
+
}
|
|
210
|
+
// Binary expression
|
|
211
|
+
if (ts.isBinaryExpression(expr)) {
|
|
212
|
+
const left = resolveExpressionText(expr.left, instance, paramNames, callArgTexts, paramDefaults);
|
|
213
|
+
const right = resolveExpressionText(expr.right, instance, paramNames, callArgTexts, paramDefaults);
|
|
214
|
+
if (left === null || right === null)
|
|
215
|
+
return null;
|
|
216
|
+
let op = expr.operatorToken.getText();
|
|
217
|
+
if (op === "===")
|
|
218
|
+
op = "==";
|
|
219
|
+
else if (op === "!==")
|
|
220
|
+
op = "!=";
|
|
221
|
+
else if (op === "??") {
|
|
222
|
+
// If the left side is a literal expression (true/false/number/string),
|
|
223
|
+
// it can never be undefined so use it directly
|
|
224
|
+
if (expr.left.kind === ts.SyntaxKind.TrueKeyword || expr.left.kind === ts.SyntaxKind.FalseKeyword ||
|
|
225
|
+
ts.isNumericLiteral(expr.left) || ts.isStringLiteral(expr.left)) {
|
|
226
|
+
return left;
|
|
227
|
+
}
|
|
228
|
+
// If the left side is an identifier whose resolved text matches its source,
|
|
229
|
+
// it means the parameter was not provided by the caller, so use the right side
|
|
230
|
+
if (ts.isIdentifier(expr.left) && left === expr.left.text) {
|
|
231
|
+
return right;
|
|
232
|
+
}
|
|
233
|
+
// If the left side resolved to a recognized literal (true, false, or number),
|
|
234
|
+
// the parameter was provided with a concrete value, not undefined.
|
|
235
|
+
if (left === "true" || left === "false" || /^-?\d+(\.\d+)?$/.test(left)) {
|
|
236
|
+
return left;
|
|
237
|
+
}
|
|
238
|
+
// Use a more concise ternary for C++
|
|
239
|
+
return `(${left} != CUTTLEFISH_UNDEFINED ? ${left} : ${right})`;
|
|
240
|
+
}
|
|
241
|
+
return `${left} ${op} ${right}`;
|
|
242
|
+
}
|
|
243
|
+
// Parenthesized expression: (expr) → unwrap to inner expression
|
|
244
|
+
if (ts.isParenthesizedExpression(expr)) {
|
|
245
|
+
return resolveExpressionText(expr.expression, instance, paramNames, callArgTexts, paramDefaults);
|
|
246
|
+
}
|
|
247
|
+
// Type assertion: this as any → unwrap to inner expression
|
|
248
|
+
if (ts.isAsExpression(expr)) {
|
|
249
|
+
return resolveExpressionText(expr.expression, instance, paramNames, callArgTexts, paramDefaults);
|
|
250
|
+
}
|
|
251
|
+
// Template expression: `text ${expr} more text`
|
|
252
|
+
if (ts.isTemplateExpression(expr)) {
|
|
253
|
+
let result = expr.head.text;
|
|
254
|
+
for (const span of expr.templateSpans) {
|
|
255
|
+
const resolved = resolveExpressionText(span.expression, instance, paramNames, callArgTexts, paramDefaults);
|
|
256
|
+
if (resolved === null)
|
|
257
|
+
return null;
|
|
258
|
+
result += resolved + stripCStrAfterStringLiteral(span.literal.text, resolved);
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
// No-substitution template literal: `text`
|
|
263
|
+
if (ts.isNoSubstitutionTemplateLiteral(expr)) {
|
|
264
|
+
return expr.text;
|
|
265
|
+
}
|
|
266
|
+
// Prefix unary expression (e.g. `!this.read()`): resolve the operand and
|
|
267
|
+
// re-apply the operator. Without this, `!this.read()` falls through to the
|
|
268
|
+
// raw getText() fallback and leaks `this` into a free function.
|
|
269
|
+
if (ts.isPrefixUnaryExpression(expr)) {
|
|
270
|
+
const operand = resolveExpressionText(expr.operand, instance, paramNames, callArgTexts, paramDefaults);
|
|
271
|
+
if (operand === null)
|
|
272
|
+
return null;
|
|
273
|
+
const op = prefixOperatorText(expr.operator);
|
|
274
|
+
return `${op}${operand}`;
|
|
275
|
+
}
|
|
276
|
+
return expr.getText ? expr.getText() : null;
|
|
277
|
+
}
|
|
278
|
+
/** Scan an expression AST for callback() calls, extract callback IR from callArgs,
|
|
279
|
+
* register them, and patch callArgTexts with placeholder names. */
|
|
280
|
+
export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgTexts) {
|
|
281
|
+
function scan(node) {
|
|
282
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "callback") {
|
|
283
|
+
const cbArg = node.arguments[0];
|
|
284
|
+
if (cbArg && ts.isIdentifier(cbArg)) {
|
|
285
|
+
const paramIdx = paramNames.indexOf(cbArg.text);
|
|
286
|
+
if (paramIdx !== -1 && paramIdx < callArgs.length) {
|
|
287
|
+
const callbackIR = callArgs[paramIdx];
|
|
288
|
+
if (callbackIR.kind === "callback" || callbackIR.kind === "lambda") {
|
|
289
|
+
const placeholder = `__CALLBACK_${getContext().callbackPlaceholderCounter++}__`;
|
|
290
|
+
const normalized = callbackIR.kind === "lambda"
|
|
291
|
+
? {
|
|
292
|
+
kind: "callback",
|
|
293
|
+
params: callbackIR.params.map((p) => p.name),
|
|
294
|
+
statements: callbackIR.body,
|
|
295
|
+
sourceSpan: callbackIR.body[0]?.sourceSpan ?? {
|
|
296
|
+
filePath: "",
|
|
297
|
+
startLine: 0,
|
|
298
|
+
startColumn: 0,
|
|
299
|
+
endLine: 0,
|
|
300
|
+
endColumn: 0,
|
|
301
|
+
startOffset: 0,
|
|
302
|
+
endOffset: 0,
|
|
303
|
+
},
|
|
304
|
+
isInterruptHandler: true,
|
|
305
|
+
}
|
|
306
|
+
: callbackIR;
|
|
307
|
+
normalized.isInterruptHandler = true;
|
|
308
|
+
registeredCallbacks.push({ placeholderName: placeholder, callbackIR: normalized });
|
|
309
|
+
callArgTexts[paramIdx] = placeholder;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (ts.isTemplateExpression(node)) {
|
|
315
|
+
for (const span of node.templateSpans) {
|
|
316
|
+
scan(span.expression);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
scan(expr);
|
|
321
|
+
}
|
|
322
|
+
/** Process a HAL method body, resolving emit()/include()/semantic calls.
|
|
323
|
+
* Returns { emitLines, halOps, returnValue, returnClassName } or null if unresolvable.
|
|
324
|
+
*
|
|
325
|
+
* `emitLines` contains legacy raw C++ strings (from `emit()` calls).
|
|
326
|
+
* `halOps` contains structured HALOpIR nodes (from semantic function calls like
|
|
327
|
+
* `gpioWrite()`, `i2cBegin()`, etc.).
|
|
328
|
+
*
|
|
329
|
+
* During migration both can coexist; consumers should prefer `halOps` when present. */
|
|
330
|
+
export function processHALMethodBody(instance, methodName, callArgs) {
|
|
331
|
+
let methodEntry;
|
|
332
|
+
if (instance.className) {
|
|
333
|
+
const classEntry = halClassRegistry.get(instance.className);
|
|
334
|
+
if (!classEntry)
|
|
335
|
+
return null;
|
|
336
|
+
methodEntry = classEntry.methods.get(methodName);
|
|
337
|
+
// Fallback: search other HAL classes for the method (e.g., Pin instance calling InputPin.onFalling)
|
|
338
|
+
if (!methodEntry) {
|
|
339
|
+
for (const [, entry] of halClassRegistry) {
|
|
340
|
+
const found = entry.methods.get(methodName);
|
|
341
|
+
if (found && found.methodNode.body) {
|
|
342
|
+
methodEntry = found;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
methodEntry = halGlobalFunctions.get(methodName);
|
|
350
|
+
}
|
|
351
|
+
if (!methodEntry || !methodEntry.methodNode.body)
|
|
352
|
+
return null;
|
|
353
|
+
const paramNames = methodEntry.paramNames;
|
|
354
|
+
const spreadParamName = methodEntry.spreadParamName;
|
|
355
|
+
const callArgTexts = callArgs.map(a => renderExprAsText(a));
|
|
356
|
+
instance._spreadParamName = spreadParamName;
|
|
357
|
+
const paramDefaults = methodEntry.paramDefaults;
|
|
358
|
+
const body = methodEntry.methodNode.body;
|
|
359
|
+
const emitLines = [];
|
|
360
|
+
const halOps = [];
|
|
361
|
+
// For string_concat args (template literals), generate snprintf instead of
|
|
362
|
+
// C++ + concatenation which is invalid for char* on Arduino.
|
|
363
|
+
for (let i = 0; i < callArgs.length; i++) {
|
|
364
|
+
const arg = callArgs[i];
|
|
365
|
+
if (arg.kind === "string_concat" && arg.parts.some(p => p.kind !== "string")) {
|
|
366
|
+
const snprintf = buildSnprintfFromConcat(arg);
|
|
367
|
+
if (snprintf) {
|
|
368
|
+
emitLines.push(...snprintf.lines);
|
|
369
|
+
callArgTexts[i] = snprintf.bufferName;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
let returnValue;
|
|
374
|
+
for (const stmt of body.statements) {
|
|
375
|
+
// this._field = param — track field updates on the instance
|
|
376
|
+
if (ts.isExpressionStatement(stmt) &&
|
|
377
|
+
ts.isBinaryExpression(stmt.expression) &&
|
|
378
|
+
stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
379
|
+
const left = stmt.expression.left;
|
|
380
|
+
const right = stmt.expression.right;
|
|
381
|
+
if (ts.isPropertyAccessExpression(left) &&
|
|
382
|
+
(left.expression.kind === ts.SyntaxKind.ThisKeyword ||
|
|
383
|
+
(ts.isIdentifier(left.expression) && left.expression.text === "this"))) {
|
|
384
|
+
const fieldName = left.name.text;
|
|
385
|
+
const resolved = resolveExpressionText(right, instance, paramNames, callArgTexts, paramDefaults);
|
|
386
|
+
if (resolved !== null) {
|
|
387
|
+
instance.fieldValues.set(fieldName, resolved);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// emit(...) call or semantic HAL function call
|
|
392
|
+
if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) {
|
|
393
|
+
const call = stmt.expression;
|
|
394
|
+
// ── Board resolve: pure lookup, no side effects — skip ──
|
|
395
|
+
if (ts.isIdentifier(call.expression) && call.expression.text === "boardResolve") {
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
// ── Semantic HAL function calls (gpioWrite, i2cBegin, etc.) ──
|
|
399
|
+
if (ts.isIdentifier(call.expression)) {
|
|
400
|
+
// Extract callbacks from semantic call arguments and patch callArgTexts
|
|
401
|
+
// with placeholder names before resolving (mirrors the emit() path).
|
|
402
|
+
for (const arg of call.arguments) {
|
|
403
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
404
|
+
}
|
|
405
|
+
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
406
|
+
if (semanticOp) {
|
|
407
|
+
halOps.push(semanticOp);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// include(...) call
|
|
412
|
+
if (ts.isIdentifier(call.expression) && call.expression.text === "include") {
|
|
413
|
+
const firstArg = call.arguments[0];
|
|
414
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
415
|
+
requiredIncludes.add(firstArg.text);
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// R4: if statement with compile-time condition evaluation
|
|
421
|
+
if (ts.isIfStatement(stmt)) {
|
|
422
|
+
let conditionTrue = true; // default: process then-branch
|
|
423
|
+
const cond = stmt.expression;
|
|
424
|
+
if (cond && ts.isBinaryExpression(cond)) {
|
|
425
|
+
const left = resolveExpressionText(cond.left, instance, paramNames, callArgTexts, paramDefaults);
|
|
426
|
+
const right = resolveExpressionText(cond.right, instance, paramNames, callArgTexts, paramDefaults);
|
|
427
|
+
if (left !== null && right !== null) {
|
|
428
|
+
const op = cond.operatorToken.kind;
|
|
429
|
+
if (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsEqualsToken) {
|
|
430
|
+
conditionTrue = left === right;
|
|
431
|
+
}
|
|
432
|
+
else if (op === ts.SyntaxKind.ExclamationEqualsToken || op === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
|
|
433
|
+
conditionTrue = left !== right;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (conditionTrue && stmt.thenStatement) {
|
|
438
|
+
const returnRef = returnValue !== undefined ? undefined : { value: "" };
|
|
439
|
+
processStatementList(ts.isBlock(stmt.thenStatement) ? stmt.thenStatement.statements : [stmt.thenStatement], instance, paramNames, callArgTexts, emitLines, halOps, callArgs, paramDefaults, returnRef);
|
|
440
|
+
if (returnRef && returnRef.value)
|
|
441
|
+
returnValue = returnRef.value;
|
|
442
|
+
}
|
|
443
|
+
if (!conditionTrue && stmt.elseStatement) {
|
|
444
|
+
const returnRef = returnValue !== undefined ? undefined : { value: "" };
|
|
445
|
+
processStatementList(ts.isBlock(stmt.elseStatement) ? stmt.elseStatement.statements : [stmt.elseStatement], instance, paramNames, callArgTexts, emitLines, halOps, callArgs, paramDefaults, returnRef);
|
|
446
|
+
if (returnRef && returnRef.value)
|
|
447
|
+
returnValue = returnRef.value;
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
// R2: return expr — check for semantic call first, then fall back to resolveExpressionText
|
|
452
|
+
if (ts.isReturnStatement(stmt) && stmt.expression && returnValue === undefined) {
|
|
453
|
+
const retExpr = stmt.expression;
|
|
454
|
+
// ── Board resolve: constant-fold via board constants ──
|
|
455
|
+
if (ts.isCallExpression(retExpr) && ts.isIdentifier(retExpr.expression) && retExpr.expression.text === "boardResolve") {
|
|
456
|
+
const resolved = tryResolveBoardResolveArg(retExpr.arguments, instance, paramNames, callArgTexts, paramDefaults);
|
|
457
|
+
if (resolved !== null) {
|
|
458
|
+
const bc = getCurrentBoardConstants();
|
|
459
|
+
const val = bc?.get(resolved);
|
|
460
|
+
if (val !== undefined) {
|
|
461
|
+
returnValue = String(val);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
let actualRetExpr = retExpr;
|
|
467
|
+
while (ts.isAsExpression(actualRetExpr) || ts.isParenthesizedExpression(actualRetExpr)) {
|
|
468
|
+
actualRetExpr = actualRetExpr.expression;
|
|
469
|
+
}
|
|
470
|
+
if (ts.isCallExpression(actualRetExpr) && ts.isIdentifier(actualRetExpr.expression)) {
|
|
471
|
+
for (const arg of actualRetExpr.arguments) {
|
|
472
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
473
|
+
}
|
|
474
|
+
const semanticOp = tryResolveSemanticCall(actualRetExpr.expression.text, actualRetExpr.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
475
|
+
if (semanticOp) {
|
|
476
|
+
halOps.push(semanticOp);
|
|
477
|
+
returnValue = "__hal_op_return__";
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Try to resolve semantic calls within compound expressions (e.g., gpioRead(pin) === HIGH)
|
|
482
|
+
const compoundResult = tryResolveCompoundSemanticReturn(retExpr, instance, paramNames, callArgTexts, paramDefaults, halOps, callArgs);
|
|
483
|
+
if (compoundResult !== null) {
|
|
484
|
+
returnValue = compoundResult;
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
// Handle: return new TypedArray(count) — mark as C array allocation
|
|
488
|
+
if (ts.isNewExpression(retExpr) && ts.isIdentifier(retExpr.expression)) {
|
|
489
|
+
const ctorName = retExpr.expression.text;
|
|
490
|
+
const elementType = TYPED_ARRAY_ELEMENT_MAP?.[ctorName];
|
|
491
|
+
if (elementType) {
|
|
492
|
+
const sizeArg = retExpr.arguments?.[0];
|
|
493
|
+
if (sizeArg) {
|
|
494
|
+
const size = resolveExpressionText(sizeArg, instance, paramNames, callArgTexts, paramDefaults);
|
|
495
|
+
if (size !== null) {
|
|
496
|
+
returnValue = `__TYPED_ARRAY__:${elementType}:${size}`;
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// Fall back to expression text resolution
|
|
503
|
+
const resolved = resolveExpressionText(retExpr, instance, paramNames, callArgTexts, paramDefaults);
|
|
504
|
+
if (resolved !== null) {
|
|
505
|
+
returnValue = resolved.replace(/===/g, "==").replace(/!==/g, "!=");
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Demo #33 Finding D — value-returning HAL methods encode their C++ return
|
|
510
|
+
// as a rawCpp emitLine/halOp of the form `return <expr>;` (e.g.
|
|
511
|
+
// Preferences.getInt → `return Preferences.getInt(k, d);`), while the TS
|
|
512
|
+
// method body's own `return 0;` / `return "";` is just a type-checking
|
|
513
|
+
// fallback. Without this, var-init context (`const v = Preferences.getInt(...)`)
|
|
514
|
+
// captured the TS fallback (0) instead of the real C++ value, so the
|
|
515
|
+
// roundtrip returned the default. When the trailing halOp is a `return X;`
|
|
516
|
+
// raw op, hoist X into returnValue so both statement and expression/var-init
|
|
517
|
+
// contexts use the real C++ expression.
|
|
518
|
+
if (halOps.length > 0) {
|
|
519
|
+
const lastOp = halOps[halOps.length - 1];
|
|
520
|
+
if (lastOp.operation === "raw" && typeof lastOp.code === "string") {
|
|
521
|
+
const m = lastOp.code.match(/^\s*return\s+([\s\S]+?);\s*$/);
|
|
522
|
+
if (m) {
|
|
523
|
+
returnValue = m[1].trim();
|
|
524
|
+
// Drop the now-redundant raw op so it isn't emitted as a stray
|
|
525
|
+
// statement alongside the value capture.
|
|
526
|
+
halOps.pop();
|
|
527
|
+
emitLines.length = 0;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Auto-passthrough for stub methods: if no emit() calls and the return is a literal
|
|
532
|
+
// (e.g., return 0), construct the C++ expression as <objectName>.<method>(<args>).
|
|
533
|
+
// Skip for Pin classes since Pin methods are typically lowered to standalone C calls (digitalRead/Write).
|
|
534
|
+
if (emitLines.length === 0 && isLiteralReturnValue(returnValue)
|
|
535
|
+
&& instance.className !== "Pin"
|
|
536
|
+
&& instance.className !== "OutputPin"
|
|
537
|
+
&& instance.className !== "InputPin") {
|
|
538
|
+
const cppObj = resolveCppObjectName(instance);
|
|
539
|
+
if (cppObj) {
|
|
540
|
+
const argsStr = callArgTexts.join(", ");
|
|
541
|
+
return { emitLines: [], halOps: [], returnValue: `${cppObj}.${methodName}(${argsStr})` };
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// Return null if nothing useful was resolved, allowing inline fallbacks to kick in
|
|
545
|
+
if (emitLines.length === 0 && halOps.length === 0 && returnValue === undefined) {
|
|
546
|
+
// take()/release() are intentional no-ops (ownership is compile-time only).
|
|
547
|
+
if (methodName === "take" || methodName === "release") {
|
|
548
|
+
return { emitLines: [], halOps: [] };
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
// Extract return type annotation to support type-narrowed pattern
|
|
553
|
+
// (e.g., Pin.asOutput(): OutputPin → returnClassName = "OutputPin")
|
|
554
|
+
let returnClassName;
|
|
555
|
+
const returnType = methodEntry.methodNode.type;
|
|
556
|
+
if (returnType && ts.isTypeReferenceNode(returnType) && ts.isIdentifier(returnType.typeName)) {
|
|
557
|
+
const name = returnType.typeName.text;
|
|
558
|
+
if (halClassRegistry.has(name) && name !== instance.className) {
|
|
559
|
+
returnClassName = name;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { emitLines, halOps, returnValue, returnClassName };
|
|
563
|
+
}
|
|
564
|
+
/** Check if a resolved C++ value string is a simple literal (for stub detection). */
|
|
565
|
+
export function isLiteralReturnValue(val) {
|
|
566
|
+
if (val === undefined)
|
|
567
|
+
return false;
|
|
568
|
+
return val === "0" || val === "true" || val === "false" || val === "''" || val === '""';
|
|
569
|
+
}
|
|
570
|
+
/** Resolve the C++ object name from a HAL instance's constructor field values. */
|
|
571
|
+
export function resolveCppObjectName(instance) {
|
|
572
|
+
const fieldMap = halClassRegistry.get(instance.className)?.ctorFieldMap;
|
|
573
|
+
if (!fieldMap)
|
|
574
|
+
return null;
|
|
575
|
+
// Get the first field value from the instance that matches a constructor field
|
|
576
|
+
for (const [fieldName] of fieldMap) {
|
|
577
|
+
const val = instance.fieldValues.get(fieldName);
|
|
578
|
+
if (val)
|
|
579
|
+
return val;
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
/** Process a list of statements for emit/include/semantic calls/return. */
|
|
584
|
+
export function processStatementList(stmts, instance, paramNames, callArgTexts, emitLines, halOps, callArgs, paramDefaults, returnExpr) {
|
|
585
|
+
for (const stmt of stmts) {
|
|
586
|
+
if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) {
|
|
587
|
+
const call = stmt.expression;
|
|
588
|
+
if (ts.isIdentifier(call.expression)) {
|
|
589
|
+
if (call.expression.text === "include") {
|
|
590
|
+
const firstArg = call.arguments[0];
|
|
591
|
+
if (firstArg && ts.isStringLiteral(firstArg)) {
|
|
592
|
+
requiredIncludes.add(firstArg.text);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
// R3: Try semantic HAL function call
|
|
597
|
+
for (const arg of call.arguments) {
|
|
598
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
599
|
+
}
|
|
600
|
+
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
601
|
+
if (semanticOp) {
|
|
602
|
+
halOps.push(semanticOp);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/** Reset resolver state (called between builds). */
|
|
610
|
+
export function resetHALResolver() {
|
|
611
|
+
halInstances.clear();
|
|
612
|
+
floatVariables.clear();
|
|
613
|
+
getContext().callbackPlaceholderCounter = 0;
|
|
614
|
+
getContext().snprintfCounter = 0;
|
|
615
|
+
}
|
|
616
|
+
/** Resolve a hal-expr IR node to its C++ text using the active strategy. */
|
|
617
|
+
export function setActiveStrategy(strategy) {
|
|
618
|
+
getContext().activeStrategy = strategy;
|
|
619
|
+
}
|
|
620
|
+
export function resolveHALExprToText(expr) {
|
|
621
|
+
const strategy = getContext().activeStrategy;
|
|
622
|
+
if (!strategy?.resolveHALOperation)
|
|
623
|
+
return null;
|
|
624
|
+
const resolved = strategy.resolveHALOperation(expr.operation);
|
|
625
|
+
if (resolved?.expression)
|
|
626
|
+
return resolved.expression;
|
|
627
|
+
if (resolved?.code)
|
|
628
|
+
return resolved.code.replace(/;\s*$/, "");
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
export function registerFloatVariable(name) {
|
|
632
|
+
floatVariables.add(name);
|
|
633
|
+
}
|
|
634
|
+
/** Build snprintf prelude lines from a string_concat expression.
|
|
635
|
+
* Returns { lines, bufferName } or null if the expression can't be formatted. */
|
|
636
|
+
export function buildSnprintfFromConcat(expr) {
|
|
637
|
+
let formatString = "";
|
|
638
|
+
const args = [];
|
|
639
|
+
let estimatedLength = 1;
|
|
640
|
+
const prelude = [];
|
|
641
|
+
requiredIncludes.add("<stdio.h>");
|
|
642
|
+
for (const part of expr.parts) {
|
|
643
|
+
const text = renderExprAsText(part);
|
|
644
|
+
if (part.kind === "string") {
|
|
645
|
+
formatString += text.slice(1, -1).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
646
|
+
estimatedLength += part.value.length;
|
|
647
|
+
}
|
|
648
|
+
else if (part.kind === "number") {
|
|
649
|
+
const isFloat = part.cppType === "float" || part.cppType === "double" || !Number.isInteger(part.value);
|
|
650
|
+
if (isFloat) {
|
|
651
|
+
requiredIncludes.add("<stdlib.h>");
|
|
652
|
+
const floatBuf = `__cuttlefish_float_${getContext().snprintfCounter++}`;
|
|
653
|
+
prelude.push(`char ${floatBuf}[16];`);
|
|
654
|
+
prelude.push(`dtostrf(${text}, 0, 1, ${floatBuf});`);
|
|
655
|
+
formatString += "%s";
|
|
656
|
+
args.push(floatBuf);
|
|
657
|
+
estimatedLength += 16;
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
formatString += "%d";
|
|
661
|
+
args.push(text);
|
|
662
|
+
estimatedLength += 12;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
// Check for string variable reference: template_string wrapping an identifier
|
|
667
|
+
const isStringVar = part.kind === "template_string"
|
|
668
|
+
&& part.expression.kind === "identifier"
|
|
669
|
+
&& (activeStringVars.has(part.expression.value) || getCurrentIrTypeScope()?.locals.get(part.expression.value) === "std::string" || getCurrentIrTypeScope()?.globals.get(part.expression.value) === "std::string");
|
|
670
|
+
// Check for float variable reference: template_string wrapping an identifier
|
|
671
|
+
const isFloatVar = part.kind === "template_string"
|
|
672
|
+
&& part.expression.kind === "identifier"
|
|
673
|
+
&& floatVariables.has(part.expression.value);
|
|
674
|
+
if (isStringVar) {
|
|
675
|
+
formatString += "%s";
|
|
676
|
+
const varName = part.expression.value;
|
|
677
|
+
const varType = getCurrentIrTypeScope()?.locals.get(varName) || getCurrentIrTypeScope()?.globals.get(varName) || "";
|
|
678
|
+
const cleanType = varType.replace(/\bconst\b\s*/g, "").trim();
|
|
679
|
+
if (cleanType && cleanType !== "char*" && cleanType !== "const char*") {
|
|
680
|
+
args.push(`${text}.c_str()`);
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
args.push(text);
|
|
684
|
+
}
|
|
685
|
+
estimatedLength += 32;
|
|
686
|
+
}
|
|
687
|
+
else if (isFloatVar) {
|
|
688
|
+
requiredIncludes.add("<stdlib.h>");
|
|
689
|
+
const floatBuf = `__cuttlefish_float_${getContext().snprintfCounter++}`;
|
|
690
|
+
prelude.push(`char ${floatBuf}[16];`);
|
|
691
|
+
prelude.push(`dtostrf(${text}, 0, 1, ${floatBuf});`);
|
|
692
|
+
formatString += "%s";
|
|
693
|
+
args.push(floatBuf);
|
|
694
|
+
estimatedLength += 16;
|
|
695
|
+
}
|
|
696
|
+
else if (part.kind === "template_string" && part.expression.kind === "hal-expr") {
|
|
697
|
+
// HAL expression inside template literal — resolve via strategy
|
|
698
|
+
const resolved = resolveHALExprToText(part.expression);
|
|
699
|
+
if (resolved !== null) {
|
|
700
|
+
formatString += "%d";
|
|
701
|
+
args.push(resolved);
|
|
702
|
+
estimatedLength += 12;
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
formatString += "%d";
|
|
706
|
+
args.push(text);
|
|
707
|
+
estimatedLength += 12;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
const numVal = Number(text);
|
|
712
|
+
if (!isNaN(numVal) && !Number.isInteger(numVal)) {
|
|
713
|
+
requiredIncludes.add("<stdlib.h>");
|
|
714
|
+
const floatBuf = `__cuttlefish_float_${getContext().snprintfCounter++}`;
|
|
715
|
+
const precision = text.includes(".") ? text.split(".")[1].length : 1;
|
|
716
|
+
prelude.push(`char ${floatBuf}[16];`);
|
|
717
|
+
prelude.push(`dtostrf(${text}, 0, ${precision}, ${floatBuf});`);
|
|
718
|
+
formatString += "%s";
|
|
719
|
+
args.push(floatBuf);
|
|
720
|
+
estimatedLength += 16;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
formatString += "%d";
|
|
724
|
+
args.push(text);
|
|
725
|
+
estimatedLength += 12;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const bufName = `__cuttlefish_snprintf_${getContext().snprintfCounter++}`;
|
|
731
|
+
const bufSize = Math.max(estimatedLength + 1, 16);
|
|
732
|
+
prelude.push(`char ${bufName}[${bufSize}];`);
|
|
733
|
+
prelude.push(`snprintf(${bufName}, sizeof(${bufName}), "${formatString}", ${args.join(", ")});`);
|
|
734
|
+
return { lines: prelude, bufferName: bufName };
|
|
735
|
+
}
|