@typecad/cuttlefish 1.0.0-alpha.3 → 1.0.0-alpha.7
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/display-adapter.d.ts +2 -1
- package/dist/api/shared/display-adapter.js +8 -1
- package/dist/api/shared/display-adapters/sdl.js +9 -2
- package/dist/api/shared/display-profile.d.ts +20 -3
- package/dist/api/shared/display-profile.js +21 -6
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +25 -0
- package/dist/api/shared/framework-manifest.d.ts +462 -0
- package/dist/api/shared/framework-manifest.js +149 -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 +28 -0
- package/dist/api/shared/hal-op-ir.d.ts +427 -1
- package/dist/api/shared/hal-op-ir.js +95 -1
- package/dist/api/shared/index.d.ts +11 -1
- package/dist/api/shared/index.js +14 -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 +9 -0
- package/dist/api/shared/promise-runtime.js +78 -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 +417 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +175 -4
- package/dist/config-loader.js +20 -1
- package/dist/config-schema.d.ts +36 -36
- package/dist/create/board-spec.d.ts +4 -4
- package/dist/create/index.d.ts +1 -1
- package/dist/create/index.js +1 -1
- package/dist/create/init-scaffold.d.ts +3 -0
- package/dist/create/init-scaffold.js +74 -2
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +217 -17
- package/dist/create/init-wizard.js +20 -1
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/emitter-context.d.ts +5 -0
- package/dist/emit/emitters/function-emitter-impl.js +69 -59
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +21 -11
- package/dist/emit/emitters/setup.js +155 -0
- package/dist/emit/emitters/top-level-prep.js +2 -0
- package/dist/emit/emitters/ui-emitter.js +23 -8
- package/dist/emit/expression-renderer.js +10 -1
- package/dist/emit/snprintf-helpers.js +8 -0
- package/dist/emit/statement-renderer.js +13 -0
- package/dist/emit/utils/async-state-machine.js +185 -116
- 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/ir/adc-range-validation.js +26 -25
- package/dist/ir/build-ir.js +5 -1
- package/dist/ir/expression-to-ir.js +57 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +40 -12
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +74 -0
- package/dist/ir/hal/hal-plugins.js +571 -0
- package/dist/ir/identifier-collector.js +18 -0
- package/dist/ir/interrupt-analysis.js +8 -3
- 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 +33 -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 +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 +39 -0
- package/dist/ir/program-analysis.js +192 -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.d.ts +6 -1
- package/dist/ir/timing-validation.js +51 -12
- package/dist/ir/transformers/expressions.js +58 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/variables.js +86 -19
- 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 +9 -7
- 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/idf-discovery.d.ts +7 -0
- package/dist/libdef/idf-discovery.js +59 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/licenses.d.ts +185 -0
- package/dist/licenses.js +963 -0
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +6 -2
- package/dist/stores/display-profile-store.d.ts +1 -0
- package/dist/stores/display-profile-store.js +1 -0
- package/dist/testing.d.ts +4 -2
- package/dist/testing.js +4 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +78 -32
- package/dist/types.d.ts +5 -1
- package/dist/ui-hook.d.ts +17 -1
- package/dist/utils/cli.js +71 -1
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +9 -4
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -13,12 +13,16 @@ export interface InitProjectOptions {
|
|
|
13
13
|
baudRate?: number;
|
|
14
14
|
includeSketch: boolean;
|
|
15
15
|
toolchainType?: string;
|
|
16
|
+
/** Extra frameworkData fields (e.g. `{ target: 'esp32s3' }` for framework-esp32). */
|
|
17
|
+
frameworkData?: Record<string, unknown>;
|
|
16
18
|
}
|
|
17
19
|
export declare function generateProjectPackageJson(options: InitProjectOptions): string;
|
|
18
20
|
export declare function generateProjectTsconfig(options: InitProjectOptions): string;
|
|
19
21
|
export declare function generateProjectConfig(options: InitProjectOptions): string;
|
|
20
22
|
export declare function generateProjectEnvDts(options: InitProjectOptions): string;
|
|
21
23
|
export declare function generateStarterSketch(options: InitProjectOptions): string;
|
|
24
|
+
export declare function generateStarterTest(_options: InitProjectOptions): string;
|
|
25
|
+
export declare function generateStarterSim(options: InitProjectOptions): string;
|
|
22
26
|
export declare function generateBoardForwardingFile(boardPackage: string): string;
|
|
23
27
|
export declare function generateGitignore(_options: InitProjectOptions): string;
|
|
24
28
|
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
|
}
|
|
@@ -117,8 +150,26 @@ export default config;
|
|
|
117
150
|
`;
|
|
118
151
|
}
|
|
119
152
|
const buildTarget = options.buildTarget;
|
|
120
|
-
const
|
|
121
|
-
|
|
153
|
+
const isEspIdf = options.frameworkPackage === '@typecad/framework-esp32'
|
|
154
|
+
|| options.framework === 'esp32'
|
|
155
|
+
|| options.toolchainType === 'idf';
|
|
156
|
+
const resolvedToolchain = options.toolchainType
|
|
157
|
+
?? (isEspIdf ? 'idf' : 'arduino-cli');
|
|
158
|
+
// framework-esp32 wants IDF chip ids in frameworkData (target + buildTarget),
|
|
159
|
+
// not Arduino FQBNs. Known IDF targets already set frameworkData.target;
|
|
160
|
+
// wizard/CLI may still pass an FQBN in buildTarget — normalize to the last segment.
|
|
161
|
+
let frameworkDataBlock = '';
|
|
162
|
+
if (isEspIdf) {
|
|
163
|
+
const idfTarget = options.frameworkData?.target
|
|
164
|
+
?? options.frameworkData?.buildTarget
|
|
165
|
+
?? (buildTarget?.includes(':') ? buildTarget.split(':').filter(Boolean).pop() : buildTarget)
|
|
166
|
+
?? options.architecture
|
|
167
|
+
?? 'esp32';
|
|
168
|
+
frameworkDataBlock = `\n // Framework data (IDF chip target)\n frameworkData: {\n target: '${idfTarget}',\n buildTarget: '${idfTarget}',\n },`;
|
|
169
|
+
}
|
|
170
|
+
else if (buildTarget) {
|
|
171
|
+
frameworkDataBlock = `\n // Framework data\n frameworkData: {\n buildTarget: '${buildTarget}',\n },`;
|
|
172
|
+
}
|
|
122
173
|
const mcuLine = options.mcu
|
|
123
174
|
? `\n // MCU package — provides silicon-level pin definitions\n mcu: '${options.mcu.startsWith('@') ? options.mcu : `@typecad/mcu-${options.mcu}`}',\n`
|
|
124
175
|
: '';
|
|
@@ -127,6 +178,11 @@ export default config;
|
|
|
127
178
|
: '';
|
|
128
179
|
const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
|
|
129
180
|
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 },` : '';
|
|
181
|
+
// Hardware test runner configuration — used by \`npm run test:hw\` (cuttlefish-test,
|
|
182
|
+
// provided by @typecad/expect). It transpiles each tests/**/*.test.ts file,
|
|
183
|
+
// flashes it to the board, and evaluates the assertions over serial.
|
|
184
|
+
const resolvedBaud = options.baudRate ?? 115200;
|
|
185
|
+
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
186
|
return `// ---------------------------------------------------------------------------
|
|
131
187
|
// cuttlefish.config.ts — Project configuration
|
|
132
188
|
//
|
|
@@ -143,7 +199,7 @@ const config: CuttlefishConfig = {
|
|
|
143
199
|
target: '${options.architecture}',${mcuLine}${boardLine}
|
|
144
200
|
|
|
145
201
|
// Framework package — controls code generation strategy
|
|
146
|
-
framework: '${options.frameworkPackage}',${
|
|
202
|
+
framework: '${options.frameworkPackage}',${frameworkDataBlock}
|
|
147
203
|
|
|
148
204
|
// Output / build options
|
|
149
205
|
output: {
|
|
@@ -155,7 +211,7 @@ const config: CuttlefishConfig = {
|
|
|
155
211
|
// Toolchain configuration
|
|
156
212
|
toolchain: {
|
|
157
213
|
type: '${resolvedToolchain}',
|
|
158
|
-
},${baudLine}
|
|
214
|
+
},${baudLine}${testLine}
|
|
159
215
|
};
|
|
160
216
|
|
|
161
217
|
export default config;
|
|
@@ -286,6 +342,148 @@ while (true) {
|
|
|
286
342
|
}
|
|
287
343
|
`;
|
|
288
344
|
}
|
|
345
|
+
export function generateStarterTest(_options) {
|
|
346
|
+
return `// ---------------------------------------------------------------------------
|
|
347
|
+
// Hardware test — Basics
|
|
348
|
+
//
|
|
349
|
+
// Runs on the board via \`npm run test:hw\` (cuttlefish-test). Each test file is
|
|
350
|
+
// transpiled, flashed to the board, and its assertions are evaluated on the host
|
|
351
|
+
// over serial. Change the serial port in cuttlefish.config.ts (the \`test.port\`
|
|
352
|
+
// field) or override it with the CUTTLEFISH_PORT env var.
|
|
353
|
+
//
|
|
354
|
+
// API: describe(...).it(...).expect(value).<matcher>() chains. Import pin
|
|
355
|
+
// objects from '@typecad/board' to assert on real hardware I/O. Every file ends
|
|
356
|
+
// with done().
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
import { describe, done } from '@typecad/expect';
|
|
360
|
+
|
|
361
|
+
describe("Basics")
|
|
362
|
+
.it("adds two numbers")
|
|
363
|
+
.expect(
|
|
364
|
+
(() => {
|
|
365
|
+
const a = 1;
|
|
366
|
+
let b = 2;
|
|
367
|
+
return a + b;
|
|
368
|
+
})
|
|
369
|
+
).toBe(3)
|
|
370
|
+
.it("multiplies two numbers")
|
|
371
|
+
.expect(
|
|
372
|
+
(() => {
|
|
373
|
+
let a = 3;
|
|
374
|
+
let b = 4;
|
|
375
|
+
return a * b;
|
|
376
|
+
})
|
|
377
|
+
).toBe(12)
|
|
378
|
+
.it("reads an array element")
|
|
379
|
+
.expect(
|
|
380
|
+
(() => {
|
|
381
|
+
const data = new Uint8Array([0xAA, 0x10, 0x20]);
|
|
382
|
+
return data[1];
|
|
383
|
+
})
|
|
384
|
+
).toBe(0x10)
|
|
385
|
+
.it("clamps a value to a range")
|
|
386
|
+
.expect(
|
|
387
|
+
(() => {
|
|
388
|
+
const value = 2000;
|
|
389
|
+
return Math.max(0, Math.min(1023, value));
|
|
390
|
+
})
|
|
391
|
+
).toBe(1023);
|
|
392
|
+
|
|
393
|
+
done();
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
export function generateStarterSim(options) {
|
|
397
|
+
const boardType = options.targetId;
|
|
398
|
+
return `// ---------------------------------------------------------------------------
|
|
399
|
+
// Hardware simulation — Button + LED
|
|
400
|
+
//
|
|
401
|
+
// Runs entirely on your computer with \`npm run simulate\` (vitest + the
|
|
402
|
+
// @typecad/simulator package). No board, serial port, or arduino-cli required.
|
|
403
|
+
// The simulator mirrors the pins/peripherals of your ${options.targetDisplayName}
|
|
404
|
+
// (${boardType}); you inject fake inputs and assert on the outputs in Node.
|
|
405
|
+
//
|
|
406
|
+
// This is the fast tier — iterate on logic here, then confirm on real hardware
|
|
407
|
+
// with \`npm run test:hw\` (which flashes tests/ to the board).
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
|
|
410
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
411
|
+
import {
|
|
412
|
+
createSimBoard,
|
|
413
|
+
type SimBoard,
|
|
414
|
+
type SimDigitalPin,
|
|
415
|
+
} from "@typecad/simulator";
|
|
416
|
+
|
|
417
|
+
// ===========================================================================
|
|
418
|
+
// FIRMWARE LOGIC
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Factor your firmware into a function that takes the simulated pins as
|
|
421
|
+
// arguments. In a real project this same logic runs on the board against real
|
|
422
|
+
// pins — here it runs against the sim board so you can test it without hardware.
|
|
423
|
+
// ===========================================================================
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Reads a button and reflects its state on an LED.
|
|
427
|
+
*
|
|
428
|
+
* To use your own logic: replace the body of this function with whatever your
|
|
429
|
+
* firmware does (read a sensor, drive a motor, print to serial, ...). As long
|
|
430
|
+
* as it only touches pins you pass in, the simulator can exercise it.
|
|
431
|
+
*/
|
|
432
|
+
function reflectButtonOnLed(button: SimDigitalPin, led: SimDigitalPin): void {
|
|
433
|
+
// The button pin is pulled HIGH (1) at rest and reads LOW (0) when pressed.
|
|
434
|
+
if (button.isLow()) {
|
|
435
|
+
led.high();
|
|
436
|
+
} else {
|
|
437
|
+
led.low();
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ===========================================================================
|
|
442
|
+
// TEST BENCH
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
// \`createSimBoard\` builds an in-memory version of your board. The pin numbers
|
|
445
|
+
// below match the physical pinout. Add the pins/peripherals your firmware uses:
|
|
446
|
+
// board.digital(n), board.analog(n), board.pwm(n), board.serial(n),
|
|
447
|
+
// board.i2c(n), board.spi(n), board.interrupt(n).
|
|
448
|
+
// ===========================================================================
|
|
449
|
+
|
|
450
|
+
function setupSim(): { board: SimBoard; button: SimDigitalPin; led: SimDigitalPin } {
|
|
451
|
+
// boardType mirrors the target chosen with \`cuttlefish create\`.
|
|
452
|
+
const board = createSimBoard({ boardType: "${boardType}" });
|
|
453
|
+
|
|
454
|
+
const button = board.digital(2).asInputPullUp(); // button on pin 2 (INPUT_PULLUP)
|
|
455
|
+
const led = board.digital(13).asOutput(false); // LED on pin 13
|
|
456
|
+
|
|
457
|
+
return { board, button, led };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
describe("Button + LED (simulator)", () => {
|
|
461
|
+
beforeEach(() => {
|
|
462
|
+
// A fresh board per test keeps state isolated. For a long-running sim you
|
|
463
|
+
// can call board.reset() between cycles instead.
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it("keeps the LED off while the button is released", () => {
|
|
467
|
+
const { button, led } = setupSim();
|
|
468
|
+
|
|
469
|
+
// Button at rest: INPUT_PULLUP reads HIGH.
|
|
470
|
+
reflectButtonOnLed(button, led);
|
|
471
|
+
|
|
472
|
+
expect(led.getBitValue()).toBe(0);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("turns the LED on while the button is pressed", () => {
|
|
476
|
+
const { button, led } = setupSim();
|
|
477
|
+
|
|
478
|
+
// Simulate a press: drive the button pin LOW.
|
|
479
|
+
button.injectValue(0);
|
|
480
|
+
reflectButtonOnLed(button, led);
|
|
481
|
+
|
|
482
|
+
expect(led.getBitValue()).toBe(1);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
`;
|
|
486
|
+
}
|
|
289
487
|
export function generateBoardForwardingFile(boardPackage) {
|
|
290
488
|
return `// ---------------------------------------------------------------------------
|
|
291
489
|
// .cuttlefish/board.ts — Dynamically generated forwarding board package
|
|
@@ -328,11 +526,13 @@ export default [
|
|
|
328
526
|
files: ["src/**/*.ts"],
|
|
329
527
|
languageOptions: {
|
|
330
528
|
parser: tsparser,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
529
|
+
// NOTE: do NOT set parserOptions.project here. None of the rules below
|
|
530
|
+
// (no-restricted-syntax, the cuttlefish/* AST rules, no-explicit-any,
|
|
531
|
+
// no-eval, ...) consume type information, so enabling type-aware linting
|
|
532
|
+
// only forces ESLint to build a full TS type-program per file — ~3.4s of
|
|
533
|
+
// pure overhead on small projects with zero change to what is detected.
|
|
534
|
+
// If a future rule needs types, scope project to that rule only via
|
|
535
|
+
// parserOptions on a dedicated config block, not globally.
|
|
336
536
|
},
|
|
337
537
|
plugins: {
|
|
338
538
|
"@typescript-eslint": tseslint,
|
|
@@ -94,6 +94,11 @@ export async function runInitWizard(partialOptions) {
|
|
|
94
94
|
if (target.architecture === 'avr') {
|
|
95
95
|
frameworkOptions.push({ label: "Bare-metal AVR (PORTB, etc.)", value: 'avr', pkg: '@typecad/framework-avr' });
|
|
96
96
|
}
|
|
97
|
+
// ESP32 family: offer the native ESP-IDF flavor alongside Arduino.
|
|
98
|
+
if (target.architecture === 'esp32' || target.architecture === 'esp32s3'
|
|
99
|
+
|| target.architecture === 'esp32c3' || target.architecture === 'esp32c6') {
|
|
100
|
+
frameworkOptions.push({ label: "ESP32 (native ESP-IDF)", value: 'esp32', pkg: '@typecad/framework-esp32' });
|
|
101
|
+
}
|
|
97
102
|
if (partialOptions?.framework) {
|
|
98
103
|
const match = frameworkOptions.find(f => f.value === partialOptions.framework);
|
|
99
104
|
framework = match?.value ?? partialOptions.framework;
|
|
@@ -141,6 +146,10 @@ export async function runInitWizard(partialOptions) {
|
|
|
141
146
|
else {
|
|
142
147
|
includeSketch = await promptConfirm(rl, "Create starter sketch?", true);
|
|
143
148
|
}
|
|
149
|
+
const isEspIdf = frameworkPackage === '@typecad/framework-esp32' || framework === 'esp32';
|
|
150
|
+
const idfTarget = target.frameworkData?.target
|
|
151
|
+
?? target.architecture
|
|
152
|
+
?? 'esp32';
|
|
144
153
|
return {
|
|
145
154
|
projectName,
|
|
146
155
|
targetId: target.id,
|
|
@@ -150,10 +159,20 @@ export async function runInitWizard(partialOptions) {
|
|
|
150
159
|
boardPackage: target.boardPackage,
|
|
151
160
|
frameworkPackage,
|
|
152
161
|
framework,
|
|
153
|
-
buildTarget: target.buildTarget,
|
|
162
|
+
buildTarget: isEspIdf ? idfTarget : target.buildTarget,
|
|
154
163
|
mcu: target.mcu,
|
|
155
164
|
baudRate,
|
|
156
165
|
includeSketch,
|
|
166
|
+
...(isEspIdf
|
|
167
|
+
? {
|
|
168
|
+
toolchainType: 'idf',
|
|
169
|
+
frameworkData: target.frameworkData?.target
|
|
170
|
+
? { ...target.frameworkData }
|
|
171
|
+
: { target: idfTarget, buildTarget: idfTarget },
|
|
172
|
+
}
|
|
173
|
+
: target.frameworkData
|
|
174
|
+
? { frameworkData: target.frameworkData }
|
|
175
|
+
: {}),
|
|
157
176
|
};
|
|
158
177
|
}
|
|
159
178
|
catch (err) {
|
package/dist/emit/cpp-emitter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { buildEmitterContext } from "./emitters/setup.js";
|
|
2
|
-
import { emitPreamble } from "./emitters/output-finalizer.js";
|
|
2
|
+
import { emitPreamble, emitAsyncTaskClasses, finalizeOutput } from "./emitters/output-finalizer.js";
|
|
3
3
|
import { runTopLevelPreprocessing } from "./emitters/top-level-prep.js";
|
|
4
4
|
import { synthesizeEntrypoints } from "./emitters/entrypoint-synthesizer.js";
|
|
5
5
|
import { emitTypeDeclarations } from "./emitters/type-decl-emitter.js";
|
|
@@ -7,7 +7,6 @@ import { emitNamespaces } from "./emitters/namespace-emitter.js";
|
|
|
7
7
|
import { emitClasses } from "./emitters/class-emitter.js";
|
|
8
8
|
import { emitPostClassDeclarations, emitCallbackFunctions, emitFunctions, emitFunctionForwardDeclarations } from "./emitters/function-emitter-impl.js";
|
|
9
9
|
import { emitUIRuntime } from "./emitters/ui-emitter.js";
|
|
10
|
-
import { finalizeOutput } from "./emitters/output-finalizer.js";
|
|
11
10
|
const globalEnumNames = new Set();
|
|
12
11
|
const globalLargeEnumNames = new Set();
|
|
13
12
|
export function registerAllEnumNames(enums) {
|
|
@@ -21,7 +20,7 @@ export function registerAllEnumNames(enums) {
|
|
|
21
20
|
export function emitCpp(program, options) {
|
|
22
21
|
// 1. Build shared emitter context (strategy, renderers, analysis, etc.)
|
|
23
22
|
const ctx = buildEmitterContext(program, options);
|
|
24
|
-
// 2. Emit preamble (includes, polyfills, async task classes
|
|
23
|
+
// 2. Emit preamble (includes, polyfills, shims) — async task classes deferred
|
|
25
24
|
emitPreamble(ctx);
|
|
26
25
|
// 2.5. Emit UI runtime (header structs + static tables) — entry file only,
|
|
27
26
|
// when a UI is mounted. File-scope, must precede any function using it.
|
|
@@ -32,6 +31,8 @@ export function emitCpp(program, options) {
|
|
|
32
31
|
synthesizeEntrypoints(ctx);
|
|
33
32
|
// 5. Emit type declarations (enums, type aliases, interfaces, top-level constants)
|
|
34
33
|
emitTypeDeclarations(ctx);
|
|
34
|
+
// 5.5. Async state machines — after globals so WIFI_SSID etc. are in scope
|
|
35
|
+
emitAsyncTaskClasses(ctx);
|
|
35
36
|
// 6. Emit namespaces
|
|
36
37
|
emitNamespaces(ctx);
|
|
37
38
|
// 6.5. Emit function forward declarations (split mode — must precede class definitions)
|
|
@@ -56,6 +56,11 @@ export interface CallbackFunction {
|
|
|
56
56
|
params: string[];
|
|
57
57
|
statements: StatementIR[];
|
|
58
58
|
debounceMs?: number;
|
|
59
|
+
/**
|
|
60
|
+
* True for GPIO / hardware ISR callbacks only. WiFi event handlers, timers,
|
|
61
|
+
* and setInterval use the same hoist path but must NOT get IRAM_ATTR.
|
|
62
|
+
*/
|
|
63
|
+
isInterruptHandler?: boolean;
|
|
59
64
|
/**
|
|
60
65
|
* Return type of the synthesized free function. Defaults to "void" (the
|
|
61
66
|
* historical ISR/HAL-callback case). Populated from a hoisted lambda's
|
|
@@ -128,7 +128,12 @@ export function emitCallbackFunctions(ctx) {
|
|
|
128
128
|
appendSourceLine(ctx, `const unsigned long ${callback.name}_debounce = ${callback.debounceMs};`);
|
|
129
129
|
appendSourceLine(ctx, "");
|
|
130
130
|
}
|
|
131
|
-
|
|
131
|
+
// IRAM_ATTR only on true ISR definitions. ESP-IDF's IRAM_ATTR uses
|
|
132
|
+
// __COUNTER__, so putting it on both forward decl and definition assigns
|
|
133
|
+
// conflicting .iram1.N sections (-Werror=attributes). Non-ISR callbacks
|
|
134
|
+
// (WiFi events, timers) must not be IRAM-placed either — they call printf.
|
|
135
|
+
const isrAttr = callback.isInterruptHandler ? (strategy.isrFunctionAttribute?.() ?? "") : "";
|
|
136
|
+
appendSourceLine(ctx, `${isrAttr}${renderCallbackSignature(callback)} {`);
|
|
132
137
|
if (callback.debounceMs !== undefined && callback.debounceMs > 0) {
|
|
133
138
|
appendSourceLine(ctx, ` volatile unsigned long now = ${strategy.currentTimeMillis()};`);
|
|
134
139
|
appendSourceLine(ctx, ` if (now - ${callback.name}_lastTime < ${callback.name}_debounce) return;`);
|
|
@@ -141,10 +146,11 @@ export function emitCallbackFunctions(ctx) {
|
|
|
141
146
|
appendSourceLine(ctx, "}");
|
|
142
147
|
appendSourceLine(ctx, "");
|
|
143
148
|
}
|
|
144
|
-
// Split-mode ISR callback forward declarations in header
|
|
149
|
+
// Split-mode ISR callback forward declarations in header (no IRAM_ATTR —
|
|
150
|
+
// attribute belongs on the definition only; see comment above).
|
|
145
151
|
if (effectiveEmitMode === "split") {
|
|
146
152
|
for (const callback of ctx.callbackFunctions) {
|
|
147
|
-
appendHeaderLine(ctx, `${
|
|
153
|
+
appendHeaderLine(ctx, `${renderCallbackSignature(callback)};`);
|
|
148
154
|
}
|
|
149
155
|
}
|
|
150
156
|
}
|
|
@@ -172,6 +178,10 @@ export function emitFunctions(ctx) {
|
|
|
172
178
|
// exported functions (which ride along with their definition below).
|
|
173
179
|
for (let fi = 0; fi < mappedFunctions.length; fi++) {
|
|
174
180
|
const fn = mappedFunctions[fi];
|
|
181
|
+
// Async functions become cooperative *Task state machines (emitAsyncTaskClasses).
|
|
182
|
+
// Emitting an empty stub here triggers -Wunused-function with no value.
|
|
183
|
+
if (fn.isAsync && ctx.hasAsyncRuntime)
|
|
184
|
+
continue;
|
|
175
185
|
const declarationParameterList = ctx.statementRenderer.renderParameters(fn.parameters, true);
|
|
176
186
|
const definitionParameterList = ctx.statementRenderer.renderParameters(fn.parameters, false);
|
|
177
187
|
const readonlyPrefix = fn.isReadonlyReturnType ? "const " : "";
|
|
@@ -246,65 +256,60 @@ export function emitFunctions(ctx) {
|
|
|
246
256
|
if (hasPromiseRuntime && fn.name === asyncDriverFn) {
|
|
247
257
|
appendSourceLine(ctx, " cuttlefish_pump_microtasks();");
|
|
248
258
|
}
|
|
249
|
-
if (fn.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
else {
|
|
253
|
-
if (fn.typeParameterConstraints) {
|
|
254
|
-
for (const [param, expr] of fn.typeParameterConstraints) {
|
|
255
|
-
appendSourceLine(ctx, ` static_assert(${expr}, "${param} constraint violated");`);
|
|
256
|
-
}
|
|
259
|
+
if (fn.typeParameterConstraints) {
|
|
260
|
+
for (const [param, expr] of fn.typeParameterConstraints) {
|
|
261
|
+
appendSourceLine(ctx, ` static_assert(${expr}, "${param} constraint violated");`);
|
|
257
262
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
appendSourceLine(ctx, ` uint32_t __tc_ui_now = (uint32_t)${strategy.currentTimeMillis()};`);
|
|
284
|
-
appendSourceLine(ctx, " static uint32_t __tc_ui_last_tick = __tc_ui_now;");
|
|
285
|
-
appendSourceLine(ctx, " uint32_t __tc_ui_delta = __tc_ui_now - __tc_ui_last_tick;");
|
|
286
|
-
appendSourceLine(ctx, " __tc_ui_last_tick = __tc_ui_now;");
|
|
287
|
-
appendSourceLine(ctx, " if (__tc_ui_delta > 250) __tc_ui_delta = 250;");
|
|
288
|
-
appendSourceLine(ctx, " ui_tick((uint16_t)__tc_ui_delta);");
|
|
289
|
-
if (loop) {
|
|
290
|
-
appendSourceLine(ctx, ` ${loop.postIteration}`);
|
|
291
|
-
appendSourceLine(ctx, ` }`);
|
|
292
|
-
}
|
|
263
|
+
}
|
|
264
|
+
const functionScope = createChildEmissionScope(topLevelScope, fn.parameters);
|
|
265
|
+
ctx.cArrayVarNames = ctx.fnCArrayVarNames.get(String(fi)) ?? new Set();
|
|
266
|
+
const isLoopDriver = fn.name === asyncDriverFn;
|
|
267
|
+
const lastStmt = fn.statements.length > 0 ? fn.statements[fn.statements.length - 1] : null;
|
|
268
|
+
const lastIsReturn = lastStmt?.kind === "return";
|
|
269
|
+
const stmtsToEmit = (isLoopDriver && lastIsReturn)
|
|
270
|
+
? fn.statements.slice(0, -1)
|
|
271
|
+
: fn.statements;
|
|
272
|
+
for (const statement of stmtsToEmit) {
|
|
273
|
+
appendRenderedStatement(ctx, statement, " ", functionScope);
|
|
274
|
+
}
|
|
275
|
+
// Drive the UI runtime each frame. Fires only in the driver function when
|
|
276
|
+
// a UI is mounted (entryHasUI). Uses a separate gate from the async pump
|
|
277
|
+
// so a pure-UI program (no async/timers) still animates. When the strategy
|
|
278
|
+
// provides hostEventLoop, the per-frame work runs inside a while loop so a
|
|
279
|
+
// host target (SDL) can pump events and present between ticks. Emitted
|
|
280
|
+
// AFTER the function's init statements (ui_init/display_init/touch_init)
|
|
281
|
+
// so initialization precedes the loop.
|
|
282
|
+
if (entryHasUI() && fn.name === asyncDriverFn) {
|
|
283
|
+
const loop = strategy.hostEventLoop?.();
|
|
284
|
+
if (loop) {
|
|
285
|
+
appendSourceLine(ctx, ` bool ${loop.flagName} = true;`);
|
|
286
|
+
appendSourceLine(ctx, ` while (${loop.continueCondition}) {`);
|
|
287
|
+
appendSourceLine(ctx, ` ${loop.preIteration}`);
|
|
293
288
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
289
|
+
appendSourceLine(ctx, ` uint32_t __tc_ui_now = (uint32_t)${strategy.currentTimeMillis()};`);
|
|
290
|
+
appendSourceLine(ctx, " static uint32_t __tc_ui_last_tick = __tc_ui_now;");
|
|
291
|
+
appendSourceLine(ctx, " uint32_t __tc_ui_delta = __tc_ui_now - __tc_ui_last_tick;");
|
|
292
|
+
appendSourceLine(ctx, " __tc_ui_last_tick = __tc_ui_now;");
|
|
293
|
+
appendSourceLine(ctx, " if (__tc_ui_delta > 250) __tc_ui_delta = 250;");
|
|
294
|
+
appendSourceLine(ctx, " ui_tick((uint16_t)__tc_ui_delta);");
|
|
295
|
+
if (loop) {
|
|
296
|
+
appendSourceLine(ctx, ` ${loop.postIteration}`);
|
|
297
|
+
appendSourceLine(ctx, ` }`);
|
|
303
298
|
}
|
|
304
|
-
|
|
305
|
-
|
|
299
|
+
}
|
|
300
|
+
if (isLoopDriver && (hasPromiseRuntime || asyncTaskClasses.length > 0 || usesTimers)) {
|
|
301
|
+
const taskNames = asyncTaskClasses.map(t => t.taskVarName);
|
|
302
|
+
const asyncConfig = strategy.getAsyncRuntimeConfig();
|
|
303
|
+
asyncConfig.hasPromiseRuntime = hasPromiseRuntime;
|
|
304
|
+
asyncConfig.hasTimers = usesTimers;
|
|
305
|
+
const injectionLines = strategy.asyncLoopInjection(taskNames, asyncConfig);
|
|
306
|
+
for (const line of injectionLines) {
|
|
307
|
+
appendSourceLine(ctx, ` ${line}`);
|
|
306
308
|
}
|
|
307
309
|
}
|
|
310
|
+
if (isLoopDriver && lastIsReturn) {
|
|
311
|
+
appendRenderedStatement(ctx, lastStmt, " ", functionScope);
|
|
312
|
+
}
|
|
308
313
|
appendSourceLine(ctx, "}");
|
|
309
314
|
emitCommentLines(fn.trailingComments, "", (line) => appendSourceLine(ctx, line));
|
|
310
315
|
appendSourceLine(ctx, "");
|
|
@@ -338,16 +343,21 @@ export function emitFunctions(ctx) {
|
|
|
338
343
|
export function emitFunctionForwardDeclarations(ctx) {
|
|
339
344
|
const { strategy, effectiveEmitMode } = ctx;
|
|
340
345
|
const excludedNames = new Set(strategy.forwardDeclarationExclusions?.() ?? []);
|
|
341
|
-
//
|
|
346
|
+
// Callback forward declarations (no IRAM_ATTR — ESP-IDF's attribute uses
|
|
347
|
+
// __COUNTER__, so decl+def would get conflicting .iram1.N sections).
|
|
342
348
|
if (effectiveEmitMode !== "split") {
|
|
343
349
|
for (const callback of ctx.callbackFunctions) {
|
|
344
|
-
appendSourceLine(ctx, `${
|
|
350
|
+
appendSourceLine(ctx, `${renderCallbackSignature(callback)};`);
|
|
345
351
|
}
|
|
346
352
|
}
|
|
347
353
|
let emittedAnyFn = false;
|
|
348
354
|
for (const fn of ctx.mappedFunctions) {
|
|
349
355
|
if (excludedNames.has(fn.name))
|
|
350
356
|
continue;
|
|
357
|
+
// Async functions become *Task classes; calls lower to `fooTask.run()`.
|
|
358
|
+
// Forward-declaring empty stubs triggers -Wunused-function.
|
|
359
|
+
if (fn.isAsync && ctx.hasAsyncRuntime)
|
|
360
|
+
continue;
|
|
351
361
|
const isExported = fn.isExported === true;
|
|
352
362
|
// A function is an entrypoint if it is the strategy's primary entrypoint
|
|
353
363
|
// (e.g. `setup` on Arduino, `main` on native) OR one of the platform's
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { GeneratedOutputs } from "../../types.js";
|
|
2
2
|
import type { EmitterContext } from "./emitter-context.js";
|
|
3
3
|
export declare function emitPreamble(ctx: EmitterContext): void;
|
|
4
|
+
/**
|
|
5
|
+
* Emit cooperative async state-machine classes. Must run AFTER top-level
|
|
6
|
+
* globals (emitTypeDeclarations) so identifiers like WIFI_SSID referenced
|
|
7
|
+
* from task bodies are already declared.
|
|
8
|
+
*/
|
|
9
|
+
export declare function emitAsyncTaskClasses(ctx: EmitterContext): void;
|
|
4
10
|
export declare function finalizeOutput(ctx: EmitterContext): GeneratedOutputs;
|