@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.11
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/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +38 -2
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +595 -0
- package/dist/api/shared/framework-manifest.js +193 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +22 -2
- package/dist/api/shared/index.js +26 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +119 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/spdx-licenses.d.ts +139 -0
- package/dist/api/shared/spdx-licenses.js +385 -0
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +271 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +372 -30
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +207 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +15 -8
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +123 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Framework catalog + board→framework compatibility for `cuttlefish install`.
|
|
2
|
+
//
|
|
3
|
+
// A "framework" is a `@typecad/framework-<id>` npm package. This module is the
|
|
4
|
+
// single source of truth for which frameworks exist as installable packages and
|
|
5
|
+
// which are compatible with a given board architecture. It is deliberately
|
|
6
|
+
// free of side effects (no fs/process beyond lockfile/package.json reads in
|
|
7
|
+
// detectPackageManager) so it unit-tests cleanly.
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
/**
|
|
11
|
+
* The installable frameworks. Kept aligned with the packages/ directory:
|
|
12
|
+
* framework-arduino, framework-native, framework-zephyr all ship real packages.
|
|
13
|
+
* esp-idf is intentionally absent (no published package in this repo).
|
|
14
|
+
*/
|
|
15
|
+
export const FRAMEWORK_CATALOG = [
|
|
16
|
+
{ id: "arduino", packageName: "@typecad/framework-arduino", label: "Arduino (digitalWrite, Wire, SPI)", installable: true },
|
|
17
|
+
{ id: "zephyr", packageName: "@typecad/framework-zephyr", label: "Zephyr RTOS", installable: true },
|
|
18
|
+
{ id: "native", packageName: "@typecad/framework-native", label: "Native (Windows/Linux executable)", installable: true },
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Architecture → compatible framework ids. Derived from each framework
|
|
22
|
+
* package's framework.manifest.ts `profile.targets`:
|
|
23
|
+
* - arduino: avr, esp32 family, rp2040/rp2350, samd, stm32
|
|
24
|
+
* - zephyr: nrf52 (xiao_ble), esp32, esp32s3
|
|
25
|
+
* - native: desktop only
|
|
26
|
+
* Unknown embedded architectures fall back to [arduino] (the broadest core).
|
|
27
|
+
*/
|
|
28
|
+
const ARCHITECTURE_FRAMEWORKS = {
|
|
29
|
+
avr: ["arduino"],
|
|
30
|
+
esp32: ["arduino", "zephyr"],
|
|
31
|
+
esp32s2: ["arduino"],
|
|
32
|
+
esp32s3: ["arduino", "zephyr"],
|
|
33
|
+
esp32c3: ["arduino"],
|
|
34
|
+
esp32c6: ["arduino"],
|
|
35
|
+
rp2040: ["arduino"],
|
|
36
|
+
rp2350: ["arduino"],
|
|
37
|
+
samd: ["arduino"],
|
|
38
|
+
stm32: ["arduino"],
|
|
39
|
+
nrf52: ["zephyr"],
|
|
40
|
+
};
|
|
41
|
+
const FALLBACK_FRAMEWORKS = ["arduino"];
|
|
42
|
+
/** Look up a catalog entry by framework id (e.g. "arduino"). */
|
|
43
|
+
export function frameworkCatalogEntry(id) {
|
|
44
|
+
return FRAMEWORK_CATALOG.find((f) => f.id === id);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The framework ids compatible with a board. Native boards map to ["native"];
|
|
48
|
+
* embedded boards map via ARCHITECTURE_FRAMEWORKS (falling back to arduino for
|
|
49
|
+
* unknown architectures). Order is preserved as the catalog order so the most
|
|
50
|
+
* common framework is offered first in the prompt.
|
|
51
|
+
*/
|
|
52
|
+
export function frameworksForTarget(target) {
|
|
53
|
+
let ids;
|
|
54
|
+
if (target.isNative) {
|
|
55
|
+
ids = ["native"];
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
ids = ARCHITECTURE_FRAMEWORKS[target.architecture ?? ""] ?? FALLBACK_FRAMEWORKS;
|
|
59
|
+
}
|
|
60
|
+
// Re-map ids → catalog entries in catalog order (stable ordering), dropping
|
|
61
|
+
// any id that has no catalog entry (defensive — keeps the prompt clean).
|
|
62
|
+
return FRAMEWORK_CATALOG.filter((entry) => ids.includes(entry.id));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect the package manager for a directory. Priority:
|
|
66
|
+
* 1. package.json#packageManager field (the strongest signal, Corepack-style)
|
|
67
|
+
* 2. lockfile presence (pnpm-lock.yaml / yarn.lock → otherwise npm)
|
|
68
|
+
* 3. default "npm"
|
|
69
|
+
* Never throws — unreadable/missing files fall through to the next signal.
|
|
70
|
+
*/
|
|
71
|
+
export function detectPackageManager(cwd) {
|
|
72
|
+
try {
|
|
73
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
74
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
75
|
+
const pm = typeof pkg.packageManager === "string" ? pkg.packageManager : "";
|
|
76
|
+
if (pm.startsWith("pnpm"))
|
|
77
|
+
return "pnpm";
|
|
78
|
+
if (pm.startsWith("yarn"))
|
|
79
|
+
return "yarn";
|
|
80
|
+
if (pm.startsWith("npm"))
|
|
81
|
+
return "npm";
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// no package.json or unparseable JSON — fall through to lockfile detection
|
|
85
|
+
}
|
|
86
|
+
if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml")))
|
|
87
|
+
return "pnpm";
|
|
88
|
+
if (fs.existsSync(path.join(cwd, "yarn.lock")))
|
|
89
|
+
return "yarn";
|
|
90
|
+
// package-lock.json implies npm; absence also defaults to npm.
|
|
91
|
+
return "npm";
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the package-manager invocation that installs `packageName` into the
|
|
95
|
+
* current project (project-local, never global — per the install command spec).
|
|
96
|
+
*/
|
|
97
|
+
export function buildInstallCommand(pm, packageName) {
|
|
98
|
+
switch (pm) {
|
|
99
|
+
case "pnpm":
|
|
100
|
+
return { bin: "pnpm", args: ["add", packageName] };
|
|
101
|
+
case "yarn":
|
|
102
|
+
return { bin: "yarn", args: ["add", packageName] };
|
|
103
|
+
case "npm":
|
|
104
|
+
default:
|
|
105
|
+
return { bin: "npm", args: ["install", packageName] };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { InstallCommandOptions } from "../types.js";
|
|
2
|
+
import { type FrameworkCatalogEntry, type PackageManager } from "./framework-catalog.js";
|
|
3
|
+
interface InstallCommand {
|
|
4
|
+
bin: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
cwd: string;
|
|
7
|
+
}
|
|
8
|
+
interface InstallRunResult {
|
|
9
|
+
/** Exit status; null when the process could not be launched (ENOENT, etc.). */
|
|
10
|
+
status: number | null;
|
|
11
|
+
/** Populated only when the binary could not be launched. */
|
|
12
|
+
launchError?: string;
|
|
13
|
+
}
|
|
14
|
+
type InstallRunner = (cmd: InstallCommand) => InstallRunResult;
|
|
15
|
+
/**
|
|
16
|
+
* FOR TESTS ONLY. Replaces the real spawn-based installer with `runner`.
|
|
17
|
+
* Pass `undefined` to restore the real executor.
|
|
18
|
+
*/
|
|
19
|
+
export declare function __setInstallRunnerForTest(runner: InstallRunner | undefined): void;
|
|
20
|
+
export interface InstallResult {
|
|
21
|
+
pm: PackageManager;
|
|
22
|
+
bin: string;
|
|
23
|
+
args: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the package manager and run the install for `entry`'s package. In
|
|
27
|
+
* dry-run mode the command is printed but not executed. Throws on launch
|
|
28
|
+
* failure or non-zero exit so the CLI surfaces a clear error.
|
|
29
|
+
*/
|
|
30
|
+
export declare function installFrameworkPackage(entry: FrameworkCatalogEntry, opts: {
|
|
31
|
+
cwd: string;
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
}): InstallResult;
|
|
34
|
+
export declare function handleInstall(options: InstallCommandOptions): Promise<void>;
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// `cuttlefish install` — installs a chosen @typecad/framework-* package into the
|
|
2
|
+
// current project. The flow asks which board to target first, then narrows the
|
|
3
|
+
// framework choices to those compatible with that board (see framework-catalog).
|
|
4
|
+
// A framework id may be passed directly (`cuttlefish install arduino`) to skip
|
|
5
|
+
// the prompts entirely, which also makes the command usable in CI.
|
|
6
|
+
import * as readline from "node:readline/promises";
|
|
7
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import { KNOWN_TARGETS } from "../create/index.js";
|
|
11
|
+
import { FRAMEWORK_CATALOG, frameworkCatalogEntry, frameworksForTarget, detectPackageManager, buildInstallCommand, } from "./framework-catalog.js";
|
|
12
|
+
// ── prompts (mirror the create wizard's style for a consistent UX) ──────────
|
|
13
|
+
async function promptSelect(rl, prompt, options) {
|
|
14
|
+
console.log(`${chalk.cyan("?")} ${prompt}:`);
|
|
15
|
+
for (let i = 0; i < options.length; i++) {
|
|
16
|
+
console.log(` ${chalk.dim(`${i + 1})`)} ${options[i].label}`);
|
|
17
|
+
}
|
|
18
|
+
while (true) {
|
|
19
|
+
const answer = await rl.question(` Enter number (1-${options.length}): `);
|
|
20
|
+
const idx = parseInt(answer.trim(), 10) - 1;
|
|
21
|
+
if (idx >= 0 && idx < options.length) {
|
|
22
|
+
return options[idx].value;
|
|
23
|
+
}
|
|
24
|
+
console.log(` ${chalk.red("✗")} Please enter a number between 1 and ${options.length}.`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function selectBoardInteractively(rl) {
|
|
28
|
+
const options = KNOWN_TARGETS.map((t) => ({
|
|
29
|
+
label: t.isNative
|
|
30
|
+
? `${t.displayName} (Windows/Linux executable)`
|
|
31
|
+
: `${t.displayName} (${t.architecture.toUpperCase()})`,
|
|
32
|
+
value: t.id,
|
|
33
|
+
}));
|
|
34
|
+
const selectedId = await promptSelect(rl, "Target board", options);
|
|
35
|
+
return KNOWN_TARGETS.find((t) => t.id === selectedId);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pick a framework for `target`. When only one framework is compatible with the
|
|
39
|
+
* board it is auto-selected (mirrors the create wizard's single-option path) so
|
|
40
|
+
* the user isn't asked a question with one answer.
|
|
41
|
+
*/
|
|
42
|
+
async function selectFrameworkInteractively(rl, target, compatible) {
|
|
43
|
+
if (compatible.length === 1) {
|
|
44
|
+
const only = compatible[0];
|
|
45
|
+
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(only.label)} ${chalk.dim(`(only option for ${target.id})`)}`);
|
|
46
|
+
return only;
|
|
47
|
+
}
|
|
48
|
+
const selectedId = await promptSelect(rl, "Framework", compatible.map((f) => ({ label: f.label, value: f.id })));
|
|
49
|
+
return compatible.find((f) => f.id === selectedId);
|
|
50
|
+
}
|
|
51
|
+
let testRunner;
|
|
52
|
+
/**
|
|
53
|
+
* FOR TESTS ONLY. Replaces the real spawn-based installer with `runner`.
|
|
54
|
+
* Pass `undefined` to restore the real executor.
|
|
55
|
+
*/
|
|
56
|
+
export function __setInstallRunnerForTest(runner) {
|
|
57
|
+
testRunner = runner;
|
|
58
|
+
}
|
|
59
|
+
function runRealInstall(cmd) {
|
|
60
|
+
// stdio: "inherit" streams the package manager's own output to the terminal
|
|
61
|
+
// (install progress, deprecation warnings, etc.). On failure the user has
|
|
62
|
+
// already seen the details above, so we only need to report the exit code.
|
|
63
|
+
const result = spawnSync(cmd.bin, cmd.args, {
|
|
64
|
+
cwd: cmd.cwd,
|
|
65
|
+
stdio: "inherit",
|
|
66
|
+
});
|
|
67
|
+
if (result.error) {
|
|
68
|
+
const errno = result.error.code;
|
|
69
|
+
return {
|
|
70
|
+
status: null,
|
|
71
|
+
launchError: errno === "ENOENT" ? `'${cmd.bin}' not found on PATH` : String(result.error),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { status: result.status };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Resolve the package manager and run the install for `entry`'s package. In
|
|
78
|
+
* dry-run mode the command is printed but not executed. Throws on launch
|
|
79
|
+
* failure or non-zero exit so the CLI surfaces a clear error.
|
|
80
|
+
*/
|
|
81
|
+
export function installFrameworkPackage(entry, opts) {
|
|
82
|
+
const pm = detectPackageManager(opts.cwd);
|
|
83
|
+
const { bin, args } = buildInstallCommand(pm, entry.packageName);
|
|
84
|
+
if (opts.dryRun) {
|
|
85
|
+
console.log(`${chalk.cyan("$")} ${bin} ${args.join(" ")}`);
|
|
86
|
+
return { pm, bin, args };
|
|
87
|
+
}
|
|
88
|
+
const cmd = { bin, args, cwd: opts.cwd };
|
|
89
|
+
const run = testRunner ?? runRealInstall;
|
|
90
|
+
const result = run(cmd);
|
|
91
|
+
if (result.launchError) {
|
|
92
|
+
throw new Error(`Failed to run '${bin}': ${result.launchError}`);
|
|
93
|
+
}
|
|
94
|
+
if (result.status !== 0) {
|
|
95
|
+
throw new Error(`'${bin} ${args.join(" ")}' exited with code ${result.status}. See the package manager output above for details.`);
|
|
96
|
+
}
|
|
97
|
+
return { pm, bin, args };
|
|
98
|
+
}
|
|
99
|
+
// ── command entry point ─────────────────────────────────────────────────────
|
|
100
|
+
export async function handleInstall(options) {
|
|
101
|
+
const cwd = process.cwd();
|
|
102
|
+
// 1. Resolve which framework to install.
|
|
103
|
+
let entry;
|
|
104
|
+
if (options.framework) {
|
|
105
|
+
const resolved = frameworkCatalogEntry(options.framework);
|
|
106
|
+
if (!resolved) {
|
|
107
|
+
const available = FRAMEWORK_CATALOG.filter((f) => f.installable).map((f) => f.id).join(", ");
|
|
108
|
+
throw new Error(`Unknown framework '${options.framework}'. Available: ${available}`);
|
|
109
|
+
}
|
|
110
|
+
if (!resolved.installable) {
|
|
111
|
+
throw new Error(`Framework '${options.framework}' has no published package yet and cannot be installed.`);
|
|
112
|
+
}
|
|
113
|
+
entry = resolved;
|
|
114
|
+
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(entry.label)}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// Resolve the target board. --board skips the board prompt; otherwise the
|
|
118
|
+
// board is chosen interactively (which requires a TTY — handled below).
|
|
119
|
+
let target;
|
|
120
|
+
if (options.board) {
|
|
121
|
+
const found = KNOWN_TARGETS.find((t) => t.id === options.board);
|
|
122
|
+
if (!found) {
|
|
123
|
+
const available = KNOWN_TARGETS.map((t) => t.id).join(", ");
|
|
124
|
+
throw new Error(`Unknown board '${options.board}'. Available: ${available}`);
|
|
125
|
+
}
|
|
126
|
+
target = found;
|
|
127
|
+
console.log(`${chalk.cyan("?")} Board: ${chalk.white(target.displayName)} (${chalk.dim(target.id)})`);
|
|
128
|
+
}
|
|
129
|
+
// Narrow to the frameworks compatible with that board.
|
|
130
|
+
let compatible;
|
|
131
|
+
if (target) {
|
|
132
|
+
compatible = frameworksForTarget(target).filter((f) => f.installable);
|
|
133
|
+
if (compatible.length === 0) {
|
|
134
|
+
throw new Error(`No installable frameworks are compatible with board '${target.id}'.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (compatible && compatible.length === 1) {
|
|
138
|
+
// Only one framework fits this board — auto-select it (no prompt, so this
|
|
139
|
+
// path also works under CI / non-interactive stdin).
|
|
140
|
+
entry = compatible[0];
|
|
141
|
+
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(entry.label)} ${chalk.dim(`(only option for ${target.id})`)}`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// Need to prompt — for the board (if --board wasn't given) and/or for the
|
|
145
|
+
// framework (when 2+ are compatible). Refuse to hang on a readline that
|
|
146
|
+
// can't be answered (e.g. piped stdin under CI).
|
|
147
|
+
if (!process.stdin.isTTY) {
|
|
148
|
+
throw new Error("No framework specified and stdin is not interactive. " +
|
|
149
|
+
"Pass a framework id (e.g. 'cuttlefish install arduino'), " +
|
|
150
|
+
"or a board with a single compatible framework (e.g. '--board arduino-uno').");
|
|
151
|
+
}
|
|
152
|
+
const rl = readline.createInterface({ input, output });
|
|
153
|
+
try {
|
|
154
|
+
if (!target) {
|
|
155
|
+
target = await selectBoardInteractively(rl);
|
|
156
|
+
compatible = frameworksForTarget(target).filter((f) => f.installable);
|
|
157
|
+
if (compatible.length === 0) {
|
|
158
|
+
throw new Error(`No installable frameworks are compatible with board '${target.id}'.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
entry = await selectFrameworkInteractively(rl, target, compatible);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
rl.close();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 2. Install it.
|
|
169
|
+
console.log(`\n${chalk.cyan("⤳")} Installing ${chalk.white(entry.packageName)} into ${chalk.dim(cwd)}…`);
|
|
170
|
+
const result = installFrameworkPackage(entry, { cwd, dryRun: options.dryRun });
|
|
171
|
+
if (options.dryRun) {
|
|
172
|
+
console.log(chalk.dim("(dry-run — nothing was installed)"));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
console.log(`\n${chalk.green("✓")} Installed ${chalk.white(entry.packageName)} via ${result.pm}.`);
|
|
176
|
+
console.log(chalk.dim(`Next: run 'cuttlefish create' and pick ${entry.id}, or set framework: "${entry.packageName}" in cuttlefish.config.ts.`));
|
|
177
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { handleInstall, installFrameworkPackage, __setInstallRunnerForTest } from "./handle-install.js";
|
|
2
|
+
export type { InstallResult } from "./handle-install.js";
|
|
3
|
+
export { FRAMEWORK_CATALOG, frameworkCatalogEntry, frameworksForTarget, detectPackageManager, buildInstallCommand, } from "./framework-catalog.js";
|
|
4
|
+
export type { FrameworkCatalogEntry, BoardLike, PackageManager, } from "./framework-catalog.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// `cuttlefish install` command — install a @typecad/framework-* package.
|
|
2
|
+
export { handleInstall, installFrameworkPackage, __setInstallRunnerForTest } from "./handle-install.js";
|
|
3
|
+
export { FRAMEWORK_CATALOG, frameworkCatalogEntry, frameworksForTarget, detectPackageManager, buildInstallCommand, } from "./framework-catalog.js";
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// Detects comparisons against analogRead() values that exceed the board's
|
|
5
5
|
// ADC resolution (e.g., comparing to > 1023 on a 10-bit Arduino Uno).
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
|
+
import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
|
|
7
8
|
/**
|
|
8
9
|
* Get ADC configuration from board constants.
|
|
9
10
|
*/
|
|
@@ -41,7 +42,19 @@ function isAnalogRead(expr) {
|
|
|
41
42
|
return op === 'adc.read' || op === 'adc.read_voltage';
|
|
42
43
|
}
|
|
43
44
|
if (expr.kind === 'raw') {
|
|
44
|
-
|
|
45
|
+
const value = expr.value;
|
|
46
|
+
// Ask the loaded framework which call names produce an ADC read; fall back
|
|
47
|
+
// to the Wiring-derived analogRead when no framework is loaded.
|
|
48
|
+
const frameworkNames = hasLoadedFramework()
|
|
49
|
+
? getLoadedFramework().strategy.analogReadCallNames?.()
|
|
50
|
+
: undefined;
|
|
51
|
+
const names = frameworkNames ?? new Set(['analogRead']);
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const re = new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\(`);
|
|
54
|
+
if (re.test(value))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
45
58
|
}
|
|
46
59
|
return false;
|
|
47
60
|
}
|
|
@@ -59,7 +72,7 @@ function extractNumericValue(expr) {
|
|
|
59
72
|
/**
|
|
60
73
|
* Check binary comparison expressions for ADC range issues.
|
|
61
74
|
*/
|
|
62
|
-
function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostics) {
|
|
75
|
+
function checkComparisonForADCRange(left, operator, right, adcConfig, filePath, diagnostics) {
|
|
63
76
|
// Check if one side is an analog read and the other is a literal
|
|
64
77
|
let analogReadSide = null;
|
|
65
78
|
let literalValue = null;
|
|
@@ -83,6 +96,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
|
|
|
83
96
|
severity: 'info',
|
|
84
97
|
message: `Comparison ${comparisonDesc} may never be true. ADC resolution is ${adcConfig.resolution}-bit (max ${adcConfig.maxValue}) on this board.`,
|
|
85
98
|
code: 'adc-range-warning',
|
|
99
|
+
filePath,
|
|
86
100
|
source: 'adc-range-validation',
|
|
87
101
|
});
|
|
88
102
|
}
|
|
@@ -90,7 +104,7 @@ function checkComparisonForADCRange(left, operator, right, adcConfig, diagnostic
|
|
|
90
104
|
/**
|
|
91
105
|
* Scan an expression for ADC range issues.
|
|
92
106
|
*/
|
|
93
|
-
function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
|
|
107
|
+
function scanExpressionForADCRange(expr, adcConfig, filePath, diagnostics) {
|
|
94
108
|
if (!expr || typeof expr !== 'object')
|
|
95
109
|
return;
|
|
96
110
|
// Check binary comparisons
|
|
@@ -98,59 +112,59 @@ function scanExpressionForADCRange(expr, adcConfig, diagnostics) {
|
|
|
98
112
|
const bin = expr;
|
|
99
113
|
const comparisonOps = ['>', '>=', '<', '<=', '===', '==', '!==', '!='];
|
|
100
114
|
if (comparisonOps.includes(bin.operator)) {
|
|
101
|
-
checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, diagnostics);
|
|
115
|
+
checkComparisonForADCRange(bin.left, bin.operator, bin.right, adcConfig, filePath, diagnostics);
|
|
102
116
|
}
|
|
103
117
|
// Recursively scan both sides
|
|
104
|
-
scanExpressionForADCRange(bin.left, adcConfig, diagnostics);
|
|
105
|
-
scanExpressionForADCRange(bin.right, adcConfig, diagnostics);
|
|
118
|
+
scanExpressionForADCRange(bin.left, adcConfig, filePath, diagnostics);
|
|
119
|
+
scanExpressionForADCRange(bin.right, adcConfig, filePath, diagnostics);
|
|
106
120
|
}
|
|
107
121
|
// Check ternary conditions
|
|
108
122
|
if (expr.kind === 'ternary') {
|
|
109
123
|
const ternary = expr;
|
|
110
|
-
scanExpressionForADCRange(ternary.condition, adcConfig, diagnostics);
|
|
111
|
-
scanExpressionForADCRange(ternary.whenTrue, adcConfig, diagnostics);
|
|
112
|
-
scanExpressionForADCRange(ternary.whenFalse, adcConfig, diagnostics);
|
|
124
|
+
scanExpressionForADCRange(ternary.condition, adcConfig, filePath, diagnostics);
|
|
125
|
+
scanExpressionForADCRange(ternary.whenTrue, adcConfig, filePath, diagnostics);
|
|
126
|
+
scanExpressionForADCRange(ternary.whenFalse, adcConfig, filePath, diagnostics);
|
|
113
127
|
}
|
|
114
128
|
// Check property access
|
|
115
129
|
if (expr.kind === 'property-access') {
|
|
116
130
|
const pa = expr;
|
|
117
|
-
scanExpressionForADCRange(pa.object, adcConfig, diagnostics);
|
|
131
|
+
scanExpressionForADCRange(pa.object, adcConfig, filePath, diagnostics);
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
/**
|
|
121
135
|
* Scan a statement for ADC range issues.
|
|
122
136
|
*/
|
|
123
|
-
function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
137
|
+
function scanStatementForADCRange(stmt, adcConfig, filePath, diagnostics) {
|
|
124
138
|
if (!stmt || typeof stmt !== 'object')
|
|
125
139
|
return;
|
|
126
140
|
switch (stmt.kind) {
|
|
127
141
|
case 'var_decl': {
|
|
128
142
|
const varDecl = stmt;
|
|
129
143
|
if (varDecl.initializer) {
|
|
130
|
-
scanExpressionForADCRange(varDecl.initializer, adcConfig, diagnostics);
|
|
144
|
+
scanExpressionForADCRange(varDecl.initializer, adcConfig, filePath, diagnostics);
|
|
131
145
|
}
|
|
132
146
|
break;
|
|
133
147
|
}
|
|
134
148
|
case 'assign': {
|
|
135
149
|
const assign = stmt;
|
|
136
150
|
if (assign.value) {
|
|
137
|
-
scanExpressionForADCRange(assign.value, adcConfig, diagnostics);
|
|
151
|
+
scanExpressionForADCRange(assign.value, adcConfig, filePath, diagnostics);
|
|
138
152
|
}
|
|
139
153
|
break;
|
|
140
154
|
}
|
|
141
155
|
case 'if': {
|
|
142
156
|
const ifStmt = stmt;
|
|
143
157
|
if (ifStmt.condition) {
|
|
144
|
-
scanExpressionForADCRange(ifStmt.condition, adcConfig, diagnostics);
|
|
158
|
+
scanExpressionForADCRange(ifStmt.condition, adcConfig, filePath, diagnostics);
|
|
145
159
|
}
|
|
146
160
|
if (ifStmt.thenBranch) {
|
|
147
161
|
for (const s of ifStmt.thenBranch) {
|
|
148
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
162
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
if (ifStmt.elseBranch) {
|
|
152
166
|
for (const s of ifStmt.elseBranch) {
|
|
153
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
167
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
154
168
|
}
|
|
155
169
|
}
|
|
156
170
|
break;
|
|
@@ -158,11 +172,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
158
172
|
case 'while': {
|
|
159
173
|
const whileStmt = stmt;
|
|
160
174
|
if (whileStmt.condition) {
|
|
161
|
-
scanExpressionForADCRange(whileStmt.condition, adcConfig, diagnostics);
|
|
175
|
+
scanExpressionForADCRange(whileStmt.condition, adcConfig, filePath, diagnostics);
|
|
162
176
|
}
|
|
163
177
|
if (whileStmt.body) {
|
|
164
178
|
for (const s of whileStmt.body) {
|
|
165
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
179
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
166
180
|
}
|
|
167
181
|
}
|
|
168
182
|
break;
|
|
@@ -170,11 +184,11 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
170
184
|
case 'for': {
|
|
171
185
|
const forStmt = stmt;
|
|
172
186
|
if (forStmt.condition) {
|
|
173
|
-
scanExpressionForADCRange(forStmt.condition, adcConfig, diagnostics);
|
|
187
|
+
scanExpressionForADCRange(forStmt.condition, adcConfig, filePath, diagnostics);
|
|
174
188
|
}
|
|
175
189
|
if (forStmt.body) {
|
|
176
190
|
for (const s of forStmt.body) {
|
|
177
|
-
scanStatementForADCRange(s, adcConfig, diagnostics);
|
|
191
|
+
scanStatementForADCRange(s, adcConfig, filePath, diagnostics);
|
|
178
192
|
}
|
|
179
193
|
}
|
|
180
194
|
break;
|
|
@@ -182,7 +196,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
182
196
|
case 'return': {
|
|
183
197
|
const retStmt = stmt;
|
|
184
198
|
if (retStmt.value) {
|
|
185
|
-
scanExpressionForADCRange(retStmt.value, adcConfig, diagnostics);
|
|
199
|
+
scanExpressionForADCRange(retStmt.value, adcConfig, filePath, diagnostics);
|
|
186
200
|
}
|
|
187
201
|
break;
|
|
188
202
|
}
|
|
@@ -190,7 +204,7 @@ function scanStatementForADCRange(stmt, adcConfig, diagnostics) {
|
|
|
190
204
|
const call = stmt;
|
|
191
205
|
if (call.args) {
|
|
192
206
|
for (const arg of call.args) {
|
|
193
|
-
scanExpressionForADCRange(arg, adcConfig, diagnostics);
|
|
207
|
+
scanExpressionForADCRange(arg, adcConfig, filePath, diagnostics);
|
|
194
208
|
}
|
|
195
209
|
}
|
|
196
210
|
break;
|
|
@@ -214,7 +228,7 @@ export function validateADCRange(program, boardConstants) {
|
|
|
214
228
|
// Scan top-level statements
|
|
215
229
|
if (program.topLevelStatements) {
|
|
216
230
|
for (const stmt of program.topLevelStatements) {
|
|
217
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
231
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
218
232
|
}
|
|
219
233
|
}
|
|
220
234
|
// Scan function bodies
|
|
@@ -222,7 +236,7 @@ export function validateADCRange(program, boardConstants) {
|
|
|
222
236
|
for (const fn of program.functions) {
|
|
223
237
|
if (fn.statements) {
|
|
224
238
|
for (const stmt of fn.statements) {
|
|
225
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
239
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
226
240
|
}
|
|
227
241
|
}
|
|
228
242
|
}
|
|
@@ -234,14 +248,14 @@ export function validateADCRange(program, boardConstants) {
|
|
|
234
248
|
for (const method of cls.methods) {
|
|
235
249
|
if (method.statements) {
|
|
236
250
|
for (const stmt of method.statements) {
|
|
237
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
251
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
}
|
|
241
255
|
}
|
|
242
256
|
if (cls.constructor?.statements) {
|
|
243
257
|
for (const stmt of cls.constructor.statements) {
|
|
244
|
-
scanStatementForADCRange(stmt, adcConfig, diagnostics);
|
|
258
|
+
scanStatementForADCRange(stmt, adcConfig, program.fileName, diagnostics);
|
|
245
259
|
}
|
|
246
260
|
}
|
|
247
261
|
}
|
|
@@ -78,6 +78,7 @@ export declare class CompilationContext {
|
|
|
78
78
|
floatVariables: Set<string>;
|
|
79
79
|
snprintfCounter: number;
|
|
80
80
|
callbackPlaceholderCounter: number;
|
|
81
|
+
bleCharCounter: number;
|
|
81
82
|
activeStrategy: PlatformStrategy | null;
|
|
82
83
|
restParamFunctions: Map<string, string>;
|
|
83
84
|
/**
|
|
@@ -65,6 +65,12 @@ export class CompilationContext {
|
|
|
65
65
|
this.floatVariables = new Set();
|
|
66
66
|
this.snprintfCounter = 0;
|
|
67
67
|
this.callbackPlaceholderCounter = 0;
|
|
68
|
+
// BLE characteristic index counter — persists across separate Ble.server()
|
|
69
|
+
// calls so a multi-characteristic server (one server() per char, the common
|
|
70
|
+
// ble-demo pattern) gets unique sequential indices instead of every char
|
|
71
|
+
// clobbering slot 0. Read by the server()/characteristic() resolver branches
|
|
72
|
+
// in hal-parser.ts. Reset per file in hal-emitter.ts with the other counters.
|
|
73
|
+
this.bleCharCounter = 0;
|
|
68
74
|
this.activeStrategy = null;
|
|
69
75
|
this.restParamFunctions = new Map();
|
|
70
76
|
/**
|
|
@@ -246,6 +252,14 @@ export function setCurrentBoardConstants(v) {
|
|
|
246
252
|
pinAliasMap.set(name, num);
|
|
247
253
|
mcuPinForwardMap.set(name, num);
|
|
248
254
|
mcuPinReverseMap.set(num, name);
|
|
255
|
+
// Register an identifier-safe variant so that pins whose canonical
|
|
256
|
+
// name is not a legal JS identifier (e.g. nRF52840 "P0.28") can be
|
|
257
|
+
// imported under their underscore form (P0_28). See "Pin Naming
|
|
258
|
+
// Conventions" in the root AGENTS.md.
|
|
259
|
+
const identName = name.replace(/[.\s-]/g, "_");
|
|
260
|
+
if (identName !== name) {
|
|
261
|
+
pinAliasMap.set(identName, num);
|
|
262
|
+
}
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
}
|
package/dist/ir/build-ir.js
CHANGED
|
@@ -144,7 +144,11 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
144
144
|
const parentStrategy = getContext().activeStrategy;
|
|
145
145
|
return contextStorage.run(new CompilationContext(), () => {
|
|
146
146
|
getContext().activeStrategy = parentStrategy;
|
|
147
|
-
|
|
147
|
+
// Ensure HAL modules are loaded. transpile.ts warms the HAL registry once
|
|
148
|
+
// per transpile run (before the per-file IR build), so this is normally a
|
|
149
|
+
// cheap no-op. The non-forced call is a safety net for direct/test callers
|
|
150
|
+
// of buildProgramIR that didn't warm the registry first.
|
|
151
|
+
loadHALModules();
|
|
148
152
|
const normalizedSourceText = normalizeEntrypointSyntax(sourceText);
|
|
149
153
|
const source = parseSource(fileName, normalizedSourceText);
|
|
150
154
|
const diagnostics = [];
|
|
@@ -439,14 +443,14 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
439
443
|
}
|
|
440
444
|
}
|
|
441
445
|
}
|
|
442
|
-
// Track HAL instances imported from board packages and framework
|
|
443
|
-
// so the HAL resolver can resolve them to
|
|
446
|
+
// Track HAL instances imported from board packages and framework HAL
|
|
447
|
+
// subpaths so the HAL resolver can resolve them to framework C++ names.
|
|
444
448
|
// The `@typecad/board` virtual import resolves to the board package, so it
|
|
445
449
|
// also registers pin aliases (LED, etc.). Case-insensitive on the scope.
|
|
446
450
|
const lowerSpecifier = moduleSpecifier.toLowerCase();
|
|
447
451
|
const isHALSource = lowerSpecifier.startsWith('@typecad/board-')
|
|
448
|
-
|| lowerSpecifier === '@typecad/
|
|
449
|
-
|| lowerSpecifier
|
|
452
|
+
|| lowerSpecifier === '@typecad/board'
|
|
453
|
+
|| /^@typecad\/framework-[a-z0-9-]+\/(arduino|hal|gpio)$/.test(lowerSpecifier);
|
|
450
454
|
// UI authoring namespace: `import { ui } from "@typecad/ui"`. The `ui`
|
|
451
455
|
// value is a compile-time construct (its calls are intercepted by
|
|
452
456
|
// tryResolveUICall); it must NOT be emitted as a C++ struct/value.
|
package/dist/ir/call-graph.js
CHANGED
|
@@ -159,6 +159,22 @@ export function buildCallGraph(program) {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
// Registered callbacks from the HAL resolver (e.g. the arrow function passed
|
|
163
|
+
// to `Ble.characteristic(...).onRead(() => readTemp())`) are NOT present in
|
|
164
|
+
// top-level statements — they ride in `program.registeredCallbacks` and are
|
|
165
|
+
// later hoisted to file-scope `*_isr_*` functions by top-level-prep. The
|
|
166
|
+
// placeholder name (`__CALLBACK_N__`) is what surfaces in top-level deps,
|
|
167
|
+
// not the identifiers the callback body references (e.g. a free function
|
|
168
|
+
// like `readTemp`). Without this scan those references are invisible to the
|
|
169
|
+
// call graph: the function survives the hoist (its call sits inside the
|
|
170
|
+
// emitted ISR) but is tree-shaken as unreachable, and g++ later reports it
|
|
171
|
+
// "not declared in this scope". Attach their body identifiers to
|
|
172
|
+
// __top_level__ since the callbacks execute from the top-level entry path.
|
|
173
|
+
for (const rc of (program.registeredCallbacks ?? [])) {
|
|
174
|
+
for (const id of collectExpressionIdentifiers(rc.callbackIR)) {
|
|
175
|
+
topLevelDependencies.add(id);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
162
178
|
nodes.set("__top_level__", {
|
|
163
179
|
name: "__top_level__",
|
|
164
180
|
kind: "variable",
|