@typecad/cuttlefish 1.0.0-alpha.6 → 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/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 +418 -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/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +417 -0
- package/dist/cli.js +109 -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/init-scaffold.d.ts +3 -0
- package/dist/create/init-scaffold.js +48 -0
- package/dist/create/init-templates.d.ts +2 -0
- package/dist/create/init-templates.js +28 -8
- 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 +40 -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/build-ir.js +5 -1
- package/dist/ir/expression-to-ir.js +14 -0
- 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 +561 -0
- package/dist/ir/identifier-collector.js +18 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +14 -1
- package/dist/ir/program-analysis.d.ts +23 -0
- package/dist/ir/program-analysis.js +81 -2
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +50 -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 +41 -3
- package/dist/ir/validation-orchestrator.js +3 -1
- 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/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 +3 -1
- package/dist/testing.js +3 -1
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +72 -27
- package/dist/ui-hook.d.ts +17 -1
- package/dist/utils/cli.js +27 -1
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/package.json +8 -4
|
@@ -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;
|
|
@@ -26,7 +26,7 @@ function polyfillDefinitionGuard(definition) {
|
|
|
26
26
|
return null;
|
|
27
27
|
}
|
|
28
28
|
export function emitPreamble(ctx) {
|
|
29
|
-
const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills,
|
|
29
|
+
const { strategy, effectiveEmitMode, program, shimLines, emittedPolyfills, includes, programAnalysis } = ctx;
|
|
30
30
|
for (const include of dedupe(includes)) {
|
|
31
31
|
appendSourceLineLocal(ctx, `#include ${include}`);
|
|
32
32
|
}
|
|
@@ -73,16 +73,8 @@ export function emitPreamble(ctx) {
|
|
|
73
73
|
}
|
|
74
74
|
appendSourceLineLocal(ctx, "");
|
|
75
75
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for (const line of classDef.split("\n")) {
|
|
79
|
-
appendSourceLineLocal(ctx, line);
|
|
80
|
-
}
|
|
81
|
-
appendSourceLineLocal(ctx, "");
|
|
82
|
-
appendSourceLineLocal(ctx, instanceDecl);
|
|
83
|
-
appendSourceLineLocal(ctx, "");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
76
|
+
// Async task classes are emitted later (emitAsyncTaskClasses) AFTER top-level
|
|
77
|
+
// globals so references like WIFI_SSID inside the state machine compile.
|
|
86
78
|
if (strategy.needsVectorOverload() && hasConsoleCalls(program, strategy) && (programAnalysis.usesVectorTypes || programAnalysis.hasArrayInObjectLiteral)) {
|
|
87
79
|
appendSourceLineLocal(ctx, "template <typename T>");
|
|
88
80
|
appendSourceLineLocal(ctx, "std::ostream& operator<<(std::ostream& os, const std::vector<T>& values)");
|
|
@@ -136,6 +128,24 @@ export function emitPreamble(ctx) {
|
|
|
136
128
|
appendSourceLineLocal(ctx, "");
|
|
137
129
|
}
|
|
138
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Emit cooperative async state-machine classes. Must run AFTER top-level
|
|
133
|
+
* globals (emitTypeDeclarations) so identifiers like WIFI_SSID referenced
|
|
134
|
+
* from task bodies are already declared.
|
|
135
|
+
*/
|
|
136
|
+
export function emitAsyncTaskClasses(ctx) {
|
|
137
|
+
const { asyncTaskClasses } = ctx;
|
|
138
|
+
if (asyncTaskClasses.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
for (const { classDef, instanceDecl } of asyncTaskClasses) {
|
|
141
|
+
for (const line of classDef.split("\n")) {
|
|
142
|
+
appendSourceLineLocal(ctx, line);
|
|
143
|
+
}
|
|
144
|
+
appendSourceLineLocal(ctx, "");
|
|
145
|
+
appendSourceLineLocal(ctx, instanceDecl);
|
|
146
|
+
appendSourceLineLocal(ctx, "");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
139
149
|
export function finalizeOutput(ctx) {
|
|
140
150
|
const { program, strategy, options, effectiveEmitMode, isNpmPackage, baseName, includes, shimLines, emittedPolyfills } = ctx;
|
|
141
151
|
const outDir = options.outDir;
|
|
@@ -327,6 +327,46 @@ export function buildEmitterContext(program, options) {
|
|
|
327
327
|
if (!programAnalysis.usesTone) {
|
|
328
328
|
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_TONE_BEGIN', '// CUTTLEFISH_TONE_END');
|
|
329
329
|
}
|
|
330
|
+
// framework-esp32 native peripheral driver shims. Same defensive-backstop
|
|
331
|
+
// pattern as the UART/SPI/TWI/EEPROM/tone blocks above. framework-arduino's
|
|
332
|
+
// and framework-avr's shimLines contain none of these markers, so these
|
|
333
|
+
// filters are no-ops there.
|
|
334
|
+
if (!programAnalysis.usesGPIO) {
|
|
335
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_GPIO_BEGIN', '// CUTTLEFISH_GPIO_END');
|
|
336
|
+
}
|
|
337
|
+
if (!programAnalysis.usesPWM) {
|
|
338
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PWM_BEGIN', '// CUTTLEFISH_PWM_END');
|
|
339
|
+
}
|
|
340
|
+
if (!programAnalysis.usesADC) {
|
|
341
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_ADC_BEGIN', '// CUTTLEFISH_ADC_END');
|
|
342
|
+
}
|
|
343
|
+
if (!programAnalysis.usesDAC) {
|
|
344
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_DAC_BEGIN', '// CUTTLEFISH_DAC_END');
|
|
345
|
+
}
|
|
346
|
+
if (!programAnalysis.usesPower) {
|
|
347
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_POWER_BEGIN', '// CUTTLEFISH_POWER_END');
|
|
348
|
+
}
|
|
349
|
+
if (!programAnalysis.usesWdt) {
|
|
350
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WDT_BEGIN', '// CUTTLEFISH_WDT_END');
|
|
351
|
+
}
|
|
352
|
+
if (!programAnalysis.usesInterrupts) {
|
|
353
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_INTR_BEGIN', '// CUTTLEFISH_INTR_END');
|
|
354
|
+
}
|
|
355
|
+
if (!programAnalysis.usesPulse) {
|
|
356
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PULSE_BEGIN', '// CUTTLEFISH_PULSE_END');
|
|
357
|
+
}
|
|
358
|
+
if (!programAnalysis.usesShift) {
|
|
359
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_SHIFT_BEGIN', '// CUTTLEFISH_SHIFT_END');
|
|
360
|
+
}
|
|
361
|
+
if (!programAnalysis.usesWifi) {
|
|
362
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_WIFI_BEGIN', '// CUTTLEFISH_WIFI_END');
|
|
363
|
+
}
|
|
364
|
+
if (!programAnalysis.usesHttp) {
|
|
365
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_HTTP_BEGIN', '// CUTTLEFISH_HTTP_END');
|
|
366
|
+
}
|
|
367
|
+
if (!programAnalysis.usesPreferences) {
|
|
368
|
+
shimLines = filterShimBlock(shimLines, '// CUTTLEFISH_PREFERENCES_BEGIN', '// CUTTLEFISH_PREFERENCES_END');
|
|
369
|
+
}
|
|
330
370
|
profileDiagnostics = [...strategy.profileDiagnostics(program, options.platformContext)];
|
|
331
371
|
}
|
|
332
372
|
for (const imported of program.imports) {
|
|
@@ -597,6 +597,8 @@ export function runTopLevelPreprocessing(ctx) {
|
|
|
597
597
|
statements: callbackIR.statements ?? callbackIR.body ?? [],
|
|
598
598
|
debounceMs: callbackIR.debounceMs,
|
|
599
599
|
isInterruptHandler: callbackIR.isInterruptHandler,
|
|
600
|
+
...(callbackIR.returnType ? { returnType: callbackIR.returnType } : {}),
|
|
601
|
+
...(callbackIR.typedParams ? { typedParams: callbackIR.typedParams } : {}),
|
|
600
602
|
});
|
|
601
603
|
replacePlaceholderInAllStatements(filteredTopLevelExecutables, rc.placeholderName, callbackName);
|
|
602
604
|
for (const fn of mappedFunctions) {
|
|
@@ -34,7 +34,7 @@ import { getRadioGroups } from "../../ir/ui-element-auto-wire.js";
|
|
|
34
34
|
// minPressure gate is skipped for them. Resistive controllers (XPT2046,
|
|
35
35
|
// STMPE610, Adafruit_TouchScreen) keep the gate.
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
|
-
const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "sdl"]);
|
|
37
|
+
const CAPACITIVE_TOUCH_LIBS = new Set(["FT6336U", "GT911", "CST816S", "sdl"]);
|
|
38
38
|
/** Generate the ui_poll_touch() body, or null for the no-touch path. */
|
|
39
39
|
export function generateTouchPollBody(input) {
|
|
40
40
|
const { library, minPressure, calibration, profile } = input;
|
|
@@ -141,9 +141,14 @@ export function emitUIRuntime(ctx) {
|
|
|
141
141
|
// display-adapter registry. Must precede the runtime header so the
|
|
142
142
|
// adapter functions (__tc_display, display_init, etc.) are available.
|
|
143
143
|
const profile = getDisplayProfile();
|
|
144
|
-
// 0. SPI-bus display drivers (ILI9341, ST7796, …) need the SPI
|
|
145
|
-
//
|
|
146
|
-
|
|
144
|
+
// 0. SPI-bus display drivers (ILI9341, ST7796, …) need the Arduino SPI
|
|
145
|
+
// library — but ONLY on the Adafruit path. Native adapters (AVR, ESP32)
|
|
146
|
+
// emit their own bus setup and would break on <SPI.h>. Host render targets
|
|
147
|
+
// (sdl, native-preview) have no SPI bus at all.
|
|
148
|
+
const nativeAdapter = ctx.strategy?.providesDisplayAdapter?.() ?? false;
|
|
149
|
+
const spiDriver = profile?.driver !== "sdl"
|
|
150
|
+
&& profile?.driver !== "native-preview"
|
|
151
|
+
&& !nativeAdapter;
|
|
147
152
|
if (spiDriver && !ctx.includes.includes("<SPI.h>")) {
|
|
148
153
|
ctx.includes.push("<SPI.h>");
|
|
149
154
|
}
|
|
@@ -151,7 +156,7 @@ export function emitUIRuntime(ctx) {
|
|
|
151
156
|
// buildEmitterContext (setup.ts), NOT here — emitPreamble runs before this
|
|
152
157
|
// function, so a push here would be too late to land in the output.
|
|
153
158
|
if (profile) {
|
|
154
|
-
const adapter = generateDisplayAdapter(profile);
|
|
159
|
+
const adapter = generateDisplayAdapter(profile, ctx.strategy);
|
|
155
160
|
// Prepend includes to the very front — Arduino's auto-prototyper scans
|
|
156
161
|
// the whole .ino and generates prototypes that reference GFXcanvas16
|
|
157
162
|
// etc. before any #include, so the GFX header must come first.
|
|
@@ -164,6 +169,14 @@ export function emitUIRuntime(ctx) {
|
|
|
164
169
|
ctx.sourceLines.push(is888
|
|
165
170
|
? "#define UI_COLOR_DEPTH 888\n#define UI_COLOR_T uint32_t\n#define UI_DIM_MASK 0x7F7F7Fu"
|
|
166
171
|
: "#define UI_COLOR_DEPTH 565\n#define UI_COLOR_T uint16_t\n#define UI_DIM_MASK 0x7BEFu");
|
|
172
|
+
// Native display adapters (AVR, ESP32) instantiate CuttlefishGFX by value
|
|
173
|
+
// in their declaration block. The class MUST be defined before that —
|
|
174
|
+
// emit the cuttlefish-gfx slice here, between the color preamble and the
|
|
175
|
+
// adapter declaration. Arduino path (Adafruit) returns "" from the slice.
|
|
176
|
+
const nativeDisplayActive = ctx.strategy?.providesDisplayAdapter?.() ?? false;
|
|
177
|
+
if (nativeDisplayActive) {
|
|
178
|
+
ctx.sourceLines.push(ui.emitCuttlefishGfx(true));
|
|
179
|
+
}
|
|
167
180
|
ctx.sourceLines.push(adapter.declaration);
|
|
168
181
|
ctx.sourceLines.push(adapter.functions);
|
|
169
182
|
}
|
|
@@ -173,7 +186,7 @@ export function emitUIRuntime(ctx) {
|
|
|
173
186
|
if (profile.touch) {
|
|
174
187
|
const t = profile.touch;
|
|
175
188
|
if (t.library) {
|
|
176
|
-
touchAdapter = generateTouchAdapter(t);
|
|
189
|
+
touchAdapter = generateTouchAdapter(t, ctx.strategy);
|
|
177
190
|
// Prepend includes to the front (Arduino auto-prototyper safety).
|
|
178
191
|
ctx.sourceLines.unshift(touchAdapter.includes[0]);
|
|
179
192
|
for (let i = 1; i < touchAdapter.includes.length; i++) {
|
|
@@ -252,13 +265,15 @@ export function emitUIRuntime(ctx) {
|
|
|
252
265
|
// header so its #ifndef guards adopt them. Source of truth:
|
|
253
266
|
// resolveScrollConfig(profile.scroll). Defaults derive from the declared
|
|
254
267
|
// touch hardware, so the demo (XPT2046) gets resistive+full with no config.
|
|
255
|
-
const scroll = resolveScrollConfig(profile, { buildTarget: profile._buildTarget });
|
|
268
|
+
const scroll = resolveScrollConfig(profile, { buildTarget: profile._buildTarget, psram: profile._psram });
|
|
256
269
|
ctx.sourceLines.push(`#define UI_SCROLL_MAX_OVERSCROLL ${scroll.maxOverscroll}`, `#define UI_SCROLL_STIFFNESS_X10 ${Math.round(scroll.stiffness * 10)}`, `#define UI_SCROLL_EDGE_SNAP_PX ${scroll.edgeSnapPx}`, `#define UI_SCROLL_DRAG_SCALE_X10 ${Math.round(scroll.dragScale * 10)}`, `#define UI_SCROLL_INPUT_TIER_CAPACITIVE ${scroll.inputTier === "capacitive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_RESISTIVE ${scroll.inputTier === "resistive" ? 1 : 0}`, `#define UI_SCROLL_INPUT_TIER_NONE ${scroll.inputTier === "none" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_FULL ${scroll.renderTier === "full" ? 1 : 0}`, `#define UI_SCROLL_RENDER_TIER_CONSTRAINED ${scroll.renderTier === "constrained" ? 1 : 0}`, `#define UI_SCROLL_CANVAS_BUDGET_BYTES ${scroll.scrollCanvasBudgetBytes}`, ...(scroll.debug ? [`#define UI_SCROLL_DEBUG 1`] : []));
|
|
257
270
|
// Forward-declare __ui_kb_set_onchange before the runtime header: the
|
|
258
271
|
// header's keyboard-open function calls it, but the definition is emitted
|
|
259
272
|
// later (section 8b). On native (single TU) this must precede the header.
|
|
260
273
|
ctx.sourceLines.push(`void __ui_kb_set_onchange();`);
|
|
261
|
-
ctx.sourceLines.push(emitRuntimeHeader(
|
|
274
|
+
ctx.sourceLines.push(emitRuntimeHeader({
|
|
275
|
+
nativeDisplayActive: ctx.strategy?.providesDisplayAdapter?.() ?? false,
|
|
276
|
+
}));
|
|
262
277
|
// 1.5. Touch poll function (uses the adapter pattern). Body is generated by
|
|
263
278
|
// generateTouchPollBody — extracted for unit-testability of the capacitive
|
|
264
279
|
// vs resistive branch (FT6336U/SDL skip the minPressure gate).
|
|
@@ -10,6 +10,7 @@ import { accessorGetterName } from "./utils/cpp-helpers.js";
|
|
|
10
10
|
import { INTEGRAL_CPP_TYPE_RE } from "./utils/cpp-helpers.js";
|
|
11
11
|
import { renderPeripheralProperty } from "../mapping/peripheral-names.js";
|
|
12
12
|
import { parseCppType, renderCppType, bareType, parsedIsPointer, parsedIsVector, needsCStrForStringLike } from "../api/shared/cpp-type-ir.js";
|
|
13
|
+
import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
|
|
13
14
|
/**
|
|
14
15
|
* Renders ExpressionIR nodes to C++ code strings.
|
|
15
16
|
*/
|
|
@@ -403,6 +404,8 @@ export class ExpressionRenderer {
|
|
|
403
404
|
}
|
|
404
405
|
case "unary":
|
|
405
406
|
return expr.operator === "!" ? "bool" : this.inferExpressionCppType(expr.operand, knownVariableTypes);
|
|
407
|
+
case "hal-expr":
|
|
408
|
+
return cppTypeForHalOp(expr.operation.operation);
|
|
406
409
|
default:
|
|
407
410
|
return undefined;
|
|
408
411
|
}
|
|
@@ -714,10 +717,16 @@ export class ExpressionRenderer {
|
|
|
714
717
|
}
|
|
715
718
|
case "hal-expr": {
|
|
716
719
|
const rendered = this.render(expr, exprTransformer);
|
|
717
|
-
// HAL expressions that resolve to string-like outputs use %s
|
|
718
720
|
if (rendered.startsWith('"') || this.stringVarNames?.has(rendered)) {
|
|
719
721
|
return { format: "%s", arg: rendered, estimatedLength: 32 };
|
|
720
722
|
}
|
|
723
|
+
const cppType = cppTypeForHalOp(expr.operation.operation);
|
|
724
|
+
if (cppType && this.isStringLikeCppType(cppType)) {
|
|
725
|
+
return { format: "%s", arg: rendered, estimatedLength: 32 };
|
|
726
|
+
}
|
|
727
|
+
if (cppType === "bool") {
|
|
728
|
+
return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5 };
|
|
729
|
+
}
|
|
721
730
|
return { format: "%d", arg: rendered, estimatedLength: 12 };
|
|
722
731
|
}
|
|
723
732
|
case "method-call":
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Types are imported from @typecad/cuttlefish/api/shared.
|
|
9
9
|
// ---------------------------------------------------------------------------
|
|
10
10
|
import { escapeCppStringLiteral, escapeSnprintfFormatFragment } from "../utils/strings.js";
|
|
11
|
+
import { cppTypeForHalOp } from "./utils/hal-op-cpp-type.js";
|
|
11
12
|
import { parsedElementString, parsedIsStringLike } from "../api/shared/cpp-type-ir.js";
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Scope state management
|
|
@@ -369,6 +370,13 @@ export function inferSnprintfArg(expr, strategy, scopeState, renderExpression, p
|
|
|
369
370
|
if (rendered.startsWith('"') || stringVarNames?.has(rendered)) {
|
|
370
371
|
return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
|
|
371
372
|
}
|
|
373
|
+
const cppType = cppTypeForHalOp(expr.operation.operation);
|
|
374
|
+
if (cppType && strategy.isStringLikeType(cppType)) {
|
|
375
|
+
return { format: "%s", arg: rendered, estimatedLength: 32, preludeLines: [] };
|
|
376
|
+
}
|
|
377
|
+
if (cppType === "bool") {
|
|
378
|
+
return { format: "%s", arg: `(${rendered} ? "true" : "false")`, estimatedLength: 5, preludeLines: [] };
|
|
379
|
+
}
|
|
372
380
|
return { format: "%d", arg: rendered, estimatedLength: 12, preludeLines: [] };
|
|
373
381
|
}
|
|
374
382
|
default:
|
|
@@ -485,6 +485,19 @@ export class StatementRenderer {
|
|
|
485
485
|
const rawStmt = statement.callee.slice('__RAW_STMT__'.length);
|
|
486
486
|
return forHeader ? rawStmt : `${rawStmt.endsWith(';') ? rawStmt : rawStmt + ';'}`;
|
|
487
487
|
}
|
|
488
|
+
// Awaited network markers (__WIFI_WAIT__/__HTTP_WAIT__) reaching the
|
|
489
|
+
// plain renderer means the await sits outside an async state machine
|
|
490
|
+
// (top-level await, or a position the state-machine splitter doesn't
|
|
491
|
+
// support). Fall back to the blocking form of the op they carry.
|
|
492
|
+
if ((statement.callee === "__WIFI_WAIT__" || statement.callee === "__HTTP_WAIT__" || statement.callee === "__BLE_WAIT__" || statement.callee === "__HAL_WAIT__")
|
|
493
|
+
&& statement.args[0]?.kind === "hal-expr") {
|
|
494
|
+
const resolved = routeHALOp(statement.args[0].operation, this.strategy);
|
|
495
|
+
const code = resolved?.code ?? (resolved?.expression ? `${resolved.expression};` : undefined);
|
|
496
|
+
if (code) {
|
|
497
|
+
return forHeader ? code.replace(/;$/, "") : code;
|
|
498
|
+
}
|
|
499
|
+
return `/* unhandled awaited hal-op: ${statement.args[0].operation.operation} */`;
|
|
500
|
+
}
|
|
488
501
|
// Handle emit() — compile-time C++ injection
|
|
489
502
|
if (statement.callee === "__EMIT__") {
|
|
490
503
|
const rawText = statement.args.map(arg => this.renderEmitArg(arg)).join("");
|