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)
104 lines
3 KiB
TypeScript
104 lines
3 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { parseVarNames, parseVarDecls } from '../../src/live-values-pump';
|
|
|
|
describe('parseVarNames', () => {
|
|
it('extracts vars from a single VAR block', () => {
|
|
const text = [
|
|
'PROGRAM PLC_PRG',
|
|
'VAR',
|
|
' counter : INT := 0;',
|
|
' bRunning : BOOL;',
|
|
' rTemperature : REAL;',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarNames(text)).toEqual(['counter', 'bRunning', 'rTemperature']);
|
|
});
|
|
|
|
it('handles VAR_INPUT / VAR_OUTPUT / VAR_GLOBAL too', () => {
|
|
const text = [
|
|
'FUNCTION_BLOCK FB_X',
|
|
'VAR_INPUT',
|
|
' x : INT;',
|
|
'END_VAR',
|
|
'VAR_OUTPUT',
|
|
' y : BOOL;',
|
|
'END_VAR',
|
|
'VAR',
|
|
' internal : DINT;',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarNames(text).sort()).toEqual(['internal', 'x', 'y']);
|
|
});
|
|
|
|
it('ignores lines inside (* ... *) blocks and after //', () => {
|
|
const text = [
|
|
'VAR',
|
|
' alpha : INT;',
|
|
' (* commentedOut : BOOL; *)',
|
|
' beta : INT; // tail comment',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarNames(text)).toEqual(['alpha', 'beta']);
|
|
});
|
|
|
|
it('returns [] when no VAR block is present', () => {
|
|
expect(parseVarNames('PROGRAM X\nEND_PROGRAM')).toEqual([]);
|
|
});
|
|
|
|
it('handles AT %X10.0 location prefix and := initializer', () => {
|
|
const text = [
|
|
'VAR',
|
|
' bRelay AT %QX0.1 : BOOL := FALSE;',
|
|
' iCount : INT := 42;',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarNames(text)).toEqual(['bRelay', 'iCount']);
|
|
});
|
|
});
|
|
|
|
describe('parseVarDecls', () => {
|
|
it('returns name + declared type per decl', () => {
|
|
const text = [
|
|
'PROGRAM PLC_PRG',
|
|
'VAR',
|
|
' counter : INT := 0;',
|
|
' bRunning : BOOL;',
|
|
' fb : FB_Test;',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarDecls(text)).toEqual([
|
|
{ name: 'counter', type: 'INT' },
|
|
{ name: 'bRunning', type: 'BOOL' },
|
|
{ name: 'fb', type: 'FB_Test' },
|
|
]);
|
|
});
|
|
|
|
it('strips ARRAY [...] OF wrapper', () => {
|
|
const text = ['VAR', ' buf : ARRAY [0..9] OF INT;', 'END_VAR'].join('\n');
|
|
expect(parseVarDecls(text)).toEqual([{ name: 'buf', type: 'INT' }]);
|
|
});
|
|
|
|
it('strips POINTER TO and REFERENCE TO', () => {
|
|
const text = [
|
|
'VAR',
|
|
' p : POINTER TO BOOL;',
|
|
' r : REFERENCE TO MyStruct;',
|
|
'END_VAR',
|
|
].join('\n');
|
|
expect(parseVarDecls(text)).toEqual([
|
|
{ name: 'p', type: 'BOOL' },
|
|
{ name: 'r', type: 'MyStruct' },
|
|
]);
|
|
});
|
|
|
|
it('handles AT %loc prefix', () => {
|
|
const text = ['VAR', ' bRelay AT %QX0.1 : BOOL := FALSE;', 'END_VAR'].join('\n');
|
|
expect(parseVarDecls(text)).toEqual([{ name: 'bRelay', type: 'BOOL' }]);
|
|
});
|
|
|
|
it('returns type=null when there is no `: <type>` clause on the line', () => {
|
|
// Pathological: missing type. Don't crash; just emit name with null type.
|
|
const text = ['VAR', ' weird;', 'END_VAR'].join('\n');
|
|
expect(parseVarDecls(text)).toEqual([{ name: 'weird', type: null }]);
|
|
});
|
|
});
|