@typecad/cuttlefish 1.0.0-alpha.7 → 1.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +2 -2
- package/dist/api/shared/display-adapter.js +9 -119
- package/dist/api/shared/display-adapters/sdl.js +29 -29
- package/dist/api/shared/display-profile.d.ts +38 -33
- package/dist/api/shared/display-profile.js +22 -170
- package/dist/api/shared/framework-manifest-registry.d.ts +1 -1
- package/dist/api/shared/framework-manifest-registry.js +8 -6
- package/dist/api/shared/framework-manifest.d.ts +147 -28
- package/dist/api/shared/framework-manifest.js +35 -2
- package/dist/api/shared/graphics-strategy.d.ts +15 -6
- package/dist/api/shared/hal-op-ir.d.ts +340 -3
- package/dist/api/shared/hal-op-ir.js +34 -0
- package/dist/api/shared/index.d.ts +9 -1
- package/dist/api/shared/index.js +11 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/platform-strategy.d.ts +104 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +17 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/validate-framework-manifest.js +77 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli.js +125 -121
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +20 -19
- package/dist/config-schema.d.ts +107 -65
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +74 -77
- package/dist/create/board-spec.js +0 -1
- package/dist/create/init-scaffold.d.ts +7 -3
- package/dist/create/init-scaffold.js +0 -62
- package/dist/create/init-templates.js +62 -16
- package/dist/create/init-wizard.js +32 -27
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +23 -1
- package/dist/emit/emitters/function-emitter-impl.js +96 -6
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.js +80 -1
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +217 -36
- package/dist/emit/emitters/top-level-prep.js +6 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +17 -8
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +81 -5
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +7 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +21 -5
- package/dist/emit/utils/async-state-machine.js +39 -12
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +14 -1
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +4 -4
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +150 -0
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.js +10 -6
- package/dist/ir/hal/hal-parser.js +16 -9
- package/dist/ir/hal/hal-plugins.js +164 -3
- package/dist/ir/identifier-collector.js +21 -4
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +8 -16
- package/dist/ir/ownership-analysis.js +6 -0
- package/dist/ir/pin-capability-validation.js +37 -0
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +49 -18
- package/dist/ir/program-analysis.d.ts +42 -0
- package/dist/ir/program-analysis.js +243 -6
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/timing-validation.js +11 -2
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +4 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +51 -0
- package/dist/ir/type-resolution.js +18 -0
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +6 -1
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/orchestrator/graph-builder.js +8 -7
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/stores/display-profile-store.d.ts +0 -1
- package/dist/stores/display-profile-store.js +0 -1
- package/dist/testing.d.ts +4 -0
- package/dist/testing.js +5 -0
- package/dist/transpile.js +124 -35
- package/dist/types.d.ts +22 -2
- package/dist/ui-hook.d.ts +0 -2
- package/dist/utils/cli.d.ts +2 -2
- package/dist/utils/cli.js +68 -3
- package/package.json +20 -5
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/libdef/idf-discovery.d.ts +0 -7
- package/dist/libdef/idf-discovery.js +0 -59
- package/dist/licenses.d.ts +0 -185
- package/dist/licenses.js +0 -963
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { getCurrentBoardConstants, halInstances } from "../build-ir-state.js";
|
|
2
|
+
import { getCurrentBoardConstants, halInstances, getContext } from "../build-ir-state.js";
|
|
3
3
|
import { resolveExpressionText } from "./hal-emitter.js";
|
|
4
4
|
import { renderExprAsText } from "../render-expr.js";
|
|
5
|
+
import { hasSafetyHook, requireSafetyHook } from "../../safety-hook.js";
|
|
5
6
|
/**
|
|
6
7
|
* Split a comma-joined argument list back into individual arguments, respecting
|
|
7
8
|
* nesting (parens/brackets/braces) and string literals so a comma inside one of
|
|
@@ -303,6 +304,17 @@ export function tryResolveCompoundSemanticReturn(expr, instance, paramNames, cal
|
|
|
303
304
|
* Returns the HALOpIR if the function name is recognized, or null.
|
|
304
305
|
*/
|
|
305
306
|
export function tryResolveSemanticCall(fnName, args, instance, paramNames, callArgTexts, paramDefaults, callArgs) {
|
|
307
|
+
// ── Safety (@typecad/safety — safe.read) ────────────────────────────────
|
|
308
|
+
// The safety package owns lowering for `safe.*` callees (v2: only safe.read;
|
|
309
|
+
// safe.pinMode was removed — mode config is the HAL's job via Pin.asInput()
|
|
310
|
+
// etc.). Check the hook before the switch so safety calls never fall
|
|
311
|
+
// through to GPIO/etc. hasSafetyHook() guards the call site (no-op when
|
|
312
|
+
// @typecad/safety absent).
|
|
313
|
+
if (hasSafetyHook()) {
|
|
314
|
+
const op = requireSafetyHook().resolveSemanticCall?.(fnName, callArgs ?? []);
|
|
315
|
+
if (op)
|
|
316
|
+
return op;
|
|
317
|
+
}
|
|
306
318
|
// Extract MCU port name from instance (set by Pin.fromPort())
|
|
307
319
|
const port = portFromInstance(instance);
|
|
308
320
|
switch (fnName) {
|
|
@@ -774,7 +786,18 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
|
|
|
774
786
|
return { operation: "ble.add_service", uuid: quoteNonIdentifier(uuid) };
|
|
775
787
|
}
|
|
776
788
|
case "bleAddChar": {
|
|
777
|
-
|
|
789
|
+
// The characteristic index is assigned HERE from a per-file counter, not
|
|
790
|
+
// resolved from `this._charCount`. resolveHALReceiver re-resolves the
|
|
791
|
+
// receiver (Ble.server(name)) freshly for each chain level when emitting,
|
|
792
|
+
// so a value stamped onto the instance field in the resolver would be
|
|
793
|
+
// dropped — leaving every characteristic on slot 0, clobbering the
|
|
794
|
+
// previous on_read/on_write handler. Consuming the counter at the single
|
|
795
|
+
// emit site (this plugin, called once per characteristic in declaration
|
|
796
|
+
// order) makes indices sequential (0,1,2,…) and stable. The counter is
|
|
797
|
+
// per-file (reset in resetHALResolver). onRead/onWrite/onConnect don't
|
|
798
|
+
// need a compile-time index — the C++ shim keys them off
|
|
799
|
+
// __tc_ble.current_char, which __tc_ble_add_char sets at runtime from
|
|
800
|
+
// this index.
|
|
778
801
|
const uuid = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
779
802
|
const type = resolveSemanticArg(args, 2, instance, paramNames, callArgTexts, paramDefaults);
|
|
780
803
|
const perms = resolveNumericOrExpression(args, 3, instance, paramNames, callArgTexts, paramDefaults);
|
|
@@ -792,7 +815,7 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
|
|
|
792
815
|
const resolvedPerms = resolveBlePermExpr(perms);
|
|
793
816
|
return {
|
|
794
817
|
operation: "ble.add_char",
|
|
795
|
-
index:
|
|
818
|
+
index: getContext().bleCharCounter++,
|
|
796
819
|
uuid: quoteNonIdentifier(uuid),
|
|
797
820
|
type: resolvedType,
|
|
798
821
|
perms: resolvedPerms,
|
|
@@ -944,6 +967,144 @@ export function tryResolveSemanticCall(fnName, args, instance, paramNames, callA
|
|
|
944
967
|
return null;
|
|
945
968
|
return { operation: "preferences.get_string", key: quoteNonIdentifier(key), defaultValue: quoteNonIdentifier(def) };
|
|
946
969
|
}
|
|
970
|
+
// ── FS (filesystem) ──
|
|
971
|
+
case "fsBegin":
|
|
972
|
+
return { operation: "fs.begin" };
|
|
973
|
+
case "fsReadText": {
|
|
974
|
+
const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
975
|
+
if (path === null)
|
|
976
|
+
return null;
|
|
977
|
+
return { operation: "fs.read_text", path };
|
|
978
|
+
}
|
|
979
|
+
case "fsWriteText": {
|
|
980
|
+
const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
981
|
+
const content = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
982
|
+
if (path === null || content === null)
|
|
983
|
+
return null;
|
|
984
|
+
return { operation: "fs.write_text", path, content };
|
|
985
|
+
}
|
|
986
|
+
case "fsExists": {
|
|
987
|
+
const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
988
|
+
if (path === null)
|
|
989
|
+
return null;
|
|
990
|
+
return { operation: "fs.exists", path };
|
|
991
|
+
}
|
|
992
|
+
case "fsRemove": {
|
|
993
|
+
const path = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
994
|
+
if (path === null)
|
|
995
|
+
return null;
|
|
996
|
+
return { operation: "fs.remove", path };
|
|
997
|
+
}
|
|
998
|
+
// ── mDNS ──
|
|
999
|
+
case "mdnsStart": {
|
|
1000
|
+
const hostname = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1001
|
+
if (hostname === null)
|
|
1002
|
+
return null;
|
|
1003
|
+
return { operation: "mdns.start", hostname };
|
|
1004
|
+
}
|
|
1005
|
+
case "mdnsSetHostname": {
|
|
1006
|
+
const name = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1007
|
+
if (name === null)
|
|
1008
|
+
return null;
|
|
1009
|
+
return { operation: "mdns.set_hostname", name };
|
|
1010
|
+
}
|
|
1011
|
+
case "mdnsAddService": {
|
|
1012
|
+
const instanceName = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1013
|
+
const proto = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
1014
|
+
const port = resolveNumericOrExpression(args, 2, instance, paramNames, callArgTexts, paramDefaults);
|
|
1015
|
+
if (instanceName === null || proto === null || port === null)
|
|
1016
|
+
return null;
|
|
1017
|
+
return { operation: "mdns.add_service", instance: instanceName, proto, port };
|
|
1018
|
+
}
|
|
1019
|
+
case "mdnsAnnounce":
|
|
1020
|
+
return { operation: "mdns.announce" };
|
|
1021
|
+
case "mdnsStop":
|
|
1022
|
+
return { operation: "mdns.stop" };
|
|
1023
|
+
// ── MQTT ──
|
|
1024
|
+
case "mqttConnect": {
|
|
1025
|
+
const brokerUri = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1026
|
+
const clientId = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
1027
|
+
if (brokerUri === null || clientId === null)
|
|
1028
|
+
return null;
|
|
1029
|
+
return { operation: "mqtt.connect", brokerUri, clientId };
|
|
1030
|
+
}
|
|
1031
|
+
case "mqttOnMessage": {
|
|
1032
|
+
const handler = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1033
|
+
if (handler === null)
|
|
1034
|
+
return null;
|
|
1035
|
+
return { operation: "mqtt.on_message", handler };
|
|
1036
|
+
}
|
|
1037
|
+
case "mqttSubscribe": {
|
|
1038
|
+
const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1039
|
+
if (topic === null)
|
|
1040
|
+
return null;
|
|
1041
|
+
return { operation: "mqtt.subscribe", topic };
|
|
1042
|
+
}
|
|
1043
|
+
case "mqttPublish": {
|
|
1044
|
+
const topic = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1045
|
+
const data = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
1046
|
+
if (topic === null || data === null)
|
|
1047
|
+
return null;
|
|
1048
|
+
return { operation: "mqtt.publish", topic, data };
|
|
1049
|
+
}
|
|
1050
|
+
case "mqttConnected":
|
|
1051
|
+
return { operation: "mqtt.connected" };
|
|
1052
|
+
case "mqttDisconnect":
|
|
1053
|
+
return { operation: "mqtt.disconnect" };
|
|
1054
|
+
// ── OTA ──
|
|
1055
|
+
case "otaFromUrl": {
|
|
1056
|
+
const url = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1057
|
+
if (url === null)
|
|
1058
|
+
return null;
|
|
1059
|
+
return { operation: "ota.from_url", url };
|
|
1060
|
+
}
|
|
1061
|
+
case "otaBegin":
|
|
1062
|
+
return { operation: "ota.begin" };
|
|
1063
|
+
case "otaWrite": {
|
|
1064
|
+
const chunk = resolveSemanticArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1065
|
+
if (chunk === null)
|
|
1066
|
+
return null;
|
|
1067
|
+
return { operation: "ota.write", chunk };
|
|
1068
|
+
}
|
|
1069
|
+
case "otaApply":
|
|
1070
|
+
return { operation: "ota.apply" };
|
|
1071
|
+
// ── Temperature (die temp) ──
|
|
1072
|
+
case "tempRead":
|
|
1073
|
+
return { operation: "temp.read" };
|
|
1074
|
+
// ── Hardware timer (GPTimer) ──
|
|
1075
|
+
case "hwtimerSetFrequency": {
|
|
1076
|
+
const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1077
|
+
const hz = resolveNumericOrExpression(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
1078
|
+
if (inst === null || hz === null)
|
|
1079
|
+
return null;
|
|
1080
|
+
return { operation: "hwtimer.set_frequency", instance: inst, hz };
|
|
1081
|
+
}
|
|
1082
|
+
case "hwtimerOnOverflow": {
|
|
1083
|
+
const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1084
|
+
const handler = resolveSemanticArg(args, 1, instance, paramNames, callArgTexts, paramDefaults);
|
|
1085
|
+
if (inst === null || handler === null)
|
|
1086
|
+
return null;
|
|
1087
|
+
return { operation: "hwtimer.on_overflow", instance: inst, handler };
|
|
1088
|
+
}
|
|
1089
|
+
case "hwtimerStart": {
|
|
1090
|
+
const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1091
|
+
if (inst === null)
|
|
1092
|
+
return null;
|
|
1093
|
+
return { operation: "hwtimer.start", instance: inst };
|
|
1094
|
+
}
|
|
1095
|
+
case "hwtimerStop": {
|
|
1096
|
+
const inst = resolveNumericOrExpression(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1097
|
+
if (inst === null)
|
|
1098
|
+
return null;
|
|
1099
|
+
return { operation: "hwtimer.stop", instance: inst };
|
|
1100
|
+
}
|
|
1101
|
+
// ── Capacitive touch pins ──
|
|
1102
|
+
case "capacitiveRead": {
|
|
1103
|
+
const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
1104
|
+
if (pin === null)
|
|
1105
|
+
return null;
|
|
1106
|
+
return { operation: "capacitive.read", port, pin };
|
|
1107
|
+
}
|
|
947
1108
|
// ── Interrupts ──
|
|
948
1109
|
case "interruptAttach": {
|
|
949
1110
|
const pin = resolveNumericArg(args, 0, instance, paramNames, callArgTexts, paramDefaults);
|
|
@@ -423,6 +423,22 @@ function collectHALOpIdentifiers(op) {
|
|
|
423
423
|
// handler is a resolved C++ function name (e.g. "myIsr" or a placeholder)
|
|
424
424
|
identifiers.add(op.handler);
|
|
425
425
|
break;
|
|
426
|
+
case "mqtt.on_message":
|
|
427
|
+
// handler is the user's onMessage callback name (callback() resolves it to
|
|
428
|
+
// a string). Without this the tree-shaker drops the function declaration —
|
|
429
|
+
// the mqtt.on_message op only carries the name, so the function has no
|
|
430
|
+
// other reference and looks unreachable. Same shape as interrupt.attach.
|
|
431
|
+
identifiers.add(op.handler);
|
|
432
|
+
break;
|
|
433
|
+
case "wifi.on_event":
|
|
434
|
+
identifiers.add(op.handler);
|
|
435
|
+
break;
|
|
436
|
+
case "ble.on_read":
|
|
437
|
+
case "ble.on_write":
|
|
438
|
+
case "ble.on_connect":
|
|
439
|
+
case "ble.on_disconnect":
|
|
440
|
+
identifiers.add(op.handler);
|
|
441
|
+
break;
|
|
426
442
|
case "raw": {
|
|
427
443
|
// Raw C++ code may reference user-defined identifiers
|
|
428
444
|
const matches = op.code.match(/[A-Za-z_][A-Za-z0-9_]*/g);
|
|
@@ -435,11 +451,12 @@ function collectHALOpIdentifiers(op) {
|
|
|
435
451
|
}
|
|
436
452
|
// Other HAL ops have only numeric/literal fields — no identifier references
|
|
437
453
|
}
|
|
438
|
-
// wifi.* / http.* ops carry resolved C++ expression texts in their
|
|
439
|
-
// fields (e.g. wifi.connect ssid: `WIFI_SSID` — a top-level const, or
|
|
440
|
-
// quoted literal). Scan every string field for identifiers so referenced
|
|
454
|
+
// wifi.* / http.* / mqtt.* ops carry resolved C++ expression texts in their
|
|
455
|
+
// string fields (e.g. wifi.connect ssid: `WIFI_SSID` — a top-level const, or
|
|
456
|
+
// a quoted literal). Scan every string field for identifiers so referenced
|
|
441
457
|
// globals survive tree-shaking; quoted literals are skipped.
|
|
442
|
-
if (op.operation.startsWith("wifi.") || op.operation.startsWith("http.")
|
|
458
|
+
if (op.operation.startsWith("wifi.") || op.operation.startsWith("http.")
|
|
459
|
+
|| op.operation.startsWith("mqtt.")) {
|
|
443
460
|
for (const value of Object.values(op)) {
|
|
444
461
|
if (typeof value !== "string" || value === op.operation)
|
|
445
462
|
continue;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProgramIR } from '../api/index.js';
|
|
1
|
+
import type { ProgramIR, StatementIR } from '../api/index.js';
|
|
2
2
|
import type { PeripheralUsage } from './peripheral-usage.js';
|
|
3
3
|
import type { Diagnostic } from '../types.js';
|
|
4
4
|
export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
|
|
@@ -24,6 +24,10 @@ export interface InterruptHandlerInfo {
|
|
|
24
24
|
* @returns Array of diagnostics for interrupt-related issues
|
|
25
25
|
*/
|
|
26
26
|
export declare function analyzeInterruptSafety(program: ProgramIR, usage: PeripheralUsage, isrUnsafeOps?: Map<string, IsrUnsafeOp>): Diagnostic[];
|
|
27
|
+
/** Collect variable names assigned (written) within a statement body. */
|
|
28
|
+
export declare function collectAssignedNames(stmts: readonly StatementIR[], names: Set<string>): void;
|
|
29
|
+
/** Collect variable names read (referenced) within a statement body. */
|
|
30
|
+
export declare function collectReadNames(stmts: readonly StatementIR[], names: Set<string>): void;
|
|
27
31
|
/**
|
|
28
32
|
* Infer `volatile` for global variables shared between ISR callbacks and main
|
|
29
33
|
* code. Mutates the IR (sets isVolatile=true on qualifying var_decls) and
|
|
@@ -7,21 +7,13 @@
|
|
|
7
7
|
import { walkNestedStatements, walkExpressionsInExpression } from './utils/walk-ir.js';
|
|
8
8
|
export { walkNestedStatements as scanNestedStatements } from './utils/walk-ir.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Fallback ISR-unsafe operations used when no platform-specific map is provided.
|
|
11
|
+
* Empty by design — the Wiring-derived specifics (delay, Serial.*, I2C*, SPI*)
|
|
12
|
+
* live in ArduinoStrategy.isrUnsafeOperations(). A generic target has no known
|
|
13
|
+
* ISR-unsafe surface. The orchestrator passes the loaded framework's map; this
|
|
14
|
+
* default only applies when none is supplied.
|
|
12
15
|
*/
|
|
13
|
-
const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map(
|
|
14
|
-
['delay', { reason: 'delay() blocks the CPU and should not be used in interrupt context', severity: 'warning' }],
|
|
15
|
-
['delayMicroseconds', { reason: 'delayMicroseconds() blocks and should be avoided in ISRs', severity: 'warning' }],
|
|
16
|
-
['Serial.print', { reason: 'Serial.print() may not work correctly in interrupt context', severity: 'info' }],
|
|
17
|
-
['Serial.println', { reason: 'Serial.println() may not work correctly in interrupt context', severity: 'info' }],
|
|
18
|
-
['Serial.write', { reason: 'Serial.write() may not work correctly in interrupt context', severity: 'info' }],
|
|
19
|
-
['Serial.read', { reason: 'Serial.read() may not work correctly in interrupt context', severity: 'info' }],
|
|
20
|
-
['I2C0', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
|
|
21
|
-
['I2C1', { reason: 'I2C operations can cause lockups in interrupt context', severity: 'warning' }],
|
|
22
|
-
['SPI0', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
|
|
23
|
-
['SPI1', { reason: 'SPI operations may cause issues in interrupt context', severity: 'info' }],
|
|
24
|
-
]);
|
|
16
|
+
const DEFAULT_ISR_UNSAFE_OPERATIONS = new Map();
|
|
25
17
|
/**
|
|
26
18
|
* Analyze a program for interrupt handler issues.
|
|
27
19
|
*
|
|
@@ -243,7 +235,7 @@ function scanExpressionForCallbacks(expr, callback, parentExpr) {
|
|
|
243
235
|
// emission path already handles isVolatile (statement-renderer.ts).
|
|
244
236
|
// ---------------------------------------------------------------------------
|
|
245
237
|
/** Collect variable names assigned (written) within a statement body. */
|
|
246
|
-
function collectAssignedNames(stmts, names) {
|
|
238
|
+
export function collectAssignedNames(stmts, names) {
|
|
247
239
|
for (const stmt of stmts) {
|
|
248
240
|
const s = stmt;
|
|
249
241
|
if (stmt.kind === 'assign') {
|
|
@@ -265,7 +257,7 @@ function collectAssignedNames(stmts, names) {
|
|
|
265
257
|
}
|
|
266
258
|
}
|
|
267
259
|
/** Collect variable names read (referenced) within a statement body. */
|
|
268
|
-
function collectReadNames(stmts, names) {
|
|
260
|
+
export function collectReadNames(stmts, names) {
|
|
269
261
|
for (const stmt of stmts) {
|
|
270
262
|
walkExpressionsInStatement(stmt, (expr) => {
|
|
271
263
|
if (expr.kind === 'identifier' && typeof expr.value === 'string') {
|
|
@@ -765,6 +765,12 @@ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
|
|
|
765
765
|
analyzeExpression(expr.inner, scope, diagnostics, span);
|
|
766
766
|
break;
|
|
767
767
|
}
|
|
768
|
+
case 'call': {
|
|
769
|
+
for (const arg of expr.args) {
|
|
770
|
+
analyzeExpression(arg, scope, diagnostics, span);
|
|
771
|
+
}
|
|
772
|
+
break;
|
|
773
|
+
}
|
|
768
774
|
case 'number':
|
|
769
775
|
case 'string':
|
|
770
776
|
case 'boolean':
|
|
@@ -77,6 +77,35 @@ function findPinsWithCapability(capability, boardConstants) {
|
|
|
77
77
|
}
|
|
78
78
|
return result;
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Does the board declare ANY per-pin `type:'pwm'` function entry anywhere?
|
|
82
|
+
*
|
|
83
|
+
* AVR-family boards (atmega328p, etc.) hardwire PWM to specific timer-output
|
|
84
|
+
* pins, so their MCU descriptors list `{ type:'pwm', ... }` per pin and the
|
|
85
|
+
* capability check correctly rejects PWM on non-timer pins. ESP32-family
|
|
86
|
+
* boards route LEDC to ANY output GPIO via the GPIO matrix, so their MCU
|
|
87
|
+
* descriptors carry zero `type:'pwm'` entries — PWM is a board-wide property
|
|
88
|
+
* (the `pins.pwm` collection), not a per-pin function. On such boards the
|
|
89
|
+
* per-pin capability check would false-positive on every pin, so the caller
|
|
90
|
+
* skips it when this returns false. This distinguishes the two families
|
|
91
|
+
* cleanly (AVR has entries, ESP32 has none) without special-casing arch names.
|
|
92
|
+
*/
|
|
93
|
+
function boardHasAnyPwmFunctionEntries(boardConstants) {
|
|
94
|
+
if (!boardConstants)
|
|
95
|
+
return false;
|
|
96
|
+
for (let i = 0; i < 100; i++) {
|
|
97
|
+
if (boardConstants.get(`pins.all.${i}.name`) === undefined)
|
|
98
|
+
continue;
|
|
99
|
+
for (let j = 0; j < 10; j++) {
|
|
100
|
+
const type = boardConstants.get(`pins.all.${i}.functions.${j}.type`);
|
|
101
|
+
if (type === undefined)
|
|
102
|
+
break;
|
|
103
|
+
if (String(type) === 'pwm')
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
80
109
|
// ---------------------------------------------------------------------------
|
|
81
110
|
// Capability check for a single HAL operation
|
|
82
111
|
// ---------------------------------------------------------------------------
|
|
@@ -88,6 +117,13 @@ function checkCapability(operation, sourceLine, sourceCol, filePath, boardConsta
|
|
|
88
117
|
const capability = operationToCapability(op);
|
|
89
118
|
if (!capability)
|
|
90
119
|
return; // GPIO/timing — always available.
|
|
120
|
+
// ESP32-family boards route PWM (LEDC) to any output GPIO via the GPIO
|
|
121
|
+
// matrix, so they carry zero per-pin `type:'pwm'` entries (unlike AVR,
|
|
122
|
+
// which hardwires PWM to timer-output pins). On such boards the per-pin PWM
|
|
123
|
+
// capability check would reject every pin; skip it and let the framework's
|
|
124
|
+
// LEDC driver handle routing. This does NOT weaken AVR, which has entries.
|
|
125
|
+
if (capability === 'pwm' && !boardHasAnyPwmFunctionEntries(boardConstants))
|
|
126
|
+
return;
|
|
91
127
|
const pin = operation.pin;
|
|
92
128
|
if (typeof pin !== 'number' || pin < 0)
|
|
93
129
|
return;
|
|
@@ -250,6 +286,7 @@ function scanExpression(expr, boardConstants, parentLine, parentCol, parentFileP
|
|
|
250
286
|
case 'identifier':
|
|
251
287
|
case 'raw':
|
|
252
288
|
case 'tuple-access':
|
|
289
|
+
case 'call':
|
|
253
290
|
break;
|
|
254
291
|
default:
|
|
255
292
|
assertNever(expr);
|
|
@@ -4,7 +4,7 @@ import type { Diagnostic } from '../types.js';
|
|
|
4
4
|
* Validate that GPIO I/O operations are preceded by mode configuration.
|
|
5
5
|
*
|
|
6
6
|
* - Read without prior mode → **warning** (undefined behavior on floating pin)
|
|
7
|
-
* - Write without prior mode → **info** (
|
|
8
|
-
* explicit configuration is recommended)
|
|
7
|
+
* - Write without prior mode → **info** (Wiring-derived frameworks implicitly
|
|
8
|
+
* set OUTPUT, but explicit configuration is recommended)
|
|
9
9
|
*/
|
|
10
10
|
export declare function validatePinModeConfig(program: ProgramIR): Diagnostic[];
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// configuration. Generates warnings for reads and info for writes.
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
7
7
|
import { scanNestedStatements } from './interrupt-analysis.js';
|
|
8
|
+
import { hasLoadedFramework, getLoadedFramework } from '../framework-registry.js';
|
|
8
9
|
/** Methods that configure pin mode. */
|
|
9
10
|
const MODE_SET_METHODS = new Set([
|
|
10
11
|
'inputPullUp', 'inputPullDown', 'outputOpenDrain',
|
|
@@ -14,7 +15,7 @@ const MODE_SET_METHODS = new Set([
|
|
|
14
15
|
const READ_METHODS = new Set([
|
|
15
16
|
'read', 'isHigh', 'isLow', 'readAnalog', 'readVoltage',
|
|
16
17
|
]);
|
|
17
|
-
/** Write operations that implicitly set OUTPUT mode on
|
|
18
|
+
/** Write operations that implicitly set OUTPUT mode on Wiring-derived frameworks. */
|
|
18
19
|
const WRITE_METHODS = new Set([
|
|
19
20
|
'write', 'high', 'low', 'toggle', 'pulse', 'pwm', 'tone',
|
|
20
21
|
]);
|
|
@@ -26,8 +27,8 @@ const PIN_RECEIVER_KINDS = new Set([
|
|
|
26
27
|
* Validate that GPIO I/O operations are preceded by mode configuration.
|
|
27
28
|
*
|
|
28
29
|
* - Read without prior mode → **warning** (undefined behavior on floating pin)
|
|
29
|
-
* - Write without prior mode → **info** (
|
|
30
|
-
* explicit configuration is recommended)
|
|
30
|
+
* - Write without prior mode → **info** (Wiring-derived frameworks implicitly
|
|
31
|
+
* set OUTPUT, but explicit configuration is recommended)
|
|
31
32
|
*/
|
|
32
33
|
export function validatePinModeConfig(program) {
|
|
33
34
|
const diagnostics = [];
|
|
@@ -65,7 +66,7 @@ export function validatePinModeConfig(program) {
|
|
|
65
66
|
diagnostics.push({
|
|
66
67
|
severity: 'info',
|
|
67
68
|
message: `Pin '${receiver}' written via '${method}()' without explicit mode configuration. ` +
|
|
68
|
-
`
|
|
69
|
+
`The target framework implicitly sets OUTPUT, but explicit ${receiver}.asOutput() is recommended.`,
|
|
69
70
|
filePath: program.fileName,
|
|
70
71
|
code: 'pin-mode-not-set',
|
|
71
72
|
source: 'pin-mode-validation',
|
|
@@ -112,7 +113,7 @@ export function validatePinModeConfig(program) {
|
|
|
112
113
|
diagnostics.push({
|
|
113
114
|
severity: 'info',
|
|
114
115
|
message: `Pin ${op.pin} written without explicit mode configuration. ` +
|
|
115
|
-
`
|
|
116
|
+
`The target framework implicitly sets OUTPUT, but explicit asOutput() is recommended.`,
|
|
116
117
|
filePath: program.fileName,
|
|
117
118
|
code: 'pin-mode-not-set',
|
|
118
119
|
source: 'pin-mode-validation',
|
|
@@ -148,21 +149,51 @@ export function validatePinModeConfig(program) {
|
|
|
148
149
|
// Raw nodes: after full HAL resolution, some pin ops land as their C++ text
|
|
149
150
|
// (e.g. `digitalRead(2)`, `digitalWrite(2, 1)`) rather than structured
|
|
150
151
|
// hal-expr nodes. Extract the pin number and apply the same mode check.
|
|
151
|
-
//
|
|
152
|
+
// Detect GPIO ops in lowered raw C++ text. Build the read/write name
|
|
153
|
+
// patterns from the loaded framework's HAL vocabulary (halCallNames),
|
|
154
|
+
// classifying names containing "Read" as reads and "Write" as writes (the
|
|
155
|
+
// Wiring-derived convention). Seed with digitalRead/digitalWrite as the
|
|
156
|
+
// canonical Wiring forms so the validator works even when no framework is
|
|
157
|
+
// loaded. Non-Wiring frameworks that emit other call forms register them
|
|
158
|
+
// via halCallNames; this loop picks them up.
|
|
152
159
|
if (e.kind === 'raw' && typeof e.value === 'string') {
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
160
|
+
const frameworkHalNames = hasLoadedFramework()
|
|
161
|
+
? getLoadedFramework().strategy.halCallNames?.()
|
|
162
|
+
: undefined;
|
|
163
|
+
const halNames = frameworkHalNames ?? new Set(['digitalRead', 'digitalWrite']);
|
|
164
|
+
const readNames = [...halNames].filter(n => /read/i.test(n));
|
|
165
|
+
const writeNames = [...halNames].filter(n => /write/i.test(n));
|
|
166
|
+
let pin = null;
|
|
167
|
+
let op = null;
|
|
168
|
+
for (const rn of readNames) {
|
|
169
|
+
const esc = rn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
170
|
+
const m = e.value.match(new RegExp(`${esc}\\((\\d+)\\)`));
|
|
171
|
+
if (m) {
|
|
172
|
+
pin = parseInt(m[1], 10);
|
|
173
|
+
op = { operation: 'gpio.read', pin };
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!op) {
|
|
178
|
+
for (const wn of writeNames) {
|
|
179
|
+
const esc = wn.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
180
|
+
const m = e.value.match(new RegExp(`${esc}\\((\\d+)`));
|
|
181
|
+
if (m) {
|
|
182
|
+
pin = parseInt(m[1], 10);
|
|
183
|
+
op = { operation: 'gpio.write', pin };
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// toggle: a write call whose second arg is a read call on the same pin.
|
|
189
|
+
if (!op) {
|
|
190
|
+
const tm = e.value.match(/\((\d+),\s*\w*[Rr]ead/);
|
|
191
|
+
if (tm) {
|
|
192
|
+
op = { operation: 'gpio.toggle', pin: parseInt(tm[1], 10) };
|
|
193
|
+
}
|
|
165
194
|
}
|
|
195
|
+
if (op)
|
|
196
|
+
checkGpioHalOp(op);
|
|
166
197
|
}
|
|
167
198
|
// Recurse into nested expressions
|
|
168
199
|
if (e.args && Array.isArray(e.args)) {
|
|
@@ -70,11 +70,53 @@ export interface ProgramAnalysisResult {
|
|
|
70
70
|
* __tc_ble runtime shims and their esp_wifi/esp_http_client/nimble includes
|
|
71
71
|
* on these. Detected from wifi.* / http.* / ble.* HAL-op operation names. */
|
|
72
72
|
usesWifi: boolean;
|
|
73
|
+
/** Fine-grained WiFi sub-feature tracking — gates individual shim sub-blocks
|
|
74
|
+
* so that unused static helper functions don't trigger -Wunused-function. */
|
|
75
|
+
usesWifiConnect: boolean;
|
|
76
|
+
usesWifiConnectBlocking: boolean;
|
|
77
|
+
usesWifiQuery: boolean;
|
|
78
|
+
usesWifiScan: boolean;
|
|
79
|
+
usesWifiConfig: boolean;
|
|
73
80
|
usesHttp: boolean;
|
|
74
81
|
usesBle: boolean;
|
|
75
82
|
/** Preferences (NVS) usage — framework-esp32 gates the __tc_prefs runtime shim
|
|
76
83
|
* and its nvs_flash/nvs includes on this. Detected from preferences.* ops. */
|
|
77
84
|
usesPreferences: boolean;
|
|
85
|
+
/** Random usage — frameworks gate their PRNG runtime/include on this
|
|
86
|
+
* (e.g. framework-esp32 includes <esp_random.h>). Detected from random.* ops. */
|
|
87
|
+
usesRandom: boolean;
|
|
88
|
+
/** FS (filesystem) usage — frameworks gate their VFS/SD runtime shim on this.
|
|
89
|
+
* Detected from fs.* ops. */
|
|
90
|
+
usesFS: boolean;
|
|
91
|
+
/** mDNS usage — frameworks gate the esp_mdns runtime/include on this. */
|
|
92
|
+
usesMdns: boolean;
|
|
93
|
+
/** MQTT usage — frameworks gate the esp_mqtt runtime/include on this. */
|
|
94
|
+
usesMqtt: boolean;
|
|
95
|
+
/** OTA usage — frameworks gate the esp_https_ota runtime/include on this. */
|
|
96
|
+
usesOta: boolean;
|
|
97
|
+
/** Temperature (die temp) usage. Detected from temp.* ops. */
|
|
98
|
+
usesTemp: boolean;
|
|
99
|
+
/** Hardware timer (GPTimer) usage. Detected from hwtimer.* ops. */
|
|
100
|
+
usesHwtimer: boolean;
|
|
101
|
+
/** Capacitive touch pins usage. Detected from capacitive.* ops. */
|
|
102
|
+
usesCapacitive: boolean;
|
|
103
|
+
/** Worker offload usage. Detected from worker.* ops. Frameworks gate the
|
|
104
|
+
* worker_runtime polyfill (and its per-framework backing) on this. */
|
|
105
|
+
usesWorker: boolean;
|
|
106
|
+
/** Native/desktop: std::chrono / steady_clock usage (gates <chrono>). */
|
|
107
|
+
usesChrono: boolean;
|
|
108
|
+
/** Native/desktop: std::set usage (gates <set>). */
|
|
109
|
+
usesSet: boolean;
|
|
110
|
+
/** Native/desktop: std::algorithm usage (std::sort/find/transform etc., gates <algorithm>). */
|
|
111
|
+
usesAlgorithm: boolean;
|
|
112
|
+
/** Native/desktop: cstdio usage (printf/fprintf/snprintf, gates <cstdio>). */
|
|
113
|
+
usesCstdio: boolean;
|
|
114
|
+
/** digitalRead() in lowered raw text or gpio.read hal-op (gates the native digitalRead shim). */
|
|
115
|
+
usesDigitalRead: boolean;
|
|
116
|
+
/** display.* hal-op present, or a UI is mounted (replaces ad-hoc programUsesDisplay walks). */
|
|
117
|
+
usesDisplay: boolean;
|
|
118
|
+
/** cuttlefish_halt() in lowered raw text (gates the cuttlefish_halt macro polyfill). */
|
|
119
|
+
usesHalt: boolean;
|
|
78
120
|
}
|
|
79
121
|
/**
|
|
80
122
|
* Analyze a program IR in a single pass to detect all features.
|