@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +595 -0
- package/dist/api/shared/framework-manifest.js +193 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +119 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +372 -30
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +207 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +15 -8
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -12,6 +12,7 @@ import { tryLowerRegisterRead } from "./transformers/register-assignment.js";
|
|
|
12
12
|
import { tryLowerArrayAndStringMethods } from "./transformers/array-methods.js";
|
|
13
13
|
import { collectReturns, inferExprCppType, typeNodeToCppType } from "./type-resolution.js";
|
|
14
14
|
import { parseCppType, elementOf, renderCppType, bareType, parsedIsPointer, parsedIsVector, parsedIsMap, parsedIsSet, parsedIsTuple, parsedIsStdString, parsedElementString, parsedBareString, isContainer } from "../api/shared/cpp-type-ir.js";
|
|
15
|
+
import { hasSafetyHook, requireSafetyHook } from "../safety-hook.js";
|
|
15
16
|
function resolveExprCppType(expr) {
|
|
16
17
|
if (ts.isNonNullExpression(expr) || ts.isParenthesizedExpression(expr)) {
|
|
17
18
|
return resolveExprCppType(expr.expression);
|
|
@@ -184,6 +185,143 @@ function resolveCallReturnTypeForNullGuard(call, sourceText) {
|
|
|
184
185
|
}
|
|
185
186
|
return undefined;
|
|
186
187
|
}
|
|
188
|
+
/** The SafeReadResult/SafeWriteResult chain methods whose lambda args must
|
|
189
|
+
* render inline (preserving closure captures). Detected on the TS chain to
|
|
190
|
+
* decide whether to take the structured-receiver path. */
|
|
191
|
+
const SAFETY_RESULT_CHAIN_METHODS = new Set(["ok", "fail", "fault", "always"]);
|
|
192
|
+
/** Detect `safe.<read|write>(...).<ok|fail|fault|always>(handler)...` chains
|
|
193
|
+
* and build them as nested method-call IR nodes with a STRUCTURED receiverExpr
|
|
194
|
+
* (not a flattened callee string). This preserves inner lambda args so the
|
|
195
|
+
* emit pipeline can render them inline via renderLambda.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when the expression is not a safe.* call chained with at least
|
|
198
|
+
* one result method (terminal safe.read/safe.write are handled by the
|
|
199
|
+
* statement/var-decl interceptors; non-safe chains use the generic path). */
|
|
200
|
+
function tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars) {
|
|
201
|
+
if (!hasSafetyHook())
|
|
202
|
+
return null;
|
|
203
|
+
const chain = [];
|
|
204
|
+
let cur = expr;
|
|
205
|
+
while (ts.isCallExpression(cur) && ts.isPropertyAccessExpression(cur.expression)) {
|
|
206
|
+
chain.push({ methodName: cur.expression.name.text, callNode: cur });
|
|
207
|
+
cur = cur.expression.expression;
|
|
208
|
+
}
|
|
209
|
+
// After the loop, `cur` is the innermost receiver. For a safe.* chain, the
|
|
210
|
+
// last-pushed chain element is the safe.read/safe.write root (its
|
|
211
|
+
// callNode.expression.expression is the identifier `safe`). Identify it.
|
|
212
|
+
if (chain.length === 0)
|
|
213
|
+
return null;
|
|
214
|
+
const root = chain[chain.length - 1];
|
|
215
|
+
const rootCall = root.callNode;
|
|
216
|
+
// rootCall.expression is `safe.read` (PropertyAccess); rootCall.expression.expression
|
|
217
|
+
// must be the identifier `safe` for this to be a safe.* root.
|
|
218
|
+
// The root method is read/write; the links above it (chain[0..length-2]) must
|
|
219
|
+
// include at least one result-chain method (ok/fail/fault/always).
|
|
220
|
+
const resultLinks = chain.slice(0, chain.length - 1);
|
|
221
|
+
const hasChainMethod = resultLinks.some((link) => SAFETY_RESULT_CHAIN_METHODS.has(link.methodName));
|
|
222
|
+
if (!hasChainMethod)
|
|
223
|
+
return null;
|
|
224
|
+
// Resolve the safe.<method>(...) root to a hal-expr (the same way variables.ts
|
|
225
|
+
// does for `const r = safe.read(pin)`). This becomes the innermost receiver.
|
|
226
|
+
const rootPropAccess = rootCall.expression;
|
|
227
|
+
const safeMethod = rootPropAccess.name.text;
|
|
228
|
+
const argValues = rootCall.arguments.map((a) => {
|
|
229
|
+
if (ts.isNumericLiteral(a))
|
|
230
|
+
return Number(a.text);
|
|
231
|
+
if (ts.isStringLiteral(a))
|
|
232
|
+
return a.text;
|
|
233
|
+
if (a.kind === ts.SyntaxKind.TrueKeyword)
|
|
234
|
+
return true;
|
|
235
|
+
if (a.kind === ts.SyntaxKind.FalseKeyword)
|
|
236
|
+
return false;
|
|
237
|
+
if (ts.isIdentifier(a))
|
|
238
|
+
return a.text;
|
|
239
|
+
return a.getText();
|
|
240
|
+
});
|
|
241
|
+
const op = requireSafetyHook().resolveSemanticCall?.(`safe.${safeMethod}`, argValues);
|
|
242
|
+
// If the hook couldn't resolve the pin (halInstances not yet populated for
|
|
243
|
+
// this identifier at this point in IR building), fall back to rendering the
|
|
244
|
+
// root verbatim. The chain structure (with inline lambdas) is still correct;
|
|
245
|
+
// only the root's HAL resolution is deferred. This happens for multi-link
|
|
246
|
+
// chains where the resolver runs before the pin's HAL instance is registered.
|
|
247
|
+
let receiver;
|
|
248
|
+
if (op) {
|
|
249
|
+
receiver = { kind: "hal-expr", operation: op };
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// Best-effort: resolve the pin number directly from halInstances (the hook's
|
|
253
|
+
// resolvePinArg does the same lookup, but calling it directly here avoids the
|
|
254
|
+
// hook's early-return on unresolved pins).
|
|
255
|
+
const pinArg = argValues[0];
|
|
256
|
+
let pinNum;
|
|
257
|
+
if (typeof pinArg === "string") {
|
|
258
|
+
const inst = halInstances.get(pinArg);
|
|
259
|
+
if (inst) {
|
|
260
|
+
const pv = inst.fieldValues.get("_pin") ?? inst.fieldValues.get("pin");
|
|
261
|
+
if (pv !== undefined)
|
|
262
|
+
pinNum = Number(pv);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (typeof pinArg === "number") {
|
|
266
|
+
pinNum = pinArg;
|
|
267
|
+
}
|
|
268
|
+
if (pinNum !== undefined) {
|
|
269
|
+
// Build the op manually now that we have the pin.
|
|
270
|
+
const fallbackOp = safeMethod === "read"
|
|
271
|
+
? { operation: "safety.read_safe", pin: pinNum }
|
|
272
|
+
: { operation: "safety.write_verify", pin: pinNum, value: argValues[1] ?? 0 };
|
|
273
|
+
receiver = { kind: "hal-expr", operation: fallbackOp };
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Last resort: verbatim text. The chain renders but the root won't have
|
|
277
|
+
// resolved to __tc_safety::read_safe — acceptable degradation.
|
|
278
|
+
receiver = { kind: "raw", value: rootCall.getText() };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// The root renders as __tc_safety::read_safe(pin) — render it to text so the
|
|
282
|
+
// callee string (used for non-receiverExpr consumers like type inference) is
|
|
283
|
+
// correct.
|
|
284
|
+
const rootText = renderExprAsText(receiver);
|
|
285
|
+
// Build nested method-call nodes bottom-up over the RESULT links only
|
|
286
|
+
// (chain[0..length-2], excluding the root at chain[length-1]). chain[0] is the
|
|
287
|
+
// outermost call (e.g. .fail); we build from the innermost result link outward.
|
|
288
|
+
for (let i = resultLinks.length - 1; i >= 0; i--) {
|
|
289
|
+
const link = resultLinks[i];
|
|
290
|
+
// Render this link's args as IR (lambdas become lambda IR nodes that
|
|
291
|
+
// renderLambda handles at emit time).
|
|
292
|
+
const argIRs = link.callNode.arguments.map((a) => expressionToIR(a, sourceText, diagnostics, pointerVars));
|
|
293
|
+
// Set concrete param types on lambda args so renderLambda emits
|
|
294
|
+
// `const SafeReadResult& r` instead of `auto r`. Generic lambdas (auto
|
|
295
|
+
// params) require C++14; AVR's gnu++11 doesn't support them. The chain
|
|
296
|
+
// knows the result type from the root (read -> SafeReadResult,
|
|
297
|
+
// write -> SafeWriteResult).
|
|
298
|
+
const resultType = safeMethod === "read" ? "SafeReadResult" : "SafeWriteResult";
|
|
299
|
+
for (const argIR of argIRs) {
|
|
300
|
+
if (argIR.kind === "lambda" && argIR.params) {
|
|
301
|
+
for (const p of argIR.params) {
|
|
302
|
+
if (!p.cppType || p.cppType === "auto") {
|
|
303
|
+
p.cppType = `const ${resultType}&`;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Build a callee string for backward-compat with consumers that read
|
|
309
|
+
// method-call.callee. The receiver text is rootText for the innermost link,
|
|
310
|
+
// or the prior receiver's rendered text for outer links.
|
|
311
|
+
const priorCallee = i === resultLinks.length - 1
|
|
312
|
+
? rootText
|
|
313
|
+
: renderExprAsText(receiver);
|
|
314
|
+
const callee = `${priorCallee}.${escapeCppKeyword(link.methodName, new Set())}`;
|
|
315
|
+
receiver = {
|
|
316
|
+
kind: "method-call",
|
|
317
|
+
callee,
|
|
318
|
+
args: argIRs,
|
|
319
|
+
methodName: link.methodName,
|
|
320
|
+
receiverExpr: receiver,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
return receiver;
|
|
324
|
+
}
|
|
187
325
|
export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new Map()) {
|
|
188
326
|
function emitUnsupportedExpression(message) {
|
|
189
327
|
diagnostics.push(makeDiagnostic(sourceText, expr.pos, message, "error", "TS2CPP_UNSUPPORTED_EXPR"));
|
|
@@ -264,15 +402,52 @@ export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new
|
|
|
264
402
|
return `(sizeof(${safeText}) / sizeof(${safeText}[0]))`;
|
|
265
403
|
}
|
|
266
404
|
if (ts.isCallExpression(receiverNode)) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
405
|
+
// Case B: a HAL buffer-returning method (I2CDevice.readBytes /
|
|
406
|
+
// SPIDevice.readRegister) used INLINE as a sub-expression — e.g.
|
|
407
|
+
// `dev.readBytes(0,6).length`. These methods lower to STATEMENTS (a fill
|
|
408
|
+
// loop), not a single C++ expression, so by the time we get here the
|
|
409
|
+
// receiver text is already a leaked `for (...) __buf[__i] = Wire.read()`
|
|
410
|
+
// statement spliced where an expression is required. The buffer also has
|
|
411
|
+
// no caller-side name to sizeof. Only the var-init form is supported
|
|
412
|
+
// (`const data = dev.readBytes(...)`); emit a clear diagnostic so the
|
|
413
|
+
// user gets an actionable error instead of inscrutable broken C++.
|
|
414
|
+
if (/\bfor\s*\(/.test(safeText) || /__buf|__spi_buf/.test(safeText)) {
|
|
415
|
+
const calleeName = ts.isPropertyAccessExpression(receiverNode.expression)
|
|
416
|
+
? receiverNode.expression.name.text : "call";
|
|
417
|
+
diagnostics.push(makeDiagnostic(sourceText, receiverNode.getStart(), `\`${calleeName}(...)\` returns a buffer and cannot be queried inline. Capture it into a variable first (e.g. \`const data = ${calleeName}(...)\`), then use \`data.length\`.`, "error", "TC_BUFFER_INLINE_LENGTH"));
|
|
418
|
+
return `0 /* ${calleeName}() result must be captured into a variable to use .length */`;
|
|
419
|
+
}
|
|
420
|
+
// Resolve the call's RETURN type (not the receiver object's type, which
|
|
421
|
+
// the previous code incorrectly did). Method calls resolve via the class
|
|
422
|
+
// registry; free-function calls resolve by scanning the source AST for
|
|
423
|
+
// the declared return type.
|
|
424
|
+
const returnType = resolveExprCppType(receiverNode)
|
|
425
|
+
?? resolveCallReturnTypeForNullGuard(receiverNode, sourceText);
|
|
426
|
+
if (returnType) {
|
|
427
|
+
const parsed = parseCppType(returnType);
|
|
428
|
+
// std::string → .length(); any STL container (vector/map/set) → .size().
|
|
429
|
+
if (parsedIsStdString(returnType)) {
|
|
430
|
+
return `static_cast<long long>(${safeText}.length())`;
|
|
431
|
+
}
|
|
432
|
+
if (parsedIsVector(returnType) || parsedIsMap(returnType) || parsedIsSet(returnType) || isContainer(parsed)) {
|
|
433
|
+
return `static_cast<long long>(${safeText}.size())`;
|
|
434
|
+
}
|
|
435
|
+
// A raw pointer / decayed-array return (e.g. uint8_t*) carries no size
|
|
436
|
+
// at the call site — sizeof would yield sizeof(pointer). This is
|
|
437
|
+
// genuinely un-sizeable inline; surface it rather than emit wrong code.
|
|
438
|
+
if (parsedIsPointer(returnType) || /\]\s*$/.test(returnType)) {
|
|
439
|
+
const calleeName = ts.isPropertyAccessExpression(receiverNode.expression)
|
|
440
|
+
? receiverNode.expression.name.text
|
|
441
|
+
: (ts.isIdentifier(receiverNode.expression) ? receiverNode.expression.text : "call");
|
|
442
|
+
diagnostics.push(makeDiagnostic(sourceText, receiverNode.getStart(), `Cannot use \`.length\` on \`${calleeName}()\` which returns a pointer (${returnType}); the size is not available at the call site. Capture the buffer into a variable first.`, "error", "TC_LENGTH_ON_POINTER_RETURN"));
|
|
443
|
+
return `0 /* .length unavailable on ${returnType} return */`;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
272
446
|
// Cast .size() to long long to match the loop-counter type (TS number ->
|
|
273
447
|
// long long). Without this, `i < vec.size()` compares long long vs
|
|
274
448
|
// size_t (unsigned) and g++ -Wall warns -Wsign-compare on every
|
|
275
|
-
// indexed loop over an array.
|
|
449
|
+
// indexed loop over an array. (Fallback for unresolvable return types —
|
|
450
|
+
// historically every call-result .length landed here.)
|
|
276
451
|
return `static_cast<long long>(${safeText}.size())`;
|
|
277
452
|
}
|
|
278
453
|
// Resolve by concrete cppType first so std::string vars render member calls
|
|
@@ -916,6 +1091,32 @@ export function expressionToIR(expr, sourceText, diagnostics, pointerVars = new
|
|
|
916
1091
|
};
|
|
917
1092
|
}
|
|
918
1093
|
if (ts.isCallExpression(expr)) {
|
|
1094
|
+
// ---- safe.read/safe.write chained with .ok/.fail/.fault/.always ----
|
|
1095
|
+
// Method chains like `safe.read(pin).ok(r => {...}).fail(r => {...})` cannot
|
|
1096
|
+
// use the generic method-call builder because it flattens the receiver into
|
|
1097
|
+
// a callee STRING (expression-to-ir.ts ~line 1475), destroying inner lambda
|
|
1098
|
+
// args (they become `/* __lambda__ */` placeholders baked into the callee
|
|
1099
|
+
// text). This resolver detects a safe.* call followed by a chain of fluent
|
|
1100
|
+
// result methods and builds it bottom-up as nested method-call nodes with a
|
|
1101
|
+
// STRUCTURED receiverExpr, so the emit pipeline renders each lambda arg
|
|
1102
|
+
// inline via renderLambda (preserving closure captures).
|
|
1103
|
+
const safetyChain = tryResolveSafetyChain(expr, sourceText, diagnostics, pointerVars);
|
|
1104
|
+
if (safetyChain)
|
|
1105
|
+
return safetyChain;
|
|
1106
|
+
// ---- rawCppExpr() — compile-time C++ injection in expression context ----
|
|
1107
|
+
// Mirrors the statement-position rawCpp()/__EMIT__ path, but emits the raw
|
|
1108
|
+
// text as an expression (e.g. for IDF macros like WIFI_INIT_CONFIG_DEFAULT()
|
|
1109
|
+
// that produce struct values). The type parameter is a TS hint only — the
|
|
1110
|
+
// transpiler emits the raw text verbatim.
|
|
1111
|
+
if (ts.isIdentifier(expr.expression) && expr.expression.text === "rawCppExpr") {
|
|
1112
|
+
const arg = expr.arguments[0];
|
|
1113
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
1114
|
+
return { kind: "raw", value: arg.text };
|
|
1115
|
+
}
|
|
1116
|
+
if (arg && ts.isNoSubstitutionTemplateLiteral(arg)) {
|
|
1117
|
+
return { kind: "raw", value: arg.text };
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
919
1120
|
// ---- board() / boardResolve() — compile-time board constant lookup ----
|
|
920
1121
|
if (ts.isIdentifier(expr.expression) && (expr.expression.text === "board" || expr.expression.text === "boardResolve")) {
|
|
921
1122
|
const pathArg = expr.arguments[0];
|
|
@@ -198,11 +198,13 @@ function kindBasedLintRules() {
|
|
|
198
198
|
// `no-restricted-syntax` selector for real-time editor warnings. Kept here so
|
|
199
199
|
// that all editor-time selectors live in one place.
|
|
200
200
|
//
|
|
201
|
-
// NOTE on async/
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
//
|
|
201
|
+
// NOTE on async/await: the transpiler fully supports these — each `async`
|
|
202
|
+
// function is lowered to a cooperative state-machine task driven from
|
|
203
|
+
// `loop()` (see `emit/utils/async-state-machine.ts` and the
|
|
204
|
+
// `async-runtime-static.ts` microtask pump). `await expr` lowers to the task
|
|
205
|
+
// yielding between segments, so blocking waits become polls. They are
|
|
206
|
+
// therefore intentionally NOT linted here. Generators/yield and
|
|
207
|
+
// `for await...of` still have no lowering and remain flagged.
|
|
206
208
|
const CONTEXT_LINT_RULES = [
|
|
207
209
|
{
|
|
208
210
|
selector: "ForOfStatement[await=true]",
|
|
@@ -244,26 +246,6 @@ const CONTEXT_LINT_RULES = [
|
|
|
244
246
|
message: "[transpiler] .then() on a Promise is not supported (no promise runtime). Use synchronous return values or callbacks.",
|
|
245
247
|
source: "context",
|
|
246
248
|
},
|
|
247
|
-
{
|
|
248
|
-
selector: "FunctionDeclaration[async=true]",
|
|
249
|
-
message: "[transpiler] async functions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous function.",
|
|
250
|
-
source: "context",
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
selector: "FunctionExpression[async=true]",
|
|
254
|
-
message: "[transpiler] async function expressions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous function.",
|
|
255
|
-
source: "context",
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
selector: "ArrowFunctionExpression[async=true]",
|
|
259
|
-
message: "[transpiler] async arrow functions are recognized for parse-compatibility but cannot produce correct embedded behavior (no event loop). Use a synchronous arrow function.",
|
|
260
|
-
source: "context",
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
selector: "AwaitExpression",
|
|
264
|
-
message: "[transpiler] await is stripped to an inline expression — semantics are approximate and there is no event loop on bare metal. Avoid in firmware.",
|
|
265
|
-
source: "context",
|
|
266
|
-
},
|
|
267
249
|
{
|
|
268
250
|
selector: "FunctionDeclaration[generator=true]",
|
|
269
251
|
message: "[transpiler] generator functions (function*) have no coroutine runtime on bare metal and are effectively unusable. Avoid.",
|
|
@@ -163,6 +163,7 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
163
163
|
const elementType = restParam.cppType.replace(/^(?:const\s+)?std::vector<(.+)>&?$/, "$1");
|
|
164
164
|
restParamFunctions.set(node.name.text, elementType);
|
|
165
165
|
}
|
|
166
|
+
const fnDecorators = extractAsilFromComments(node, sourceText);
|
|
166
167
|
return {
|
|
167
168
|
originalName: node.name.text,
|
|
168
169
|
isAsync,
|
|
@@ -174,8 +175,29 @@ export function functionDeclarationToIR(node, fileName, sourceText, diagnostics,
|
|
|
174
175
|
...(fnTypeParams && fnTypeParams.length > 0 ? { typeParameters: fnTypeParams } : {}),
|
|
175
176
|
...(isGenerator ? { isGenerator: true } : {}),
|
|
176
177
|
...(isExported ? { isExported: true } : {}),
|
|
178
|
+
...(fnDecorators && fnDecorators.length > 0 ? { decorators: fnDecorators } : {}),
|
|
177
179
|
};
|
|
178
180
|
}
|
|
181
|
+
/** Extract ASIL annotations from leading comments (e.g. `// @asilD`).
|
|
182
|
+
*
|
|
183
|
+
* Uses comment-based annotations instead of TypeScript decorators because
|
|
184
|
+
* TS decorators on function declarations produce TS1206 errors in the VS Code
|
|
185
|
+
* language server and the @typescript-eslint/parser (both reject decorators
|
|
186
|
+
* on functions — only class/method/property decorators are supported).
|
|
187
|
+
* Comment annotations are valid TypeScript in any context, any editor, and
|
|
188
|
+
* any toolchain — no false-positive errors. */
|
|
189
|
+
function extractAsilFromComments(node, sourceText) {
|
|
190
|
+
const comments = extractNodeComments(node, sourceText);
|
|
191
|
+
const names = [];
|
|
192
|
+
for (const comment of comments.leadingComments) {
|
|
193
|
+
// Match `// @asilD`, `// @asilC`, `// @asilB`, `// @asilA`, `// @asilQM`
|
|
194
|
+
const match = comment.match(/@(asil[DCBAQM])/i);
|
|
195
|
+
if (match) {
|
|
196
|
+
names.push(match[1]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return names.length > 0 ? names : undefined;
|
|
200
|
+
}
|
|
179
201
|
export function variableAsFunctionToIR(node, fileName, sourceText, diagnostics, functionReturnTypes, typeAliasNodes, boilerplates, pointerVars = new Map(), lowerStatementList) {
|
|
180
202
|
const functionExpressionDeclarations = node.declarationList.declarations.filter((declaration) => {
|
|
181
203
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
@@ -19,9 +19,12 @@ export declare function maybeEscapeResolvedText(text: string): string;
|
|
|
19
19
|
export declare function stripCStrAfterStringLiteral(followingLiteralText: string, resolvedValue: string): string;
|
|
20
20
|
/** Resolve an arbitrary expression to its text form, with this/param substitution. */
|
|
21
21
|
export declare function resolveExpressionText(expr: ts.Expression, instance: HALInstance, paramNames: string[], callArgTexts: string[], paramDefaults?: Map<string, string>): string | null;
|
|
22
|
+
/** True when a HAL semantic call name wraps a GPIO/hardware ISR callback. */
|
|
23
|
+
export declare function semanticCallUsesIsrCallback(fnName: string): boolean;
|
|
22
24
|
/** Scan an expression AST for callback() calls, extract callback IR from callArgs,
|
|
23
|
-
* register them, and patch callArgTexts with placeholder names.
|
|
24
|
-
|
|
25
|
+
* register them, and patch callArgTexts with placeholder names.
|
|
26
|
+
* @param isInterruptHandler — only true for GPIO interrupt attach paths. */
|
|
27
|
+
export declare function extractAndRegisterCallbacks(expr: ts.Expression, paramNames: string[], callArgs: ExpressionIR[], callArgTexts: string[], isInterruptHandler?: boolean): void;
|
|
25
28
|
/** Process a HAL method body, resolving emit()/include()/semantic calls.
|
|
26
29
|
* Returns { emitLines, halOps, returnValue, returnClassName } or null if unresolvable.
|
|
27
30
|
*
|
|
@@ -5,6 +5,7 @@ import { renderExprAsText } from "../render-expr.js";
|
|
|
5
5
|
import { escapeCppKeyword } from "../../utils/strings.js";
|
|
6
6
|
import { halClassRegistry, halGlobalFunctions } from "./hal-parser.js";
|
|
7
7
|
import { tryResolveSemanticCall, tryResolveBoardResolveArg, tryResolveCompoundSemanticReturn, resolveConcatPath } from "./hal-plugins.js";
|
|
8
|
+
import { cppTypeForHalOp } from "../../emit/utils/hal-op-cpp-type.js";
|
|
8
9
|
/** Escape C++ keywords in resolved text, but only when the text looks like a
|
|
9
10
|
* variable reference (not a literal like "false", "true", "42", or a string). */
|
|
10
11
|
export function maybeEscapeResolvedText(text) {
|
|
@@ -54,16 +55,16 @@ function prefixOperatorText(operator) {
|
|
|
54
55
|
* keeps `this` out of the emitted free function. Returns null if the method
|
|
55
56
|
* is not a recognized inlinable getter.
|
|
56
57
|
*/
|
|
57
|
-
function inlineThisGetterCall(methodName, pin) {
|
|
58
|
+
function inlineThisGetterCall(methodName, pin, strategy) {
|
|
58
59
|
switch (methodName) {
|
|
59
60
|
case "read":
|
|
60
|
-
return `digitalRead(${pin})`;
|
|
61
|
+
return strategy?.readDigitalPin?.(pin) ?? `digitalRead(${pin})`;
|
|
61
62
|
case "readAnalog":
|
|
62
|
-
return `analogRead(${pin})`;
|
|
63
|
+
return strategy?.readAnalogPin?.(pin) ?? `analogRead(${pin})`;
|
|
63
64
|
case "isHigh":
|
|
64
|
-
return `digitalRead(${pin})`;
|
|
65
|
+
return strategy?.readDigitalPin?.(pin) ?? `digitalRead(${pin})`;
|
|
65
66
|
case "isLow":
|
|
66
|
-
return `(!digitalRead(${pin}))`;
|
|
67
|
+
return strategy?.readDigitalPin ? `(!${strategy.readDigitalPin(pin)})` : `(!digitalRead(${pin}))`;
|
|
67
68
|
default:
|
|
68
69
|
return null;
|
|
69
70
|
}
|
|
@@ -199,7 +200,7 @@ export function resolveExpressionText(expr, instance, paramNames, callArgTexts,
|
|
|
199
200
|
const methodName = expr.expression.name.text;
|
|
200
201
|
const pin = instance.fieldValues.get("_pin") ?? instance.fieldValues.get("pin");
|
|
201
202
|
if (pin !== undefined) {
|
|
202
|
-
const inlined = inlineThisGetterCall(methodName, pin);
|
|
203
|
+
const inlined = inlineThisGetterCall(methodName, pin, getContext().activeStrategy);
|
|
203
204
|
if (inlined !== null)
|
|
204
205
|
return inlined;
|
|
205
206
|
}
|
|
@@ -275,9 +276,22 @@ export function resolveExpressionText(expr, instance, paramNames, callArgTexts,
|
|
|
275
276
|
}
|
|
276
277
|
return expr.getText ? expr.getText() : null;
|
|
277
278
|
}
|
|
279
|
+
/** Semantic HAL helpers whose callbacks run in true ISR context (need IRAM_ATTR).
|
|
280
|
+
* WiFi event handlers, timers, setInterval, etc. use callback() too but run in
|
|
281
|
+
* a task / event loop — marking those as ISRs puts printf in IRAM and, on
|
|
282
|
+
* ESP-IDF, trips -Werror=attributes when IRAM_ATTR's __COUNTER__ disagrees
|
|
283
|
+
* between forward declaration and definition. */
|
|
284
|
+
const ISR_CALLBACK_SEMANTIC_FNS = new Set([
|
|
285
|
+
"interruptAttach",
|
|
286
|
+
]);
|
|
287
|
+
/** True when a HAL semantic call name wraps a GPIO/hardware ISR callback. */
|
|
288
|
+
export function semanticCallUsesIsrCallback(fnName) {
|
|
289
|
+
return ISR_CALLBACK_SEMANTIC_FNS.has(fnName);
|
|
290
|
+
}
|
|
278
291
|
/** Scan an expression AST for callback() calls, extract callback IR from callArgs,
|
|
279
|
-
* register them, and patch callArgTexts with placeholder names.
|
|
280
|
-
|
|
292
|
+
* register them, and patch callArgTexts with placeholder names.
|
|
293
|
+
* @param isInterruptHandler — only true for GPIO interrupt attach paths. */
|
|
294
|
+
export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgTexts, isInterruptHandler = false) {
|
|
281
295
|
function scan(node) {
|
|
282
296
|
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "callback") {
|
|
283
297
|
const cbArg = node.arguments[0];
|
|
@@ -292,6 +306,8 @@ export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgT
|
|
|
292
306
|
kind: "callback",
|
|
293
307
|
params: callbackIR.params.map((p) => p.name),
|
|
294
308
|
statements: callbackIR.body,
|
|
309
|
+
...(callbackIR.returnType && callbackIR.returnType !== "void" ? { returnType: callbackIR.returnType } : {}),
|
|
310
|
+
...(callbackIR.params ? { typedParams: callbackIR.params.map((p) => ({ name: p.name, cppType: p.cppType })).filter((p) => p.cppType && p.cppType !== "void") } : {}),
|
|
295
311
|
sourceSpan: callbackIR.body[0]?.sourceSpan ?? {
|
|
296
312
|
filePath: "",
|
|
297
313
|
startLine: 0,
|
|
@@ -301,10 +317,12 @@ export function extractAndRegisterCallbacks(expr, paramNames, callArgs, callArgT
|
|
|
301
317
|
startOffset: 0,
|
|
302
318
|
endOffset: 0,
|
|
303
319
|
},
|
|
304
|
-
isInterruptHandler: true,
|
|
320
|
+
...(isInterruptHandler ? { isInterruptHandler: true } : {}),
|
|
305
321
|
}
|
|
306
322
|
: callbackIR;
|
|
307
|
-
|
|
323
|
+
if (isInterruptHandler) {
|
|
324
|
+
normalized.isInterruptHandler = true;
|
|
325
|
+
}
|
|
308
326
|
registeredCallbacks.push({ placeholderName: placeholder, callbackIR: normalized });
|
|
309
327
|
callArgTexts[paramIdx] = placeholder;
|
|
310
328
|
}
|
|
@@ -399,8 +417,9 @@ export function processHALMethodBody(instance, methodName, callArgs) {
|
|
|
399
417
|
if (ts.isIdentifier(call.expression)) {
|
|
400
418
|
// Extract callbacks from semantic call arguments and patch callArgTexts
|
|
401
419
|
// with placeholder names before resolving (mirrors the emit() path).
|
|
420
|
+
const isIsr = semanticCallUsesIsrCallback(call.expression.text);
|
|
402
421
|
for (const arg of call.arguments) {
|
|
403
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
422
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
404
423
|
}
|
|
405
424
|
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
406
425
|
if (semanticOp) {
|
|
@@ -468,8 +487,9 @@ export function processHALMethodBody(instance, methodName, callArgs) {
|
|
|
468
487
|
actualRetExpr = actualRetExpr.expression;
|
|
469
488
|
}
|
|
470
489
|
if (ts.isCallExpression(actualRetExpr) && ts.isIdentifier(actualRetExpr.expression)) {
|
|
490
|
+
const isIsr = semanticCallUsesIsrCallback(actualRetExpr.expression.text);
|
|
471
491
|
for (const arg of actualRetExpr.arguments) {
|
|
472
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
492
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
473
493
|
}
|
|
474
494
|
const semanticOp = tryResolveSemanticCall(actualRetExpr.expression.text, actualRetExpr.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
475
495
|
if (semanticOp) {
|
|
@@ -594,8 +614,9 @@ export function processStatementList(stmts, instance, paramNames, callArgTexts,
|
|
|
594
614
|
}
|
|
595
615
|
else {
|
|
596
616
|
// R3: Try semantic HAL function call
|
|
617
|
+
const isIsr = semanticCallUsesIsrCallback(call.expression.text);
|
|
597
618
|
for (const arg of call.arguments) {
|
|
598
|
-
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts);
|
|
619
|
+
extractAndRegisterCallbacks(arg, paramNames, callArgs, callArgTexts, isIsr);
|
|
599
620
|
}
|
|
600
621
|
const semanticOp = tryResolveSemanticCall(call.expression.text, call.arguments, instance, paramNames, callArgTexts, paramDefaults, callArgs);
|
|
601
622
|
if (semanticOp) {
|
|
@@ -612,6 +633,10 @@ export function resetHALResolver() {
|
|
|
612
633
|
floatVariables.clear();
|
|
613
634
|
getContext().callbackPlaceholderCounter = 0;
|
|
614
635
|
getContext().snprintfCounter = 0;
|
|
636
|
+
// Reset the BLE characteristic counter so each file's GATT table starts at
|
|
637
|
+
// index 0. Without this, a second transpile in one process would continue
|
|
638
|
+
// from the previous file's counter and emit offset indices.
|
|
639
|
+
getContext().bleCharCounter = 0;
|
|
615
640
|
}
|
|
616
641
|
/** Resolve a hal-expr IR node to its C++ text using the active strategy. */
|
|
617
642
|
export function setActiveStrategy(strategy) {
|
|
@@ -696,14 +721,21 @@ export function buildSnprintfFromConcat(expr) {
|
|
|
696
721
|
else if (part.kind === "template_string" && part.expression.kind === "hal-expr") {
|
|
697
722
|
// HAL expression inside template literal — resolve via strategy
|
|
698
723
|
const resolved = resolveHALExprToText(part.expression);
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
724
|
+
const argText = resolved !== null ? resolved : text;
|
|
725
|
+
const cppType = cppTypeForHalOp(part.expression.operation.operation);
|
|
726
|
+
if (cppType === "const char*" || (cppType !== undefined && /char\s*\*$/.test(cppType))) {
|
|
727
|
+
formatString += "%s";
|
|
728
|
+
args.push(argText);
|
|
729
|
+
estimatedLength += 32;
|
|
730
|
+
}
|
|
731
|
+
else if (cppType === "bool") {
|
|
732
|
+
formatString += "%s";
|
|
733
|
+
args.push(`(${argText} ? "true" : "false")`);
|
|
734
|
+
estimatedLength += 5;
|
|
703
735
|
}
|
|
704
736
|
else {
|
|
705
737
|
formatString += "%d";
|
|
706
|
-
args.push(
|
|
738
|
+
args.push(argText);
|
|
707
739
|
estimatedLength += 12;
|
|
708
740
|
}
|
|
709
741
|
}
|
|
@@ -50,6 +50,12 @@ export declare function extractMethods(cls: ts.ClassDeclaration): Map<string, HA
|
|
|
50
50
|
export declare function loadHALModules(force?: boolean): void;
|
|
51
51
|
/** Get constructor includes for a HAL class. */
|
|
52
52
|
export declare function getCtorIncludes(className: string): string[];
|
|
53
|
+
/** Map an HttpClass factory method name to its HTTP verb, or null. */
|
|
54
|
+
export declare function httpFactoryVerb(methodName: string): string | null;
|
|
55
|
+
/** Render an Http factory URL argument as C++ expression text (string
|
|
56
|
+
* literals quoted, identifiers/member accesses verbatim), or null when the
|
|
57
|
+
* argument shape can't be resolved at compile time. */
|
|
58
|
+
export declare function httpUrlArgText(arg: ts.Expression): string | null;
|
|
53
59
|
/** Resolve a call receiver to a tracked HAL instance. */
|
|
54
60
|
export declare function resolveHALReceiver(receiver: ts.Expression): HALInstance | null;
|
|
55
61
|
/** Build field values from constructor arguments using the field mapping.
|
|
@@ -210,6 +210,30 @@ export function loadHALModules(force = false) {
|
|
|
210
210
|
export function getCtorIncludes(className) {
|
|
211
211
|
return halCtorIncludes.get(className) ?? [];
|
|
212
212
|
}
|
|
213
|
+
/** Map an HttpClass factory method name to its HTTP verb, or null. */
|
|
214
|
+
export function httpFactoryVerb(methodName) {
|
|
215
|
+
switch (methodName) {
|
|
216
|
+
case "get": return "GET";
|
|
217
|
+
case "post": return "POST";
|
|
218
|
+
case "put": return "PUT";
|
|
219
|
+
case "del": return "DELETE";
|
|
220
|
+
case "head": return "HEAD";
|
|
221
|
+
case "patch": return "PATCH";
|
|
222
|
+
default: return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Render an Http factory URL argument as C++ expression text (string
|
|
226
|
+
* literals quoted, identifiers/member accesses verbatim), or null when the
|
|
227
|
+
* argument shape can't be resolved at compile time. */
|
|
228
|
+
export function httpUrlArgText(arg) {
|
|
229
|
+
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))
|
|
230
|
+
return JSON.stringify(arg.text);
|
|
231
|
+
if (ts.isIdentifier(arg))
|
|
232
|
+
return arg.text;
|
|
233
|
+
if (ts.isPropertyAccessExpression(arg))
|
|
234
|
+
return arg.getText();
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
213
237
|
/** Resolve a call receiver to a tracked HAL instance. */
|
|
214
238
|
export function resolveHALReceiver(receiver) {
|
|
215
239
|
const result = (() => {
|
|
@@ -336,6 +360,58 @@ export function resolveHALReceiver(receiver) {
|
|
|
336
360
|
return { className: "ToneChain", fieldValues };
|
|
337
361
|
}
|
|
338
362
|
}
|
|
363
|
+
// Specialized handling for Http factory chaining
|
|
364
|
+
// (Http.get(url).header(...).send()): the factory records the HTTP
|
|
365
|
+
// verb and URL into the HttpRequest instance so send() can resolve
|
|
366
|
+
// this._method / this._url.
|
|
367
|
+
if (innerInstance.className === "HttpClass") {
|
|
368
|
+
const verb = httpFactoryVerb(methodName);
|
|
369
|
+
if (verb && receiver.arguments.length > 0) {
|
|
370
|
+
const urlText = httpUrlArgText(receiver.arguments[0]);
|
|
371
|
+
if (urlText) {
|
|
372
|
+
return {
|
|
373
|
+
className: "HttpRequest",
|
|
374
|
+
fieldValues: new Map([["_method", verb], ["_url", urlText]]),
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
// Specialized handling for BLE factory chaining
|
|
380
|
+
// (Ble.server(name).characteristic(uuid,type,perms).onRead(handler)):
|
|
381
|
+
// server() creates a BleServer. The characteristic index is taken from a
|
|
382
|
+
// per-file counter (bleCharCounter on CompilationContext) that persists
|
|
383
|
+
// across separate server() calls so a multi-characteristic server — the
|
|
384
|
+
// common ble-demo pattern of one Ble.server(name).characteristic(...) per
|
|
385
|
+
// char — gets unique sequential indices (0,1,2,…) instead of every char
|
|
386
|
+
// landing on slot 0 and clobbering the previous on_read/on_write handler.
|
|
387
|
+
// characteristic() reads the current counter for the add_char call and
|
|
388
|
+
// posts-increments it so the next characteristic gets the next slot.
|
|
389
|
+
// onRead/onWrite/onNotify carry _lastChar (the slot characteristic() just
|
|
390
|
+
// reserved). The counter resets per file in hal-emitter.ts.
|
|
391
|
+
if (innerInstance.className === "BleClass" && methodName === "server" && receiver.arguments.length > 0) {
|
|
392
|
+
const nameText = httpUrlArgText(receiver.arguments[0]);
|
|
393
|
+
if (nameText) {
|
|
394
|
+
return {
|
|
395
|
+
className: "BleServer",
|
|
396
|
+
fieldValues: new Map([["_name", nameText], ["_charCount", "0"], ["_lastChar", "0"], ["_svcCount", "1"]]),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (innerInstance.className === "BleServer" && methodName === "characteristic") {
|
|
401
|
+
// characteristic(uuid, type, perms) returns this (BleServer). The
|
|
402
|
+
// characteristic index is assigned centrally in the bleAddChar plugin
|
|
403
|
+
// (hal-plugins.ts) from a per-file counter — see the note there for
|
|
404
|
+
// why this can't be done via instance fieldValues (resolveHALReceiver
|
|
405
|
+
// re-resolves the receiver for each chain level and would drop a
|
|
406
|
+
// value stamped here).
|
|
407
|
+
return innerInstance;
|
|
408
|
+
}
|
|
409
|
+
if (innerInstance.className === "BleServer" && methodName === "service") {
|
|
410
|
+
// service() returns this (BleServer); advance the service counter.
|
|
411
|
+
const svc = Number(innerInstance.fieldValues.get("_svcCount") ?? "1") + 1;
|
|
412
|
+
innerInstance.fieldValues.set("_svcCount", String(svc));
|
|
413
|
+
return innerInstance;
|
|
414
|
+
}
|
|
339
415
|
// General fallback: if the method is known to return another HAL class, carry over fields
|
|
340
416
|
const classEntry = halClassRegistry.get(innerInstance.className);
|
|
341
417
|
const methodEntry = classEntry?.methods.get(methodName);
|
|
@@ -345,6 +421,11 @@ export function resolveHALReceiver(receiver) {
|
|
|
345
421
|
return { className: returnClassName, fieldValues: new Map(innerInstance.fieldValues) };
|
|
346
422
|
}
|
|
347
423
|
}
|
|
424
|
+
// Fluent `this`-returning methods (e.g. HttpRequest.header/timeout):
|
|
425
|
+
// the chain result is the same instance, so keep resolving through it.
|
|
426
|
+
if (methodEntry && methodEntry.methodNode.type && methodEntry.methodNode.type.kind === ts.SyntaxKind.ThisType) {
|
|
427
|
+
return innerInstance;
|
|
428
|
+
}
|
|
348
429
|
}
|
|
349
430
|
}
|
|
350
431
|
// Inline new expression: new Pin(x).method()
|