Extends the pump beyond top-level vars: when a var's declared type
resolves to another mirror .st file, we descend one level and read
each of that type's vars as <var>.<member>.
Parser: parseVarDecls(text) now returns name + declared type per
decl. ARRAY/POINTER/REFERENCE wrappers stripped to the inner type.
parseVarNames is kept as a thin wrapper for the existing callers.
Pump: new resolveTypeMirror(typeName, deviceRoot) dep. Pump itself
doesn't know how to find a type's source -- the caller plugs in a
strategy. server.ts wires a recursive walk under the device root
looking for '<typeName>.st'; the mirror layout guarantees stable
filenames for POU/FB/DUT (every code-bearing object).
Pump's deviceRootFor(absPath) parses the abs path back to the
device root by locating the '/mcp-mirror/<device>/' segment. If
that fails we fall back to the file's parent dir (still works for
same-folder type lookups, just won't find types in sibling folders).
Constructor now accepts partial deps and fills in safe defaults
(no-op resolveTypeMirror -> never descend, matching v0.3
top-level-only behaviour). Existing tests don't need to change.
407/407 tests pass; new coverage:
- parseVarDecls: name+type extraction, ARRAY/POINTER/REFERENCE
stripping, AT %loc prefix, missing-type pathological case
- LiveValuesPump.tick: descends when resolver returns content;
doesn't descend when resolver returns null (primitives)
Task 6 of v0.3 plan (parser + pump class; full --live-values CLI
wiring is Task 7).
parseVarNames(text) extracts variable names from any VAR /
VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL / etc. block. Handles:
- one name per declaration line
- 'AT %QX0.1' location prefix
- := initializer
- inline (* ... *) and // line comments
- multi-line (* ... *) blocks (state threaded across lines)
Doesn't handle the multi-name shorthand 'a, b : INT;' -- vanishingly
rare in practice, cost of getting it wrong is just a missing overlay.
LiveValuesPump owns a setInterval. Each tick:
- reads tui-state.json via the injected readSelection
- reads the POU's .st file from the mirror
- parseVarNames -> per-var read_variable round-trip (errors per-var
are silent, others may succeed)
- writeLiveValues snapshot
Reentrancy guard: if a tick is still in flight when the next
interval fires, skip the new tick rather than queueing. Errors at any
layer are swallowed -- pump must never crash the server.
start()/stop() lifecycle so the server can hand it to the existing
shutdown path.