@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.6
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 +4 -4
- package/dist/api/shared/hal-op-ir.d.ts +10 -1
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +66 -0
- package/dist/create/index.d.ts +1 -1
- package/dist/create/index.js +1 -1
- package/dist/create/init-scaffold.js +26 -2
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +189 -9
- package/dist/emit/emitters/setup.js +115 -0
- package/dist/ir/adc-range-validation.js +26 -25
- package/dist/ir/expression-to-ir.js +43 -6
- package/dist/ir/hal/hal-plugins.js +10 -0
- package/dist/ir/interrupt-analysis.js +8 -3
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/ownership-analysis.js +19 -0
- 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 +34 -32
- package/dist/ir/pin-mode-validation.js +5 -0
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +16 -0
- package/dist/ir/program-analysis.js +113 -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/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.js +1 -0
- package/dist/ir/transformers/variables.js +46 -17
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +2 -2
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/validation-orchestrator.js +6 -6
- package/dist/licenses.d.ts +185 -0
- package/dist/licenses.js +963 -0
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/transpile.js +6 -5
- package/dist/types.d.ts +5 -1
- package/dist/utils/cli.js +44 -0
- package/package.json +5 -4
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
package/README.md
CHANGED
|
@@ -12,15 +12,15 @@ C++, and can chain compile, upload, and serial-monitor steps.
|
|
|
12
12
|
From a project root containing `cuttlefish.config.ts`:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npx cuttlefish build
|
|
16
|
-
npx cuttlefish build --compile --upload --port COM4
|
|
17
|
-
npx cuttlefish build --compile --upload --monitor --port COM4 --baud 115200
|
|
15
|
+
npx @typecad/cuttlefish build
|
|
16
|
+
npx @typecad/cuttlefish build --compile --upload --port COM4
|
|
17
|
+
npx @typecad/cuttlefish build --compile --upload --monitor --port COM4 --baud 115200
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Scaffold a starter project with the built-in wizard:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx cuttlefish create --board arduino:avr:uno --framework arduino
|
|
23
|
+
npx @typecad/cuttlefish create --board arduino:avr:uno --framework arduino
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Commands
|
|
@@ -267,6 +267,15 @@ export interface SpiCsHighOp {
|
|
|
267
267
|
port?: string;
|
|
268
268
|
pin: number;
|
|
269
269
|
}
|
|
270
|
+
export interface SpiReadBufferOp {
|
|
271
|
+
operation: "spi.read_buffer";
|
|
272
|
+
bus: string;
|
|
273
|
+
/** Number of bytes — numeric or runtime expression string */
|
|
274
|
+
count: number | string;
|
|
275
|
+
/** Buffer variable name, "__HAL_READ_BUF__" placeholder (rewritten to the
|
|
276
|
+
* caller's variable by the variable-init transformer), or "__DISCARD__" */
|
|
277
|
+
buffer: string;
|
|
278
|
+
}
|
|
270
279
|
export interface UartBeginOp {
|
|
271
280
|
operation: "uart.begin";
|
|
272
281
|
port: string;
|
|
@@ -385,7 +394,7 @@ export interface RawCppOp {
|
|
|
385
394
|
* operations. Each node carries a semantic `operation` tag and typed
|
|
386
395
|
* arguments. Framework strategies translate these into concrete C++.
|
|
387
396
|
*/
|
|
388
|
-
export type HALOpIR = GpioWriteOp | GpioReadOp | GpioToggleOp | GpioSetModeOp | PwmWriteOp | PwmGetFrequencyOp | PwmGetResolutionOp | AdcReadOp | AdcGetResolutionOp | AdcSetReferenceOp | AdcGetReferenceOp | AdcReadVoltageOp | DacWriteOp | InterruptAttachOp | InterruptDetachOp | TonePlayOp | ToneStopOp | TimingDelayOp | TimingDelayMicrosecondsOp | TimingMillisOp | TimingMicrosOp | TimingFreeHeapOp | TimingSetIntervalOp | TimingSetTimeoutOp | TimingClearIntervalOp | TimingClearTimeoutOp | PowerDeepSleepOp | PowerLightSleepOp | PowerSetCpuFrequencyOp | I2cBeginOp | I2cEndOp | I2cSetClockOp | I2cBeginTransmissionOp | I2cWriteOp | I2cWriteBytesOp | I2cWriteBufferOp | I2cReadBufferOp | I2cEndTransmissionOp | I2cRequestFromOp | I2cAvailableOp | I2cReadOp | I2cRecoverOp | SpiBeginOp | SpiEndOp | SpiTransferOp | SpibeginTransactionOp | SpiEndTransactionOp | SpiSetModeOp | SpiSetBitOrderOp | SpiCsLowOp | SpiCsHighOp | UartBeginOp | UartEndOp | UartPrintOp | UartPrintlnOp | UartPrintfOp | UartWriteOp | UartReadOp | UartPeekOp | UartAvailableOp | UartFlushOp | PulseInOp | PulseInLongOp | ShiftOutOp | ShiftInOp | BoardResolveOp | WdtEnableOp | WdtResetOp | WdtDisableOp | SnprintfEmitOp | RawCppOp | DisplayHALOp;
|
|
397
|
+
export type HALOpIR = GpioWriteOp | GpioReadOp | GpioToggleOp | GpioSetModeOp | PwmWriteOp | PwmGetFrequencyOp | PwmGetResolutionOp | AdcReadOp | AdcGetResolutionOp | AdcSetReferenceOp | AdcGetReferenceOp | AdcReadVoltageOp | DacWriteOp | InterruptAttachOp | InterruptDetachOp | TonePlayOp | ToneStopOp | TimingDelayOp | TimingDelayMicrosecondsOp | TimingMillisOp | TimingMicrosOp | TimingFreeHeapOp | TimingSetIntervalOp | TimingSetTimeoutOp | TimingClearIntervalOp | TimingClearTimeoutOp | PowerDeepSleepOp | PowerLightSleepOp | PowerSetCpuFrequencyOp | I2cBeginOp | I2cEndOp | I2cSetClockOp | I2cBeginTransmissionOp | I2cWriteOp | I2cWriteBytesOp | I2cWriteBufferOp | I2cReadBufferOp | I2cEndTransmissionOp | I2cRequestFromOp | I2cAvailableOp | I2cReadOp | I2cRecoverOp | SpiBeginOp | SpiEndOp | SpiTransferOp | SpibeginTransactionOp | SpiEndTransactionOp | SpiSetModeOp | SpiSetBitOrderOp | SpiCsLowOp | SpiCsHighOp | SpiReadBufferOp | UartBeginOp | UartEndOp | UartPrintOp | UartPrintlnOp | UartPrintfOp | UartWriteOp | UartReadOp | UartPeekOp | UartAvailableOp | UartFlushOp | PulseInOp | PulseInLongOp | ShiftOutOp | ShiftInOp | BoardResolveOp | WdtEnableOp | WdtResetOp | WdtDisableOp | SnprintfEmitOp | RawCppOp | DisplayHALOp;
|
|
389
398
|
/**
|
|
390
399
|
* Helper type: extracts the operation string from a HALOpIR variant.
|
|
391
400
|
* Useful for type-safe switch statements in framework strategies.
|
|
@@ -26,6 +26,14 @@ export interface Diagnostic {
|
|
|
26
26
|
column?: number;
|
|
27
27
|
code?: string;
|
|
28
28
|
source?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Path (or basename) of the source file the diagnostic refers to. When set,
|
|
31
|
+
* it is rendered in the diagnostic header so the location is not just
|
|
32
|
+
* `(line,col)` but `path/to/file.ts (line,col)`. Emitters should prefer the
|
|
33
|
+
* real file path from the originating node's sourceSpan, falling back to a
|
|
34
|
+
* basename when the full path is unavailable.
|
|
35
|
+
*/
|
|
36
|
+
filePath?: string;
|
|
29
37
|
/** Text of the source line for display context. */
|
|
30
38
|
sourceLine?: string;
|
|
31
39
|
}
|
package/dist/cli-utils.d.ts
CHANGED
package/dist/cli-utils.js
CHANGED
|
@@ -50,6 +50,7 @@ export function printDiagnostics(diagnostics) {
|
|
|
50
50
|
for (const diagnostic of diagnostics) {
|
|
51
51
|
const position = diagnostic.line && diagnostic.column ? chalk.gray(`(${diagnostic.line},${diagnostic.column})`) : "";
|
|
52
52
|
const code = diagnostic.code ? chalk.gray(` [${diagnostic.code}]`) : "";
|
|
53
|
+
const file = 'filePath' in diagnostic && diagnostic.filePath ? chalk.cyan(diagnostic.filePath) : "";
|
|
53
54
|
let severityLabel;
|
|
54
55
|
let hintColor;
|
|
55
56
|
if (diagnostic.severity === "error") {
|
|
@@ -65,7 +66,8 @@ export function printDiagnostics(diagnostics) {
|
|
|
65
66
|
hintColor = chalk.cyan;
|
|
66
67
|
}
|
|
67
68
|
const location = position ? ` ${position}` : "";
|
|
68
|
-
const
|
|
69
|
+
const filePrefix = file ? `${file} ` : "";
|
|
70
|
+
const header = `${filePrefix}${severityLabel}${code}${location}: ${chalk.white(diagnostic.message)}`;
|
|
69
71
|
if (diagnostic.severity === "error") {
|
|
70
72
|
console.error(header);
|
|
71
73
|
}
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,8 @@ import { runExpectTests, assertTypeScriptInput, printDiagnostics, printMappedCom
|
|
|
19
19
|
import { runPreviewServer } from "./preview/server.js";
|
|
20
20
|
import * as ui from "./utils/ui.js";
|
|
21
21
|
import chalk from "chalk";
|
|
22
|
+
import { checkArduinoEnv } from "@typecad/arduino-cli";
|
|
23
|
+
import { runLicensesPresenter } from "./licenses.js";
|
|
22
24
|
function hasFatalDiagnostics(result) {
|
|
23
25
|
return result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
24
26
|
}
|
|
@@ -107,6 +109,62 @@ async function handleBoardAdd(options) {
|
|
|
107
109
|
}
|
|
108
110
|
console.log(generateFrameworkChecklist(spec));
|
|
109
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* `cuttlefish doctor` — verify arduino-cli is installed and the board's core
|
|
114
|
+
* (derived from the FQBN in cuttlefish.config.ts) is present. Exits 0 if the
|
|
115
|
+
* environment is OK, non-zero otherwise. Reuses checkArduinoEnv so the
|
|
116
|
+
* detection logic is shared with the build/test gates.
|
|
117
|
+
*/
|
|
118
|
+
function runDoctor() {
|
|
119
|
+
ui.printHeader();
|
|
120
|
+
ui.printStep("Checking arduino-cli environment...");
|
|
121
|
+
const config = loadCuttlefishConfig(process.cwd());
|
|
122
|
+
const fqbn = config?.buildTarget;
|
|
123
|
+
const result = checkArduinoEnv(fqbn);
|
|
124
|
+
const check = result.check;
|
|
125
|
+
// arduino-cli presence line
|
|
126
|
+
if (check.arduinoCliInstalled) {
|
|
127
|
+
ui.printInfo(`arduino-cli .... ${check.arduinoCliVersion ?? "unknown"} ✓`);
|
|
128
|
+
}
|
|
129
|
+
else if (!result.ok && result.reason === "arduino-cli-not-found") {
|
|
130
|
+
ui.printError(`arduino-cli .... NOT FOUND on PATH`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
ui.printError(`arduino-cli .... found but unresponsive`);
|
|
134
|
+
}
|
|
135
|
+
// core presence line (only meaningful if we have an FQBN)
|
|
136
|
+
if (fqbn) {
|
|
137
|
+
if (check.requiredCore) {
|
|
138
|
+
const status = check.requiredCoreInstalled ? "installed ✓" : "NOT installed ✗";
|
|
139
|
+
const line = `${check.requiredCore} ....... ${status}`;
|
|
140
|
+
if (check.requiredCoreInstalled) {
|
|
141
|
+
ui.printInfo(line);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
ui.printError(line);
|
|
145
|
+
ui.printInfo(` → run: arduino-cli core install ${check.requiredCore}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
ui.printInfo("(no buildTarget in cuttlefish.config.ts — skipping core check)");
|
|
151
|
+
}
|
|
152
|
+
// Exit code
|
|
153
|
+
if (result.ok) {
|
|
154
|
+
ui.printSuccess("Environment OK");
|
|
155
|
+
return; // exitCode stays unset => 0
|
|
156
|
+
}
|
|
157
|
+
if (!result.ok) {
|
|
158
|
+
for (const line of result.messages)
|
|
159
|
+
ui.printInfo(line);
|
|
160
|
+
process.exitCode = 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* `cuttlefish licenses` dispatch — the presenter lives in licenses.ts so it is
|
|
165
|
+
* unit-testable without importing this binary entry module (cli.ts has a
|
|
166
|
+
* shebang and runs main() at import time). See runLicensesPresenter.
|
|
167
|
+
*/
|
|
110
168
|
async function main() {
|
|
111
169
|
try {
|
|
112
170
|
const options = parseCommandLine(process.argv);
|
|
@@ -154,6 +212,14 @@ async function main() {
|
|
|
154
212
|
}
|
|
155
213
|
return;
|
|
156
214
|
}
|
|
215
|
+
if (options.command === "doctor") {
|
|
216
|
+
runDoctor();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (options.command === "licenses") {
|
|
220
|
+
runLicensesPresenter(options.strict ?? false, options.all ?? false);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
157
223
|
// ── Handle build command — entry point comes from config ──────────
|
|
158
224
|
if (options.command === "build") {
|
|
159
225
|
const buildConfig = loadCuttlefishConfig(process.cwd());
|
package/dist/create/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
|
|
2
2
|
export type { KnownTarget, KnownBoard, ScaffoldProjectResult } from './init-scaffold.js';
|
|
3
|
-
export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateEslintConfig, } from './init-templates.js';
|
|
3
|
+
export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
|
|
4
4
|
export type { InitProjectOptions } from './init-templates.js';
|
|
5
5
|
export { runInitWizard } from './init-wizard.js';
|
|
6
6
|
export { scaffoldBoardPackages } from './board-codegen.js';
|
package/dist/create/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { scaffoldProject, normalizeProjectName, KNOWN_TARGETS, KNOWN_BOARDS, registerKnownTarget, printInitNextSteps } from './init-scaffold.js';
|
|
2
|
-
export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateEslintConfig, } from './init-templates.js';
|
|
2
|
+
export { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateEslintConfig, } from './init-templates.js';
|
|
3
3
|
export { runInitWizard } from './init-wizard.js';
|
|
4
4
|
// Board codegen tool (`cuttlefish board add`)
|
|
5
5
|
export { scaffoldBoardPackages } from './board-codegen.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateGitignore, generateBoardForwardingFile, generateEslintConfig, } from "./init-templates.js";
|
|
4
|
+
import { generateProjectPackageJson, generateProjectTsconfig, generateProjectConfig, generateProjectEnvDts, generateStarterSketch, generateStarterTest, generateStarterSim, generateGitignore, generateBoardForwardingFile, generateEslintConfig, } from "./init-templates.js";
|
|
5
5
|
import { generateEslintRules } from "./eslint-rules-template.js";
|
|
6
6
|
const _knownTargets = [
|
|
7
7
|
{
|
|
@@ -150,6 +150,24 @@ export function scaffoldProject(options, outDir) {
|
|
|
150
150
|
fs.writeFileSync(sketchPath, generateStarterSketch(options), 'utf-8');
|
|
151
151
|
createdFiles.push(sketchPath);
|
|
152
152
|
}
|
|
153
|
+
// Embedded projects get a starter hardware test (@typecad/expect / cuttlefish-test).
|
|
154
|
+
// Native projects have no serial/board path, so they get no test setup.
|
|
155
|
+
if (!options.isNative) {
|
|
156
|
+
const testsDir = path.join(resolvedOutDir, 'tests');
|
|
157
|
+
fs.mkdirSync(testsDir, { recursive: true });
|
|
158
|
+
const testPath = path.join(testsDir, '01-basics.test.ts');
|
|
159
|
+
fs.writeFileSync(testPath, generateStarterTest(options), 'utf-8');
|
|
160
|
+
createdFiles.push(testPath);
|
|
161
|
+
// Host-side simulation (@typecad/simulator + vitest). sim/ is kept separate
|
|
162
|
+
// from tests/ so `vitest run sim/` never loads the @typecad/expect no-op
|
|
163
|
+
// stubs, and `cuttlefish-test` (which globs tests/) never tries to flash a
|
|
164
|
+
// simulator file as firmware.
|
|
165
|
+
const simDir = path.join(resolvedOutDir, 'sim');
|
|
166
|
+
fs.mkdirSync(simDir, { recursive: true });
|
|
167
|
+
const simPath = path.join(simDir, 'main.test.ts');
|
|
168
|
+
fs.writeFileSync(simPath, generateStarterSim(options), 'utf-8');
|
|
169
|
+
createdFiles.push(simPath);
|
|
170
|
+
}
|
|
153
171
|
return { createdFiles, outDir: resolvedOutDir, options };
|
|
154
172
|
}
|
|
155
173
|
export function printInitNextSteps(options, outDir) {
|
|
@@ -164,9 +182,15 @@ export function printInitNextSteps(options, outDir) {
|
|
|
164
182
|
if (!options.isNative) {
|
|
165
183
|
const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
|
|
166
184
|
console.log();
|
|
185
|
+
console.log(chalk.bold.white("To simulate without hardware:"));
|
|
186
|
+
console.log(` ${chalk.cyan("npm run simulate")}`);
|
|
187
|
+
console.log();
|
|
167
188
|
console.log(chalk.bold.white("To upload to your board:"));
|
|
168
189
|
console.log(` ${chalk.cyan("npm run upload")}`);
|
|
169
190
|
console.log();
|
|
170
|
-
console.log(chalk.
|
|
191
|
+
console.log(chalk.bold.white("To run hardware tests:"));
|
|
192
|
+
console.log(` ${chalk.cyan("npm run test:hw")}`);
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.dim(`Edit ${chalk.white("cuttlefish.config.ts")} to change the serial port from ${chalk.white(portHint)} to your port.`));
|
|
171
195
|
}
|
|
172
196
|
}
|
|
@@ -19,6 +19,8 @@ export declare function generateProjectTsconfig(options: InitProjectOptions): st
|
|
|
19
19
|
export declare function generateProjectConfig(options: InitProjectOptions): string;
|
|
20
20
|
export declare function generateProjectEnvDts(options: InitProjectOptions): string;
|
|
21
21
|
export declare function generateStarterSketch(options: InitProjectOptions): string;
|
|
22
|
+
export declare function generateStarterTest(_options: InitProjectOptions): string;
|
|
23
|
+
export declare function generateStarterSim(options: InitProjectOptions): string;
|
|
22
24
|
export declare function generateBoardForwardingFile(boardPackage: string): string;
|
|
23
25
|
export declare function generateGitignore(_options: InitProjectOptions): string;
|
|
24
26
|
export declare function generateEslintConfig(_options: InitProjectOptions): string;
|
|
@@ -2,11 +2,11 @@ import { LINT_RULES } from '../ir/feature-registry.js';
|
|
|
2
2
|
export function generateProjectPackageJson(options) {
|
|
3
3
|
const { projectName, frameworkPackage, boardPackage } = options;
|
|
4
4
|
const deps = {
|
|
5
|
-
"@typecad/cuttlefish": "^
|
|
6
|
-
[frameworkPackage]: "^
|
|
5
|
+
"@typecad/cuttlefish": "^1.0.0-alpha.3",
|
|
6
|
+
[frameworkPackage]: "^1.0.0-alpha.3",
|
|
7
7
|
};
|
|
8
8
|
if (boardPackage) {
|
|
9
|
-
deps[boardPackage] = "^
|
|
9
|
+
deps[boardPackage] = "^1.0.0-alpha.3";
|
|
10
10
|
}
|
|
11
11
|
const depsJson = Object.entries(deps)
|
|
12
12
|
.map(([k, v]) => ` "${k}": "${v}"`)
|
|
@@ -15,12 +15,27 @@ export function generateProjectPackageJson(options) {
|
|
|
15
15
|
// eslint-transpiler-rules plugin (which is plain JS, no dep). Without these
|
|
16
16
|
// devDependencies `npm run lint` fails to resolve the parser/plugin in a
|
|
17
17
|
// freshly created project. Versions mirror the repo demo's package.json.
|
|
18
|
-
const
|
|
18
|
+
const baseDevDeps = [
|
|
19
19
|
' "eslint": "^10.4.1"',
|
|
20
20
|
' "@typescript-eslint/parser": "^8.61.0"',
|
|
21
21
|
' "@typescript-eslint/eslint-plugin": "^8.61.0"',
|
|
22
|
-
]
|
|
22
|
+
];
|
|
23
|
+
// Developer-utility scripts shared by every target (native + embedded).
|
|
24
|
+
// None require hardware or extra dependencies:
|
|
25
|
+
// dev — auto-retranspile on save (transpile-only; append --compile
|
|
26
|
+
// to also compile on each change). The fast "does it
|
|
27
|
+
// typecheck" feedback loop.
|
|
28
|
+
// gen-decls — generate TypeScript .d.ts from C++ headers. File-argument:
|
|
29
|
+
// `npm run gen-decls -- lib/foo.h` or `-- --all lib/`.
|
|
30
|
+
// gen-libdefs — generate library-definition stubs from a TS file's imports.
|
|
31
|
+
// File-argument: `npm run gen-libdefs -- src/main.ts`.
|
|
32
|
+
const devScripts = [
|
|
33
|
+
'"dev": "cuttlefish build --watch"',
|
|
34
|
+
'"gen-decls": "cuttlefish gen-decls"',
|
|
35
|
+
'"gen-libdefs": "cuttlefish gen-libdefs"',
|
|
36
|
+
];
|
|
23
37
|
if (options.isNative) {
|
|
38
|
+
const devDepsJson = baseDevDeps.join(',\n');
|
|
24
39
|
return `{
|
|
25
40
|
"name": "${projectName}",
|
|
26
41
|
"version": "1.0.0",
|
|
@@ -28,7 +43,8 @@ export function generateProjectPackageJson(options) {
|
|
|
28
43
|
"scripts": {
|
|
29
44
|
"build": "cuttlefish build",
|
|
30
45
|
"compile": "cuttlefish build --compile",
|
|
31
|
-
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/"
|
|
46
|
+
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/",
|
|
47
|
+
${devScripts.join(',\n ')}
|
|
32
48
|
},
|
|
33
49
|
"dependencies": {
|
|
34
50
|
${depsJson}
|
|
@@ -39,6 +55,19 @@ ${devDepsJson}
|
|
|
39
55
|
}
|
|
40
56
|
`;
|
|
41
57
|
}
|
|
58
|
+
// Embedded projects get two host-side testing tiers:
|
|
59
|
+
// - @typecad/expect: hardware tests run on the board via cuttlefish-test
|
|
60
|
+
// (`npm run test:hw`), scoped to tests/**/*.test.ts.
|
|
61
|
+
// - @typecad/simulator + vitest: simulate the board in Node (`npm run
|
|
62
|
+
// simulate`), scoped to sim/**/*.test.ts so vitest never collides with the
|
|
63
|
+
// @typecad/expect no-op stubs under tests/.
|
|
64
|
+
// Versions mirror the workspace's published releases / root devDeps.
|
|
65
|
+
const devDepsJson = [
|
|
66
|
+
...baseDevDeps,
|
|
67
|
+
' "@typecad/expect": "^1.0.0-alpha.3"',
|
|
68
|
+
' "@typecad/simulator": "^1.0.0-alpha.3"',
|
|
69
|
+
' "vitest": "^4.0.18"',
|
|
70
|
+
].join(',\n');
|
|
42
71
|
return `{
|
|
43
72
|
"name": "${projectName}",
|
|
44
73
|
"version": "1.0.0",
|
|
@@ -48,7 +77,10 @@ ${devDepsJson}
|
|
|
48
77
|
"compile": "cuttlefish build --compile",
|
|
49
78
|
"upload": "cuttlefish build --compile --upload",
|
|
50
79
|
"monitor": "cuttlefish build --compile --upload --monitor",
|
|
51
|
-
"
|
|
80
|
+
"test:hw": "npm exec -- cuttlefish-test",
|
|
81
|
+
"simulate": "vitest run sim/",
|
|
82
|
+
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/",
|
|
83
|
+
${devScripts.join(',\n ')}
|
|
52
84
|
},
|
|
53
85
|
"dependencies": {
|
|
54
86
|
${depsJson}
|
|
@@ -85,9 +117,10 @@ export function generateProjectTsconfig(options) {
|
|
|
85
117
|
"noEmit": true,
|
|
86
118
|
"resolveJsonModule": true,
|
|
87
119
|
"allowArbitraryExtensions": true,
|
|
120
|
+
"allowImportingTsExtensions": true,
|
|
88
121
|
"rootDirs": ["src", "types"]${paths}
|
|
89
122
|
},
|
|
90
|
-
"include": ["src/**/*.ts", "types/**/*.ts", "cuttlefish.config.ts"${options.boardPackage ? ', ".cuttlefish/cuttlefish-env.d.ts"' : ''}]
|
|
123
|
+
"include": ["src/**/*.ts", "types/**/*.ts", "cuttlefish.config.ts"${options.boardPackage ? ', ".cuttlefish/cuttlefish-env.d.ts"' : ''}${options.isNative ? '' : ', "sim/**/*.ts"'}]
|
|
91
124
|
}
|
|
92
125
|
`;
|
|
93
126
|
}
|
|
@@ -127,6 +160,11 @@ export default config;
|
|
|
127
160
|
: '';
|
|
128
161
|
const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
|
|
129
162
|
const baudLine = options.baudRate ? `\n\n // Console polyfill configuration\n console: {\n baudRate: ${options.baudRate},\n // Serial port for upload/monitor. Override with --port on the CLI.\n port: '${portHint}',\n },` : '';
|
|
163
|
+
// Hardware test runner configuration — used by \`npm run test:hw\` (cuttlefish-test,
|
|
164
|
+
// provided by @typecad/expect). It transpiles each tests/**/*.test.ts file,
|
|
165
|
+
// flashes it to the board, and evaluates the assertions over serial.
|
|
166
|
+
const resolvedBaud = options.baudRate ?? 115200;
|
|
167
|
+
const testLine = `\n\n // Hardware test runner (@typecad/expect / \`npm run test:hw\`)\n test: {\n // Serial port for the test board. Override with --port on the CLI or the\n // CUTTLEFISH_PORT env var (e.g. CUTTLEFISH_PORT=/dev/ttyUSB0 npm run test:hw).\n port: '${portHint}',\n baudRate: ${resolvedBaud},\n timeout: 30000,\n include: ['tests/**/*.test.ts'],\n },`;
|
|
130
168
|
return `// ---------------------------------------------------------------------------
|
|
131
169
|
// cuttlefish.config.ts — Project configuration
|
|
132
170
|
//
|
|
@@ -155,7 +193,7 @@ const config: CuttlefishConfig = {
|
|
|
155
193
|
// Toolchain configuration
|
|
156
194
|
toolchain: {
|
|
157
195
|
type: '${resolvedToolchain}',
|
|
158
|
-
},${baudLine}
|
|
196
|
+
},${baudLine}${testLine}
|
|
159
197
|
};
|
|
160
198
|
|
|
161
199
|
export default config;
|
|
@@ -286,6 +324,148 @@ while (true) {
|
|
|
286
324
|
}
|
|
287
325
|
`;
|
|
288
326
|
}
|
|
327
|
+
export function generateStarterTest(_options) {
|
|
328
|
+
return `// ---------------------------------------------------------------------------
|
|
329
|
+
// Hardware test — Basics
|
|
330
|
+
//
|
|
331
|
+
// Runs on the board via \`npm run test:hw\` (cuttlefish-test). Each test file is
|
|
332
|
+
// transpiled, flashed to the board, and its assertions are evaluated on the host
|
|
333
|
+
// over serial. Change the serial port in cuttlefish.config.ts (the \`test.port\`
|
|
334
|
+
// field) or override it with the CUTTLEFISH_PORT env var.
|
|
335
|
+
//
|
|
336
|
+
// API: describe(...).it(...).expect(value).<matcher>() chains. Import pin
|
|
337
|
+
// objects from '@typecad/board' to assert on real hardware I/O. Every file ends
|
|
338
|
+
// with done().
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
import { describe, done } from '@typecad/expect';
|
|
342
|
+
|
|
343
|
+
describe("Basics")
|
|
344
|
+
.it("adds two numbers")
|
|
345
|
+
.expect(
|
|
346
|
+
(() => {
|
|
347
|
+
const a = 1;
|
|
348
|
+
let b = 2;
|
|
349
|
+
return a + b;
|
|
350
|
+
})
|
|
351
|
+
).toBe(3)
|
|
352
|
+
.it("multiplies two numbers")
|
|
353
|
+
.expect(
|
|
354
|
+
(() => {
|
|
355
|
+
let a = 3;
|
|
356
|
+
let b = 4;
|
|
357
|
+
return a * b;
|
|
358
|
+
})
|
|
359
|
+
).toBe(12)
|
|
360
|
+
.it("reads an array element")
|
|
361
|
+
.expect(
|
|
362
|
+
(() => {
|
|
363
|
+
const data = new Uint8Array([0xAA, 0x10, 0x20]);
|
|
364
|
+
return data[1];
|
|
365
|
+
})
|
|
366
|
+
).toBe(0x10)
|
|
367
|
+
.it("clamps a value to a range")
|
|
368
|
+
.expect(
|
|
369
|
+
(() => {
|
|
370
|
+
const value = 2000;
|
|
371
|
+
return Math.max(0, Math.min(1023, value));
|
|
372
|
+
})
|
|
373
|
+
).toBe(1023);
|
|
374
|
+
|
|
375
|
+
done();
|
|
376
|
+
`;
|
|
377
|
+
}
|
|
378
|
+
export function generateStarterSim(options) {
|
|
379
|
+
const boardType = options.targetId;
|
|
380
|
+
return `// ---------------------------------------------------------------------------
|
|
381
|
+
// Hardware simulation — Button + LED
|
|
382
|
+
//
|
|
383
|
+
// Runs entirely on your computer with \`npm run simulate\` (vitest + the
|
|
384
|
+
// @typecad/simulator package). No board, serial port, or arduino-cli required.
|
|
385
|
+
// The simulator mirrors the pins/peripherals of your ${options.targetDisplayName}
|
|
386
|
+
// (${boardType}); you inject fake inputs and assert on the outputs in Node.
|
|
387
|
+
//
|
|
388
|
+
// This is the fast tier — iterate on logic here, then confirm on real hardware
|
|
389
|
+
// with \`npm run test:hw\` (which flashes tests/ to the board).
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
|
|
392
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
393
|
+
import {
|
|
394
|
+
createSimBoard,
|
|
395
|
+
type SimBoard,
|
|
396
|
+
type SimDigitalPin,
|
|
397
|
+
} from "@typecad/simulator";
|
|
398
|
+
|
|
399
|
+
// ===========================================================================
|
|
400
|
+
// FIRMWARE LOGIC
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Factor your firmware into a function that takes the simulated pins as
|
|
403
|
+
// arguments. In a real project this same logic runs on the board against real
|
|
404
|
+
// pins — here it runs against the sim board so you can test it without hardware.
|
|
405
|
+
// ===========================================================================
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Reads a button and reflects its state on an LED.
|
|
409
|
+
*
|
|
410
|
+
* To use your own logic: replace the body of this function with whatever your
|
|
411
|
+
* firmware does (read a sensor, drive a motor, print to serial, ...). As long
|
|
412
|
+
* as it only touches pins you pass in, the simulator can exercise it.
|
|
413
|
+
*/
|
|
414
|
+
function reflectButtonOnLed(button: SimDigitalPin, led: SimDigitalPin): void {
|
|
415
|
+
// The button pin is pulled HIGH (1) at rest and reads LOW (0) when pressed.
|
|
416
|
+
if (button.isLow()) {
|
|
417
|
+
led.high();
|
|
418
|
+
} else {
|
|
419
|
+
led.low();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// ===========================================================================
|
|
424
|
+
// TEST BENCH
|
|
425
|
+
// ---------------------------------------------------------------------------
|
|
426
|
+
// \`createSimBoard\` builds an in-memory version of your board. The pin numbers
|
|
427
|
+
// below match the physical pinout. Add the pins/peripherals your firmware uses:
|
|
428
|
+
// board.digital(n), board.analog(n), board.pwm(n), board.serial(n),
|
|
429
|
+
// board.i2c(n), board.spi(n), board.interrupt(n).
|
|
430
|
+
// ===========================================================================
|
|
431
|
+
|
|
432
|
+
function setupSim(): { board: SimBoard; button: SimDigitalPin; led: SimDigitalPin } {
|
|
433
|
+
// boardType mirrors the target chosen with \`cuttlefish create\`.
|
|
434
|
+
const board = createSimBoard({ boardType: "${boardType}" });
|
|
435
|
+
|
|
436
|
+
const button = board.digital(2).asInputPullUp(); // button on pin 2 (INPUT_PULLUP)
|
|
437
|
+
const led = board.digital(13).asOutput(false); // LED on pin 13
|
|
438
|
+
|
|
439
|
+
return { board, button, led };
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
describe("Button + LED (simulator)", () => {
|
|
443
|
+
beforeEach(() => {
|
|
444
|
+
// A fresh board per test keeps state isolated. For a long-running sim you
|
|
445
|
+
// can call board.reset() between cycles instead.
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it("keeps the LED off while the button is released", () => {
|
|
449
|
+
const { button, led } = setupSim();
|
|
450
|
+
|
|
451
|
+
// Button at rest: INPUT_PULLUP reads HIGH.
|
|
452
|
+
reflectButtonOnLed(button, led);
|
|
453
|
+
|
|
454
|
+
expect(led.getBitValue()).toBe(0);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it("turns the LED on while the button is pressed", () => {
|
|
458
|
+
const { button, led } = setupSim();
|
|
459
|
+
|
|
460
|
+
// Simulate a press: drive the button pin LOW.
|
|
461
|
+
button.injectValue(0);
|
|
462
|
+
reflectButtonOnLed(button, led);
|
|
463
|
+
|
|
464
|
+
expect(led.getBitValue()).toBe(1);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
`;
|
|
468
|
+
}
|
|
289
469
|
export function generateBoardForwardingFile(boardPackage) {
|
|
290
470
|
return `// ---------------------------------------------------------------------------
|
|
291
471
|
// .cuttlefish/board.ts — Dynamically generated forwarding board package
|
|
@@ -31,6 +31,80 @@ function filterShimBlock(lines, startMarker, endMarker) {
|
|
|
31
31
|
}
|
|
32
32
|
return filtered;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Detect whether a program uses the watchdog timer. The HAL resolver lowers
|
|
36
|
+
* `WDT.enable/reset/disable` calls to structured `wdt.*` hal-ops, which the
|
|
37
|
+
* setup emitter then renders as bare `wdt_enable/wdt_reset/wdt_disable` calls
|
|
38
|
+
* (or constant-folded `wdt_enable(WDTO_*)` macros). All of those require
|
|
39
|
+
* `<avr/wdt.h>`, so the include is gated on this check instead of being forced
|
|
40
|
+
* into every AVR program.
|
|
41
|
+
*
|
|
42
|
+
* `programAnalysis.usesWDT` is NOT used here: it scans `WDT.`-prefixed
|
|
43
|
+
* callees and raw-code hal-ops, but the structured `wdt.*` ops carry a typed
|
|
44
|
+
* operation name with no raw code, so the analysis misses them.
|
|
45
|
+
*
|
|
46
|
+
* The recursion mirrors `collectStatementIdentifiers` (identifier-collector.ts)
|
|
47
|
+
* — the canonical IR walker — so every compound statement shape is covered.
|
|
48
|
+
*/
|
|
49
|
+
function programUsesWdt(program) {
|
|
50
|
+
const visitStatement = (stmt) => {
|
|
51
|
+
if (stmt.kind === "hal-op") {
|
|
52
|
+
const opName = stmt.operation?.operation;
|
|
53
|
+
if (typeof opName === "string" && opName.startsWith("wdt."))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
switch (stmt.kind) {
|
|
57
|
+
case "block":
|
|
58
|
+
case "labeled":
|
|
59
|
+
return visit(stmt.body);
|
|
60
|
+
case "if":
|
|
61
|
+
return visit(stmt.thenBranch) || visit(stmt.elseBranch ?? []);
|
|
62
|
+
case "for":
|
|
63
|
+
return visit(stmt.body)
|
|
64
|
+
|| (stmt.initializer ? visitStatement(stmt.initializer) : false);
|
|
65
|
+
case "while":
|
|
66
|
+
case "do_while":
|
|
67
|
+
case "for_of":
|
|
68
|
+
case "for_in":
|
|
69
|
+
return visit(stmt.body);
|
|
70
|
+
case "switch":
|
|
71
|
+
return stmt.cases.some((c) => visit(c.body ?? []));
|
|
72
|
+
case "try":
|
|
73
|
+
return visit(stmt.tryBlock) || visit(stmt.catchBlock ?? []) || visit(stmt.finallyBlock ?? []);
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const visit = (statements) => {
|
|
79
|
+
if (!statements)
|
|
80
|
+
return false;
|
|
81
|
+
for (const stmt of statements) {
|
|
82
|
+
if (visitStatement(stmt))
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
if (visit(program.topLevelStatements))
|
|
88
|
+
return true;
|
|
89
|
+
for (const fn of program.functions) {
|
|
90
|
+
if (visit(fn.statements))
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
for (const cls of program.classes) {
|
|
94
|
+
for (const m of cls.methods)
|
|
95
|
+
if (visit(m.statements))
|
|
96
|
+
return true;
|
|
97
|
+
for (const g of cls.getters)
|
|
98
|
+
if (visit(g.statements))
|
|
99
|
+
return true;
|
|
100
|
+
for (const s of cls.setters)
|
|
101
|
+
if (visit(s.statements))
|
|
102
|
+
return true;
|
|
103
|
+
if (cls.constructor && visit(cls.constructor.statements))
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
34
108
|
/**
|
|
35
109
|
* Extract a `#ifndef MACRO ... #endif` include-guard block from a shim line
|
|
36
110
|
* list. Used to emit just the macro definition (e.g. CUTTLEFISH_UNDEFINED) in
|
|
@@ -118,6 +192,14 @@ export function buildEmitterContext(program, options) {
|
|
|
118
192
|
})();
|
|
119
193
|
const programAnalysis = analyzeProgram(program, strategy);
|
|
120
194
|
if (options.platformContext) {
|
|
195
|
+
// The UI runtime's per-frame tick calls millis() (injected by the emitter,
|
|
196
|
+
// not present in user source), so a mounted UI needs the native millis ISR
|
|
197
|
+
// even when usesNativeTiming is false. OR entryHasUI() into the flag the
|
|
198
|
+
// AVR strategy reads when gating native_millis, so framework-avr doesn't
|
|
199
|
+
// drop the Timer0 ISR from a UI program.
|
|
200
|
+
if (!programAnalysis.usesNativeTiming && entryHasUI()) {
|
|
201
|
+
programAnalysis.usesNativeTiming = true;
|
|
202
|
+
}
|
|
121
203
|
options.platformContext.analysis = programAnalysis;
|
|
122
204
|
if (!options.platformContext.architecture && program.boardConstants) {
|
|
123
205
|
options.platformContext.architecture = program.boardConstants.get("architecture");
|
|
@@ -223,6 +305,28 @@ export function buildEmitterContext(program, options) {
|
|
|
223
305
|
if (!programAnalysis.usesStrPtr) {
|
|
224
306
|
shimLines = filterShimBlock(shimLines, '#ifndef CUTTLEFISH_STR_BUF_SIZE', 'inline size_t (strlen)(const __tc_str_ptr& s) { return ::strlen(s.buf); }');
|
|
225
307
|
}
|
|
308
|
+
// framework-avr native peripheral driver shims. Each block carries a
|
|
309
|
+
// CUTTLEFISH_*_BEGIN/END marker pair; strip the ones the program doesn't
|
|
310
|
+
// use. framework-arduino's shimLines contain none of these markers, so
|
|
311
|
+
// these filters are no-ops there. The strategies also self-gate on the
|
|
312
|
+
// same flags; this is the defensive backstop (mirrors how usesWDT/etc.
|
|
313
|
+
// backstop the strategy-side gating above).
|
|
314
|
+
if (!programAnalysis.usesUart) {
|
|
315
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_BEGIN', '// CUTTLEFISH_UART_END');
|
|
316
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_UART_EXT_BEGIN', '// CUTTLEFISH_UART_EXT_END');
|
|
317
|
+
}
|
|
318
|
+
if (!programAnalysis.usesSPI) {
|
|
319
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SPI_BEGIN', '// CUTTLEFISH_SPI_END');
|
|
320
|
+
}
|
|
321
|
+
if (!programAnalysis.usesI2C) {
|
|
322
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TWI_BEGIN', '// CUTTLEFISH_TWI_END');
|
|
323
|
+
}
|
|
324
|
+
if (!programAnalysis.usesEEPROM) {
|
|
325
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_EEPROM_BEGIN', '// CUTTLEFISH_EEPROM_END');
|
|
326
|
+
}
|
|
327
|
+
if (!programAnalysis.usesTone) {
|
|
328
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TONE_BEGIN', '// CUTTLEFISH_TONE_END');
|
|
329
|
+
}
|
|
226
330
|
profileDiagnostics = [...strategy.profileDiagnostics(program, options.platformContext)];
|
|
227
331
|
}
|
|
228
332
|
for (const imported of program.imports) {
|
|
@@ -745,6 +849,17 @@ export function buildEmitterContext(program, options) {
|
|
|
745
849
|
if (stringEnumNames.size > 0) {
|
|
746
850
|
includes.push(strategy.cstringHeader());
|
|
747
851
|
}
|
|
852
|
+
// `<avr/wdt.h>` is only needed when the program actually uses the watchdog.
|
|
853
|
+
// The HAL resolver lowers WDT.enable/reset/disable to bare wdt_*() calls
|
|
854
|
+
// (or constant-folded wdt_enable(WDTO_*) macros), all of which require the
|
|
855
|
+
// header. Detect the structured `wdt.*` hal-ops directly here rather than
|
|
856
|
+
// relying on programAnalysis.usesWDT (which misses these ops — they carry
|
|
857
|
+
// a typed operation name, not raw code the analysis scans) or on a forced
|
|
858
|
+
// include in the AVR profile (which leaked the header into every AVR
|
|
859
|
+
// program, even ones that never touch the watchdog like `led.toggle()`).
|
|
860
|
+
if (programUsesWdt(program)) {
|
|
861
|
+
includes.push("<avr/wdt.h>");
|
|
862
|
+
}
|
|
748
863
|
// Placeholder defaults for fields that are computed later by other phases
|
|
749
864
|
const noopFixPointer = (c) => c;
|
|
750
865
|
// Demo #18 Finding B: collect the names of non-exported free functions that
|