@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.9
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/dist/api/config.d.ts +32 -0
- 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 +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -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/platform-strategy.d.ts +104 -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 +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -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.js +125 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- 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 +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- 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/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- 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.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- 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 +14 -1
- 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 +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- 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 +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- 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/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +22 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +68 -3
- package/package.json +20 -5
- 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/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -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
|
}
|
|
@@ -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
|
@@ -443,14 +443,14 @@ export function buildProgramIR(fileName, sourceText, boardPackage, prebuiltClass
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
}
|
|
446
|
-
// Track HAL instances imported from board packages and framework
|
|
447
|
-
// 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.
|
|
448
448
|
// The `@typecad/board` virtual import resolves to the board package, so it
|
|
449
449
|
// also registers pin aliases (LED, etc.). Case-insensitive on the scope.
|
|
450
450
|
const lowerSpecifier = moduleSpecifier.toLowerCase();
|
|
451
451
|
const isHALSource = lowerSpecifier.startsWith('@typecad/board-')
|
|
452
|
-
|| lowerSpecifier === '@typecad/
|
|
453
|
-
|| lowerSpecifier
|
|
452
|
+
|| lowerSpecifier === '@typecad/board'
|
|
453
|
+
|| /^@typecad\/framework-[a-z0-9-]+\/(arduino|hal|gpio)$/.test(lowerSpecifier);
|
|
454
454
|
// UI authoring namespace: `import { ui } from "@typecad/ui"`. The `ui`
|
|
455
455
|
// value is a compile-time construct (its calls are intercepted by
|
|
456
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",
|
|
@@ -12,6 +12,7 @@ import { tryLowerRegisterRead } from "./transformers/register-assignment.js";
|
|
|
12
12
|
import { tryLowerArrayAndStringMethods } from "./transformers/array-methods.js";
|
|
13
13
|
import { collectReturns, inferExprCppType, typeNodeToCppType } from "./type-resolution.js";
|
|
14
14
|
import { parseCppType, elementOf, renderCppType, bareType, parsedIsPointer, parsedIsVector, parsedIsMap, parsedIsSet, parsedIsTuple, parsedIsStdString, parsedElementString, parsedBareString, isContainer } from "../api/shared/cpp-type-ir.js";
|
|
15
|
+
import { hasSafetyHook, requireSafetyHook } from "../safety-hook.js";
|
|
15
16
|
function resolveExprCppType(expr) {
|
|
16
17
|
if (ts.isNonNullExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
17
18
|
return resolveExprCppType(expr.expression);
|
|
@@ -184,6 +185,143 @@ function resolveCallReturnTypeForNullGuard(call, sourceText) {
|
|
|
184
185
|
}
|
|
185
186
|
return undefined;
|
|
186
187
|
}
|
|
188
|
+
/** The SafeReadResult/SafeWriteResult chain methods whose lambda args must
|
|
189
|
+
* render inline (preserving closure captures). Detected on the TS chain to
|
|
190
|
+
* decide whether to take the structured-receiver path. */
|
|
191
|
+
const SAFETY_RESULT_CHAIN_METHODS = new Set(["ok", "fail", "fault", "always"]);
|
|
192
|
+
/** Detect `safe.<read|write>(...).<ok|fail|fault|always>(handler)...` chains
|
|
193
|
+
* and build them as nested method-call IR nodes with a STRUCTURED receiverExpr
|
|
194
|
+
* (not a flattened callee string). This preserves inner lambda args so the
|
|
195
|
+
* emit pipeline can render them inline via renderLambda.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when the expression is not a safe.* call chained with at least
|
|
198
|
+
* one result method (terminal safe.read/safe.write are handled by the
|
|
199
|
+
* statement/var-decl interceptors; non-safe chains use the generic path). */
|
|
200
|
+
function tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars) {
|
|
201
|
+
if (!hasSafetyHook())
|
|
202
|
+
return null;
|
|
203
|
+
const chain = [];
|
|
204
|
+
let cur = expr;
|
|
205
|
+
while (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
|
|
206
|
+
chain.push({ methodName: cur.expression.name.text, callNode: cur });
|
|
207
|
+
cur = cur.expression.expression;
|
|
208
|
+
}
|
|
209
|
+
// After the loop, `cur` is the innermost receiver. For a safe.* chain, the
|
|
210
|
+
// last-pushed chain element is the safe.read/safe.write root (its
|
|
211
|
+
// callNode.expression.expression is the identifier `safe`). Identify it.
|
|
212
|
+
if (chain.length === 0)
|
|
213
|
+
return null;
|
|
214
|
+
const root = chain[chain.length - 1];
|
|
215
|
+
const rootCall = root.callNode;
|
|
216
|
+
// rootCall.expression is `safe.read` (PropertyAccess); rootCall.expression.expression
|
|
217
|
+
// must be the identifier `safe` for this to be a safe.* root.
|
|
218
|
+
// The root method is read/write; the links above it (chain[0..length-2]) must
|
|
219
|
+
// include at least one result-chain method (ok/fail/fault/always).
|
|
220
|
+
const resultLinks = chain.slice(0, chain.length - 1);
|
|
221
|
+
const hasChainMethod = resultLinks.some((link) => SAFETY_RESULT_CHAIN_METHODS.has(link.methodName));
|
|
222
|
+
if (!hasChainMethod)
|
|
223
|
+
return null;
|
|
224
|
+
// Resolve the safe.<method>(...) root to a hal-expr (the same way variables.ts
|
|
225
|
+
// does for `const r = safe.read(pin)`). This becomes the innermost receiver.
|
|
226
|
+
const rootPropAccess = rootCall.expression;
|
|
227
|
+
const safeMethod = rootPropAccess.name.text;
|
|
228
|
+
const argValues = rootCall.arguments.map((a) => {
|
|
229
|
+
if (ts.isNumericLiteral(a))
|
|
230
|
+
return Number(a.text);
|
|
231
|
+
if (ts.isStringLiteral(a))
|
|
232
|
+
return a.text;
|
|
233
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
234
|
+
return true;
|
|
235
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
236
|
+
return false;
|
|
237
|
+
if (ts.isIdentifier(a))
|
|
238
|
+
return a.text;
|
|
239
|
+
return a.getText();
|
|
240
|
+
});
|
|
241
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${safeMethod}`, argValues);
|
|
242
|
+
// If the hook couldn't resolve the pin (halInstances not yet populated for
|
|
243
|
+
// this identifier at this point in IR building), fall back to rendering the
|
|
244
|
+
// root verbatim. The chain structure (with inline lambdas) is still correct;
|
|
245
|
+
// only the root's HAL resolution is deferred. This happens for multi-link
|
|
246
|
+
// chains where the resolver runs before the pin's HAL instance is registered.
|
|
247
|
+
let receiver;
|
|
248
|
+
if (op) {
|
|
249
|
+
receiver = { kind: "hal-expr", operation: op };
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Best-effort: resolve the pin number directly from halInstances (the hook's
|
|
253
|
+
// resolvePinArg does the same lookup, but calling it directly here avoids the
|
|
254
|
+
// hook's early-return on unresolved pins).
|
|
255
|
+
const pinArg = argValues[0];
|
|
256
|
+
let pinNum;
|
|
257
|
+
if (typeof pinArg === "string") {
|
|
258
|
+
const inst = halInstances.get(pinArg);
|
|
259
|
+
if (inst) {
|
|
260
|
+
const pv = inst.fieldValues.get("_pin") ?? inst.fieldValues.get("pin");
|
|
261
|
+
if (pv !== undefined)
|
|
262
|
+
pinNum = Number(pv);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (typeof pinArg === "number") {
|
|
266
|
+
pinNum = pinArg;
|
|
267
|
+
}
|
|
268
|
+
if (pinNum !== undefined) {
|
|
269
|
+
// Build the op manually now that we have the pin.
|
|
270
|
+
const fallbackOp = safeMethod === "read"
|
|
271
|
+
? { operation: "safety.read_safe", pin: pinNum }
|
|
272
|
+
: { operation: "safety.write_verify", pin: pinNum, value: argValues[1] ?? 0 };
|
|
273
|
+
receiver = { kind: "hal-expr", operation: fallbackOp };
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Last resort: verbatim text. The chain renders but the root won't have
|
|
277
|
+
// resolved to __tc_safety::read_safe — acceptable degradation.
|
|
278
|
+
receiver = { kind: "raw", value: rootCall.getText() };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// The root renders as __tc_safety::read_safe(pin) — render it to text so the
|
|
282
|
+
// callee string (used for non-receiverExpr consumers like type inference) is
|
|
283
|
+
// correct.
|
|
284
|
+
const rootText = renderExprAsText(receiver);
|
|
285
|
+
// Build nested method-call nodes bottom-up over the RESULT links only
|
|
286
|
+
// (chain[0..length-2], excluding the root at chain[length-1]). chain[0] is the
|
|
287
|
+
// outermost call (e.g. .fail); we build from the innermost result link outward.
|
|
288
|
+
for (let i = resultLinks.length - 1; i >= 0; i--) {
|
|
289
|
+
const link = resultLinks[i];
|
|
290
|
+
// Render this link's args as IR (lambdas become lambda IR nodes that
|
|
291
|
+
// renderLambda handles at emit time).
|
|
292
|
+
const argIRs = link.callNode.arguments.map((a) => expressionToIR(a, sourceText, diagnostics, pointerVars));
|
|
293
|
+
// Set concrete param types on lambda args so renderLambda emits
|
|
294
|
+
// `const SafeReadResult& r` instead of `auto r`. Generic lambdas (auto
|
|
295
|
+
// params) require C++14; AVR's gnu++11 doesn't support them. The chain
|
|
296
|
+
// knows the result type from the root (read -> SafeReadResult,
|
|
297
|
+
// write -> SafeWriteResult).
|
|
298
|
+
const resultType = safeMethod === "read" ? "SafeReadResult" : "SafeWriteResult";
|
|
299
|
+
for (const argIR of argIRs) {
|
|
300
|
+
if (argIR.kind === "lambda" && argIR.params) {
|
|
301
|
+
for (const p of argIR.params) {
|
|
302
|
+
if (!p.cppType || p.cppType === "auto") {
|
|
303
|
+
p.cppType = `const ${resultType}&`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Build a callee string for backward-compat with consumers that read
|
|
309
|
+
// method-call.callee. The receiver text is rootText for the innermost link,
|
|
310
|
+
// or the prior receiver's rendered text for outer links.
|
|
311
|
+
const priorCallee = i === resultLinks.length - 1
|
|
312
|
+
? rootText
|
|
313
|
+
: renderExprAsText(receiver);
|
|
314
|
+
const callee = `${priorCallee}.${escapeCppKeyword(link.methodName, new Set())}`;
|
|
315
|
+
receiver = {
|
|
316
|
+
kind: "method-call",
|
|
317
|
+
callee,
|
|
318
|
+
args: argIRs,
|
|
319
|
+
methodName: link.methodName,
|
|
320
|
+
receiverExpr: receiver,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return receiver;
|
|
324
|
+
}
|
|
187
325
|
export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new Map()) {
|
|
188
326
|
function emitUnsupportedExpression(message) {
|
|
189
327
|
diagnostics.push(makeDiagnostic(sourceText, expr.pos, message, "error", "TS2CPP_UNSUPPORTED_EXPR"));
|
|
@@ -953,6 +1091,18 @@ export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new
|
|
|
953
1091
|
};
|
|
954
1092
|
}
|
|
955
1093
|
if (ts.isCallExpression(expr)) {
|
|
1094
|
+
// ---- safe.read/safe.write chained with .ok/.fail/.fault/.always ----
|
|
1095
|
+
// Method chains like `safe.read(pin).ok(r => {...}).fail(r => {...})` cannot
|
|
1096
|
+
// use the generic method-call builder because it flattens the receiver into
|
|
1097
|
+
// a callee STRING (expression-to-ir.ts ~line 1475), destroying inner lambda
|
|
1098
|
+
// args (they become `/* __lambda__ */` placeholders baked into the callee
|
|
1099
|
+
// text). This resolver detects a safe.* call followed by a chain of fluent
|
|
1100
|
+
// result methods and builds it bottom-up as nested method-call nodes with a
|
|
1101
|
+
// STRUCTURED receiverExpr, so the emit pipeline renders each lambda arg
|
|
1102
|
+
// inline via renderLambda (preserving closure captures).
|
|
1103
|
+
const safetyChain = tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars);
|
|
1104
|
+
if (safetyChain)
|
|
1105
|
+
return safetyChain;
|
|
956
1106
|
// ---- rawCppExpr() — compile-time C++ injection in expression context ----
|
|
957
1107
|
// Mirrors the statement-position rawCpp()/__EMIT__ path, but emits the raw
|
|
958
1108
|
// text as an expression (e.g. for IDF macros like WIFI_INIT_CONFIG_DEFAULT()
|
|
@@ -163,6 +163,7 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
163
163
|
const elementType = restParam.cppType.replace(/^(?:const\s+)?std::vector<(.+)>&?$/, "$1");
|
|
164
164
|
restParamFunctions.set(node.name.text, elementType);
|
|
165
165
|
}
|
|
166
|
+
const fnDecorators = extractAsilFromComments(node, sourceText);
|
|
166
167
|
return {
|
|
167
168
|
originalName: node.name.text,
|
|
168
169
|
isAsync,
|
|
@@ -174,8 +175,29 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
174
175
|
...(fnTypeParams && fnTypeParams.length > 0 ? { typeParameters: fnTypeParams } : {}),
|
|
175
176
|
...(isGenerator ? { isGenerator: true } : {}),
|
|
176
177
|
...(isExported ? { isExported: true } : {}),
|
|
178
|
+
...(fnDecorators && fnDecorators.length > 0 ? { decorators: fnDecorators } : {}),
|
|
177
179
|
};
|
|
178
180
|
}
|
|
181
|
+
/** Extract ASIL annotations from leading comments (e.g. `// @asilD`).
|
|
182
|
+
*
|
|
183
|
+
* Uses comment-based annotations instead of TypeScript decorators because
|
|
184
|
+
* TS decorators on function declarations produce TS1206 errors in the VS Code
|
|
185
|
+
* language server and the @typescript-eslint/parser (both reject decorators
|
|
186
|
+
* on functions — only class/method/property decorators are supported).
|
|
187
|
+
* Comment annotations are valid TypeScript in any context, any editor, and
|
|
188
|
+
* any toolchain — no false-positive errors. */
|
|
189
|
+
function extractAsilFromComments(node, sourceText) {
|
|
190
|
+
const comments = extractNodeComments(node, sourceText);
|
|
191
|
+
const names = [];
|
|
192
|
+
for (const comment of comments.leadingComments) {
|
|
193
|
+
// Match `// @asilD`, `// @asilC`, `// @asilB`, `// @asilA`, `// @asilQM`
|
|
194
|
+
const match = comment.match(/@(asil[DCBAQM])/i);
|
|
195
|
+
if (match) {
|
|
196
|
+
names.push(match[1]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return names.length > 0 ? names : undefined;
|
|
200
|
+
}
|
|
179
201
|
export function variableAsFunctionToIR(node, fileName, sourceText, diagnostics, functionReturnTypes, typeAliasNodes, boilerplates, pointerVars = new Map(), lowerStatementList) {
|
|
180
202
|
const functionExpressionDeclarations = node.declarationList.declarations.filter((declaration) => {
|
|
181
203
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
@@ -55,16 +55,16 @@ function prefixOperatorText(operator) {
|
|
|
55
55
|
* keeps `this` out of the emitted free function. Returns null if the method
|
|
56
56
|
* is not a recognized inlinable getter.
|
|
57
57
|
*/
|
|
58
|
-
function inlineThisGetterCall(methodName, pin) {
|
|
58
|
+
function inlineThisGetterCall(methodName, pin, strategy) {
|
|
59
59
|
switch (methodName) {
|
|
60
60
|
case "read":
|
|
61
|
-
return `digitalRead(${pin})`;
|
|
61
|
+
return strategy?.readDigitalPin?.(pin) ?? `digitalRead(${pin})`;
|
|
62
62
|
case "readAnalog":
|
|
63
|
-
return `analogRead(${pin})`;
|
|
63
|
+
return strategy?.readAnalogPin?.(pin) ?? `analogRead(${pin})`;
|
|
64
64
|
case "isHigh":
|
|
65
|
-
return `digitalRead(${pin})`;
|
|
65
|
+
return strategy?.readDigitalPin?.(pin) ?? `digitalRead(${pin})`;
|
|
66
66
|
case "isLow":
|
|
67
|
-
return `(!digitalRead(${pin}))`;
|
|
67
|
+
return strategy?.readDigitalPin ? `(!${strategy.readDigitalPin(pin)})` : `(!digitalRead(${pin}))`;
|
|
68
68
|
default:
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
@@ -200,7 +200,7 @@ export function resolveExpressionText(expr, instance, paramNames, callArgTexts,
|
|
|
200
200
|
const methodName = expr.expression.name.text;
|
|
201
201
|
const pin = instance.fieldValues.get("_pin") ?? instance.fieldValues.get("pin");
|
|
202
202
|
if (pin !== undefined) {
|
|
203
|
-
const inlined = inlineThisGetterCall(methodName, pin);
|
|
203
|
+
const inlined = inlineThisGetterCall(methodName, pin, getContext().activeStrategy);
|
|
204
204
|
if (inlined !== null)
|
|
205
205
|
return inlined;
|
|
206
206
|
}
|
|
@@ -633,6 +633,10 @@ export function resetHALResolver() {
|
|
|
633
633
|
floatVariables.clear();
|
|
634
634
|
getContext().callbackPlaceholderCounter = 0;
|
|
635
635
|
getContext().snprintfCounter = 0;
|
|
636
|
+
// Reset the BLE characteristic counter so each file's GATT table starts at
|
|
637
|
+
// index 0. Without this, a second transpile in one process would continue
|
|
638
|
+
// from the previous file's counter and emit offset indices.
|
|
639
|
+
getContext().bleCharCounter = 0;
|
|
636
640
|
}
|
|
637
641
|
/** Resolve a hal-expr IR node to its C++ text using the active strategy. */
|
|
638
642
|
export function setActiveStrategy(strategy) {
|
|
@@ -378,12 +378,16 @@ export function resolveHALReceiver(receiver) {
|
|
|
378
378
|
}
|
|
379
379
|
// Specialized handling for BLE factory chaining
|
|
380
380
|
// (Ble.server(name).characteristic(uuid,type,perms).onRead(handler)):
|
|
381
|
-
// server() creates a BleServer
|
|
382
|
-
// counter (
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
381
|
+
// server() creates a BleServer. The characteristic index is taken from a
|
|
382
|
+
// per-file counter (bleCharCounter on CompilationContext) that persists
|
|
383
|
+
// across separate server() calls so a multi-characteristic server — the
|
|
384
|
+
// common ble-demo pattern of one Ble.server(name).characteristic(...) per
|
|
385
|
+
// char — gets unique sequential indices (0,1,2,…) instead of every char
|
|
386
|
+
// landing on slot 0 and clobbering the previous on_read/on_write handler.
|
|
387
|
+
// characteristic() reads the current counter for the add_char call and
|
|
388
|
+
// posts-increments it so the next characteristic gets the next slot.
|
|
389
|
+
// onRead/onWrite/onNotify carry _lastChar (the slot characteristic() just
|
|
390
|
+
// reserved). The counter resets per file in hal-emitter.ts.
|
|
387
391
|
if (innerInstance.className === "BleClass" && methodName === "server" && receiver.arguments.length > 0) {
|
|
388
392
|
const nameText = httpUrlArgText(receiver.arguments[0]);
|
|
389
393
|
if (nameText) {
|
|
@@ -394,9 +398,12 @@ export function resolveHALReceiver(receiver) {
|
|
|
394
398
|
}
|
|
395
399
|
}
|
|
396
400
|
if (innerInstance.className === "BleServer" && methodName === "characteristic") {
|
|
397
|
-
// characteristic(uuid, type, perms) returns this (BleServer).
|
|
398
|
-
//
|
|
399
|
-
//
|
|
401
|
+
// characteristic(uuid, type, perms) returns this (BleServer). The
|
|
402
|
+
// characteristic index is assigned centrally in the bleAddChar plugin
|
|
403
|
+
// (hal-plugins.ts) from a per-file counter — see the note there for
|
|
404
|
+
// why this can't be done via instance fieldValues (resolveHALReceiver
|
|
405
|
+
// re-resolves the receiver for each chain level and would drop a
|
|
406
|
+
// value stamped here).
|
|
400
407
|
return innerInstance;
|
|
401
408
|
}
|
|
402
409
|
if (innerInstance.className === "BleServer" && methodName === "service") {
|