nodalis-compiler 1.0.24 → 1.0.26
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/compilers/CPPCompiler.js +2 -2
- package/src/compilers/st-parser/gcctranspiler.js +18 -3
- package/src/compilers/st-parser/jstranspiler.js +18 -3
- package/src/compilers/st-parser/parser.js +52 -29
- package/src/compilers/st-parser/tokenizer.js +43 -14
- package/src/compilers/support/generic/bacnet.cpp +2 -4
- package/src/compilers/support/jint/nodalis/NodalisEngine/BacnetClient.cs +3 -2
- package/src/compilers/support/nodejs/bacnet.js +1 -2
- package/src/programmers/MTIProgrammer.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [1.0.26] 2026-03-19
|
|
3
|
+
- Improved error handling on programming.
|
|
4
|
+
|
|
5
|
+
## [1.0.25] 2026-03-18
|
|
6
|
+
- Added line number errors to compilers.
|
|
7
|
+
- Changed bacnet to use remote address property in mapping as the instance number.
|
|
2
8
|
|
|
3
9
|
## [1.0.24] 2026-03-04
|
|
4
10
|
- Added support for CONFIGURATION, RESOURCE, TASK, and PROGRAM (instance) keywords in ST.
|
package/package.json
CHANGED
|
@@ -559,8 +559,8 @@ int main() {
|
|
|
559
559
|
"windows-x64": " -lws2_32 -lcrypt32 -lwsock32 -lole32 -liphlpapi",
|
|
560
560
|
"windows-arm64": " -lws2_32 -lcrypt32 -lwsock32 -lole32 -liphlpapi",
|
|
561
561
|
"linux-x64": "",
|
|
562
|
-
"linux-arm64": "",
|
|
563
|
-
"linux-arm": "",
|
|
562
|
+
"linux-arm64": " -latomic",
|
|
563
|
+
"linux-arm": " -latomic",
|
|
564
564
|
"macos-x64": "",
|
|
565
565
|
"macos-arm64": "",
|
|
566
566
|
}
|
|
@@ -179,13 +179,12 @@ function mapStatement(stmt){
|
|
|
179
179
|
return [`${stmt.name}();`];
|
|
180
180
|
}
|
|
181
181
|
default:
|
|
182
|
-
|
|
182
|
+
throw createTranspileError(`Unsupported statement type '${stmt.type}'`, stmt);
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
catch(e){
|
|
186
|
-
|
|
186
|
+
throw enrichTranspileError(e, stmt);
|
|
187
187
|
}
|
|
188
|
-
return "// uncompilable statement " + JSON.stringify(stmt);
|
|
189
188
|
}
|
|
190
189
|
|
|
191
190
|
/**
|
|
@@ -197,6 +196,22 @@ function transpileStatements(statements) {
|
|
|
197
196
|
return statements?.flatMap(mapStatement);
|
|
198
197
|
}
|
|
199
198
|
|
|
199
|
+
function createTranspileError(message, node) {
|
|
200
|
+
const line = node?.loc?.line ?? 1;
|
|
201
|
+
const column = node?.loc?.column ?? 1;
|
|
202
|
+
const error = new Error(`${message} at line ${line}, column ${column}`);
|
|
203
|
+
error.line = line;
|
|
204
|
+
error.column = column;
|
|
205
|
+
error.sourceLocation = { line, column };
|
|
206
|
+
return error;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function enrichTranspileError(error, node) {
|
|
210
|
+
if (error?.line) return error;
|
|
211
|
+
const message = error?.message || String(error);
|
|
212
|
+
return createTranspileError(message, node);
|
|
213
|
+
}
|
|
214
|
+
|
|
200
215
|
/**
|
|
201
216
|
* Creates a transpiled section of declared variables.
|
|
202
217
|
* @param {{type: string, address: string, initialValue: string, sectionType: string}[]} varSections An array of variable tokens.
|
|
@@ -196,11 +196,10 @@ function mapStatement(stmt, infb = false) {
|
|
|
196
196
|
return [`${stmt.name}();`];
|
|
197
197
|
}
|
|
198
198
|
default:
|
|
199
|
-
|
|
199
|
+
throw createTranspileError(`Unsupported statement type '${stmt.type}'`, stmt);
|
|
200
200
|
}
|
|
201
201
|
} catch (e) {
|
|
202
|
-
|
|
203
|
-
return [`// Failed to transpile: ${JSON.stringify(stmt)}`];
|
|
202
|
+
throw enrichTranspileError(e, stmt);
|
|
204
203
|
}
|
|
205
204
|
}
|
|
206
205
|
|
|
@@ -213,6 +212,22 @@ function transpileStatements(statements, infb=false) {
|
|
|
213
212
|
return statements?.flatMap((stmt) => mapStatement(stmt, infb));
|
|
214
213
|
}
|
|
215
214
|
|
|
215
|
+
function createTranspileError(message, node) {
|
|
216
|
+
const line = node?.loc?.line ?? 1;
|
|
217
|
+
const column = node?.loc?.column ?? 1;
|
|
218
|
+
const error = new Error(`${message} at line ${line}, column ${column}`);
|
|
219
|
+
error.line = line;
|
|
220
|
+
error.column = column;
|
|
221
|
+
error.sourceLocation = { line, column };
|
|
222
|
+
return error;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function enrichTranspileError(error, node) {
|
|
226
|
+
if (error?.line) return error;
|
|
227
|
+
const message = error?.message || String(error);
|
|
228
|
+
return createTranspileError(message, node);
|
|
229
|
+
}
|
|
230
|
+
|
|
216
231
|
/**
|
|
217
232
|
* Creates a transpiled section of declared variables.
|
|
218
233
|
* @param {{type: string, address: string, initialValue: string, sectionType: string}[]} varSections An array of variable tokens.
|
|
@@ -46,14 +46,33 @@ export function parseStructuredText(code) {
|
|
|
46
46
|
return tokens[position + offset];
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function previous() {
|
|
50
|
+
return tokens[position - 1];
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
function consume() {
|
|
50
54
|
return tokens[position++];
|
|
51
55
|
}
|
|
52
56
|
|
|
57
|
+
function withLoc(node, token) {
|
|
58
|
+
if (!node || !token) return node;
|
|
59
|
+
return { ...node, loc: { line: token.line, column: token.column } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createSourceError(message, token = peek() || previous()) {
|
|
63
|
+
const line = token?.line ?? 1;
|
|
64
|
+
const column = token?.column ?? 1;
|
|
65
|
+
const error = new Error(`${message} at line ${line}, column ${column}`);
|
|
66
|
+
error.line = line;
|
|
67
|
+
error.column = column;
|
|
68
|
+
error.sourceLocation = { line, column };
|
|
69
|
+
return error;
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
function expect(value) {
|
|
54
73
|
const token = consume();
|
|
55
74
|
if (!token || token.value.toUpperCase() !== value.toUpperCase()) {
|
|
56
|
-
throw
|
|
75
|
+
throw createSourceError(`Expected '${value}', but got '${token?.value ?? 'EOF'}'`, token || previous());
|
|
57
76
|
}
|
|
58
77
|
return token;
|
|
59
78
|
}
|
|
@@ -129,7 +148,7 @@ export function parseStructuredText(code) {
|
|
|
129
148
|
}
|
|
130
149
|
|
|
131
150
|
if (peek()?.value === ';') consume();
|
|
132
|
-
return { type: 'TaskDeclaration', name, interval, priority };
|
|
151
|
+
return withLoc({ type: 'TaskDeclaration', name, interval, priority }, tokens[position - 1]);
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
function normalizeNumericString(value, fallback) {
|
|
@@ -173,7 +192,7 @@ export function parseStructuredText(code) {
|
|
|
173
192
|
if (addrToken?.type === 'ADDRESS' || addrToken?.type === 'IDENTIFIER') {
|
|
174
193
|
address = addrToken.value;
|
|
175
194
|
} else {
|
|
176
|
-
throw
|
|
195
|
+
throw createSourceError(`Expected address after AT, got '${addrToken?.value ?? 'EOF'}'`, addrToken || previous());
|
|
177
196
|
}
|
|
178
197
|
}
|
|
179
198
|
expect(':');
|
|
@@ -188,12 +207,12 @@ export function parseStructuredText(code) {
|
|
|
188
207
|
}
|
|
189
208
|
initialValue = initTokens.join('');
|
|
190
209
|
}
|
|
191
|
-
variables.push({ name, type, address, initialValue, sectionType: 'VAR_GLOBAL' });
|
|
210
|
+
variables.push(withLoc({ name, type, address, initialValue, sectionType: 'VAR_GLOBAL' }, tokens[position - 1]));
|
|
192
211
|
if (peek()?.value === ';') consume();
|
|
193
212
|
}
|
|
194
213
|
|
|
195
214
|
expect('END_VAR');
|
|
196
|
-
return { type: 'GlobalVars', variables };
|
|
215
|
+
return withLoc({ type: 'GlobalVars', variables }, variables[0]?.loc ? { line: variables[0].loc.line, column: variables[0].loc.column } : previous());
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
|
|
@@ -202,6 +221,7 @@ export function parseStructuredText(code) {
|
|
|
202
221
|
const sectionType = consume().value.toUpperCase();
|
|
203
222
|
|
|
204
223
|
while (peek() && peek().value.toUpperCase() !== 'END_VAR') {
|
|
224
|
+
const startToken = peek();
|
|
205
225
|
const name = consume().value;
|
|
206
226
|
expect(':');
|
|
207
227
|
const type = consume().value;
|
|
@@ -215,7 +235,7 @@ export function parseStructuredText(code) {
|
|
|
215
235
|
}
|
|
216
236
|
initialValue = initTokens.join('');
|
|
217
237
|
}
|
|
218
|
-
variables.push({ name, type, initialValue, sectionType });
|
|
238
|
+
variables.push(withLoc({ name, type, initialValue, sectionType }, startToken));
|
|
219
239
|
if (peek()?.value === ';') consume();
|
|
220
240
|
}
|
|
221
241
|
expect('END_VAR');
|
|
@@ -234,6 +254,10 @@ export function parseStructuredText(code) {
|
|
|
234
254
|
function parseStatement() {
|
|
235
255
|
const token = peek();
|
|
236
256
|
if (!token) return null;
|
|
257
|
+
if (token.value === ';') {
|
|
258
|
+
consume();
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
237
261
|
|
|
238
262
|
if (token.value.toUpperCase() === 'IF') return parseIf();
|
|
239
263
|
if (token.value.toUpperCase() === 'WHILE') return parseWhile();
|
|
@@ -261,7 +285,7 @@ function parseStatement() {
|
|
|
261
285
|
// optional semicolon
|
|
262
286
|
if (peek()?.value === ';') consume();
|
|
263
287
|
|
|
264
|
-
return { type: 'CALL', name, args };
|
|
288
|
+
return withLoc({ type: 'CALL', name, args }, token);
|
|
265
289
|
}
|
|
266
290
|
|
|
267
291
|
// Assignment: x := y;
|
|
@@ -280,16 +304,15 @@ if (peek(i)?.value === ':=') {
|
|
|
280
304
|
right.push(consume().value);
|
|
281
305
|
}
|
|
282
306
|
if (peek()?.value === ';') consume();
|
|
283
|
-
return { type: 'ASSIGN', left: lhs, right };
|
|
307
|
+
return withLoc({ type: 'ASSIGN', left: lhs, right }, lhsTokens[0]);
|
|
284
308
|
}
|
|
285
309
|
|
|
286
|
-
|
|
287
|
-
return null;
|
|
310
|
+
throw createSourceError(`Unexpected token '${token.value}'`, token);
|
|
288
311
|
}
|
|
289
312
|
|
|
290
313
|
|
|
291
314
|
function parseIf() {
|
|
292
|
-
consume(); // IF
|
|
315
|
+
const startToken = consume(); // IF
|
|
293
316
|
|
|
294
317
|
// Collect condition tokens until THEN
|
|
295
318
|
const conditionTokens = [];
|
|
@@ -322,13 +345,13 @@ function parseIf() {
|
|
|
322
345
|
consume(); // END_IF
|
|
323
346
|
}
|
|
324
347
|
|
|
325
|
-
return {
|
|
348
|
+
return withLoc({
|
|
326
349
|
type: 'IF',
|
|
327
350
|
condition: conditionTokens,
|
|
328
351
|
thenBlock,
|
|
329
352
|
elseIfBlocks,
|
|
330
353
|
elseBlock
|
|
331
|
-
};
|
|
354
|
+
}, startToken);
|
|
332
355
|
}
|
|
333
356
|
|
|
334
357
|
|
|
@@ -349,7 +372,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
349
372
|
|
|
350
373
|
|
|
351
374
|
function parseWhile() {
|
|
352
|
-
consume(); // WHILE
|
|
375
|
+
const startToken = consume(); // WHILE
|
|
353
376
|
const condition = [];
|
|
354
377
|
while (peek() && peek().value.toUpperCase() !== 'DO') {
|
|
355
378
|
condition.push(consume().value);
|
|
@@ -357,11 +380,11 @@ function parseStatementsUntil(endTokens) {
|
|
|
357
380
|
expect('DO');
|
|
358
381
|
const body = parseStatements('END_WHILE');
|
|
359
382
|
expect('END_WHILE');
|
|
360
|
-
return { type: 'WHILE', condition, body };
|
|
383
|
+
return withLoc({ type: 'WHILE', condition, body }, startToken);
|
|
361
384
|
}
|
|
362
385
|
|
|
363
386
|
function parseFor() {
|
|
364
|
-
consume(); // FOR
|
|
387
|
+
const startToken = consume(); // FOR
|
|
365
388
|
const variable = consume().value;
|
|
366
389
|
expect(':=');
|
|
367
390
|
const from = consume().value;
|
|
@@ -375,11 +398,11 @@ function parseStatementsUntil(endTokens) {
|
|
|
375
398
|
expect('DO');
|
|
376
399
|
const body = parseStatements('END_FOR');
|
|
377
400
|
expect('END_FOR');
|
|
378
|
-
return { type: 'FOR', variable, from, to, step, body };
|
|
401
|
+
return withLoc({ type: 'FOR', variable, from, to, step, body }, startToken);
|
|
379
402
|
}
|
|
380
403
|
|
|
381
404
|
function parseRepeat() {
|
|
382
|
-
consume(); // REPEAT
|
|
405
|
+
const startToken = consume(); // REPEAT
|
|
383
406
|
const body = parseStatements('UNTIL');
|
|
384
407
|
expect('UNTIL');
|
|
385
408
|
const condition = [];
|
|
@@ -388,11 +411,11 @@ function parseStatementsUntil(endTokens) {
|
|
|
388
411
|
}
|
|
389
412
|
expect('END_REPEAT');
|
|
390
413
|
if (peek()?.value === ';') consume();
|
|
391
|
-
return { type: 'REPEAT', condition, body };
|
|
414
|
+
return withLoc({ type: 'REPEAT', condition, body }, startToken);
|
|
392
415
|
}
|
|
393
416
|
|
|
394
417
|
function parseCase() {
|
|
395
|
-
consume(); // CASE
|
|
418
|
+
const startToken = consume(); // CASE
|
|
396
419
|
const expression = [];
|
|
397
420
|
while (peek() && peek().value.toUpperCase() !== 'OF') {
|
|
398
421
|
expression.push(consume().value);
|
|
@@ -411,7 +434,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
411
434
|
const stmt = parseStatement();
|
|
412
435
|
if (stmt) body.push(stmt);
|
|
413
436
|
}
|
|
414
|
-
branches.push({ label: labels.join(','), body });
|
|
437
|
+
branches.push(withLoc({ label: labels.join(','), body }, peek(-1)));
|
|
415
438
|
}
|
|
416
439
|
let elseBlock = null;
|
|
417
440
|
if (peek()?.value.toUpperCase() === 'ELSE') {
|
|
@@ -423,7 +446,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
423
446
|
}
|
|
424
447
|
}
|
|
425
448
|
expect('END_CASE');
|
|
426
|
-
return { type: 'CASE', expression, branches, elseBlock };
|
|
449
|
+
return withLoc({ type: 'CASE', expression, branches, elseBlock }, startToken);
|
|
427
450
|
}
|
|
428
451
|
|
|
429
452
|
function isCaseBranchBoundary(token) {
|
|
@@ -434,7 +457,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
434
457
|
}
|
|
435
458
|
|
|
436
459
|
function parseProgramOrInstance() {
|
|
437
|
-
expect('PROGRAM');
|
|
460
|
+
const startToken = expect('PROGRAM');
|
|
438
461
|
const name = consume().value;
|
|
439
462
|
|
|
440
463
|
if (peek()?.value?.toUpperCase() === 'WITH' || peek()?.value === ':') {
|
|
@@ -446,7 +469,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
446
469
|
expect(':');
|
|
447
470
|
const typeName = consume().value;
|
|
448
471
|
if (peek()?.value === ';') consume();
|
|
449
|
-
return { type: 'ProgramInstanceDeclaration', name, typeName, associatedTaskName };
|
|
472
|
+
return withLoc({ type: 'ProgramInstanceDeclaration', name, typeName, associatedTaskName }, startToken);
|
|
450
473
|
}
|
|
451
474
|
|
|
452
475
|
const vars = [];
|
|
@@ -459,11 +482,11 @@ function parseStatementsUntil(endTokens) {
|
|
|
459
482
|
stmts.push(...parseStatements('END_PROGRAM'));
|
|
460
483
|
expect('END_PROGRAM');
|
|
461
484
|
|
|
462
|
-
return { type: 'ProgramDeclaration', name, varSections: vars, statements: stmts };
|
|
485
|
+
return withLoc({ type: 'ProgramDeclaration', name, varSections: vars, statements: stmts }, startToken);
|
|
463
486
|
}
|
|
464
487
|
|
|
465
488
|
function parseFunction() {
|
|
466
|
-
expect('FUNCTION');
|
|
489
|
+
const startToken = expect('FUNCTION');
|
|
467
490
|
const name = consume().value;
|
|
468
491
|
expect(':');
|
|
469
492
|
const returnType = consume().value;
|
|
@@ -477,11 +500,11 @@ function parseStatementsUntil(endTokens) {
|
|
|
477
500
|
stmts.push(...parseStatements('END_FUNCTION'));
|
|
478
501
|
expect('END_FUNCTION');
|
|
479
502
|
|
|
480
|
-
return { type: 'FunctionDeclaration', name, returnType, varSections: vars, statements: stmts };
|
|
503
|
+
return withLoc({ type: 'FunctionDeclaration', name, returnType, varSections: vars, statements: stmts }, startToken);
|
|
481
504
|
}
|
|
482
505
|
|
|
483
506
|
function parseFunctionBlock() {
|
|
484
|
-
expect('FUNCTION_BLOCK');
|
|
507
|
+
const startToken = expect('FUNCTION_BLOCK');
|
|
485
508
|
const name = consume().value;
|
|
486
509
|
const vars = [];
|
|
487
510
|
const stmts = [];
|
|
@@ -493,7 +516,7 @@ function parseStatementsUntil(endTokens) {
|
|
|
493
516
|
stmts.push(...parseStatements('END_FUNCTION_BLOCK'));
|
|
494
517
|
expect('END_FUNCTION_BLOCK');
|
|
495
518
|
|
|
496
|
-
return { type: 'FunctionBlockDeclaration', name, varSections: vars, statements: stmts };
|
|
519
|
+
return withLoc({ type: 'FunctionBlockDeclaration', name, varSections: vars, statements: stmts }, startToken);
|
|
497
520
|
}
|
|
498
521
|
|
|
499
522
|
const body = [];
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
/**
|
|
25
25
|
* Tokenizes a block of structured text into their types and values.
|
|
26
26
|
* @param {string} code A block of structured text code.
|
|
27
|
-
* @returns {{type: string, value: string}[]} An array of tokens.
|
|
27
|
+
* @returns {{type: string, value: string, line: number, column: number}[]} An array of tokens.
|
|
28
28
|
*/
|
|
29
29
|
const INTEGER_TYPE_PATTERN = '(?:BYTE|WORD|DWORD|LWORD|SINT|INT|DINT|LINT|USINT|UINT|UDINT|ULINT)';
|
|
30
30
|
const REAL_TYPE_PATTERN = '(?:REAL|LREAL)';
|
|
@@ -40,37 +40,66 @@ const NUMBER_TOKEN_PATTERN = `(?:${TYPED_INTEGER_LITERAL_PATTERN}|${TYPED_REAL_L
|
|
|
40
40
|
export function tokenize(code) {
|
|
41
41
|
const tokens = [];
|
|
42
42
|
let match;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Remove multi-line comments ((*...*))
|
|
47
|
-
code = code.replace(/\(\*[\s\S]*?\*\)/g, '');
|
|
43
|
+
const sanitizedCode = stripCommentsPreservePositions(code);
|
|
44
|
+
const lineStarts = buildLineStarts(sanitizedCode);
|
|
48
45
|
|
|
49
46
|
//const regex = /(%[IQM][A-Z]?[0-9]+(?:\.[0-9]+)?)|(:=)|([A-Za-z_]\w*\.\d+)|([A-Za-z_]\w*\.\w+)|([A-Za-z_]\w*)|(\d+)|([:;()<>+\-*/=])/g;
|
|
50
47
|
const regex = new RegExp(`(%[IQM][A-Z]*\\d+(?:\\.\\d+)?)|(:=|=>|>=|<=|<>|!=|\\*\\*)|([A-Za-z_]\\w*\\.\\d+)|([A-Za-z_]\\w*\\.\\w+)|(${NUMBER_TOKEN_PATTERN})|([A-Za-z_]\\w*)|([<>+\\-*/=;():,&|^])`, 'gi');
|
|
51
48
|
|
|
52
|
-
while ((match = regex.exec(
|
|
49
|
+
while ((match = regex.exec(sanitizedCode)) !== null) {
|
|
53
50
|
const [_, address, compoundSymbol, bitIdentifier, propIdentifier, number, identifier, symbol] = match;
|
|
51
|
+
const location = getLineColumn(lineStarts, match.index);
|
|
54
52
|
|
|
55
53
|
if (address)
|
|
56
|
-
tokens.push({ type: 'ADDRESS', value: address });
|
|
54
|
+
tokens.push({ type: 'ADDRESS', value: address, ...location });
|
|
57
55
|
else if (compoundSymbol)
|
|
58
|
-
tokens.push({ type: 'SYMBOL', value: compoundSymbol });
|
|
56
|
+
tokens.push({ type: 'SYMBOL', value: compoundSymbol, ...location });
|
|
59
57
|
else if (bitIdentifier)
|
|
60
|
-
tokens.push({ type: 'IDENTIFIER', value: bitIdentifier });
|
|
58
|
+
tokens.push({ type: 'IDENTIFIER', value: bitIdentifier, ...location });
|
|
61
59
|
else if (propIdentifier)
|
|
62
|
-
tokens.push({ type: 'IDENTIFIER', value: propIdentifier });
|
|
60
|
+
tokens.push({ type: 'IDENTIFIER', value: propIdentifier, ...location });
|
|
63
61
|
else if (identifier)
|
|
64
|
-
tokens.push({ type: 'IDENTIFIER', value: identifier });
|
|
62
|
+
tokens.push({ type: 'IDENTIFIER', value: identifier, ...location });
|
|
65
63
|
else if (number)
|
|
66
|
-
tokens.push({ type: 'NUMBER', value: normalizeNumericLiteral(number) });
|
|
64
|
+
tokens.push({ type: 'NUMBER', value: normalizeNumericLiteral(number), ...location });
|
|
67
65
|
else if (symbol)
|
|
68
|
-
tokens.push({ type: 'SYMBOL', value: symbol });
|
|
66
|
+
tokens.push({ type: 'SYMBOL', value: symbol, ...location });
|
|
69
67
|
|
|
70
68
|
}
|
|
71
69
|
return tokens;
|
|
72
70
|
}
|
|
73
71
|
|
|
72
|
+
function stripCommentsPreservePositions(code) {
|
|
73
|
+
return String(code || '')
|
|
74
|
+
.replace(/\/\/.*$/gm, (comment) => ' '.repeat(comment.length))
|
|
75
|
+
.replace(/\(\*[\s\S]*?\*\)/g, (comment) => comment.replace(/[^\n]/g, ' '));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildLineStarts(text) {
|
|
79
|
+
const starts = [0];
|
|
80
|
+
for (let i = 0; i < text.length; i++) {
|
|
81
|
+
if (text[i] === '\n') starts.push(i + 1);
|
|
82
|
+
}
|
|
83
|
+
return starts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getLineColumn(lineStarts, index) {
|
|
87
|
+
let low = 0;
|
|
88
|
+
let high = lineStarts.length - 1;
|
|
89
|
+
|
|
90
|
+
while (low <= high) {
|
|
91
|
+
const mid = Math.floor((low + high) / 2);
|
|
92
|
+
if (lineStarts[mid] <= index) low = mid + 1;
|
|
93
|
+
else high = mid - 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const lineIndex = Math.max(0, high);
|
|
97
|
+
return {
|
|
98
|
+
line: lineIndex + 1,
|
|
99
|
+
column: (index - lineStarts[lineIndex]) + 1
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
74
103
|
function normalizeNumericLiteral(value) {
|
|
75
104
|
const literal = String(value);
|
|
76
105
|
const boolMatch = literal.match(new RegExp(`^${BOOL_TYPE_PATTERN}#(TRUE|FALSE|1|0)$`, 'i'));
|
|
@@ -428,6 +428,7 @@ bool BACNETClient::resolveRemote(const std::string& remote, BACnetRemotePoint& p
|
|
|
428
428
|
bool BACNETClient::parseRemoteDefinition(const IOMap &map, BACnetRemotePoint &point)
|
|
429
429
|
{
|
|
430
430
|
json config = nullptr;
|
|
431
|
+
point.objectInstance = static_cast<uint32_t>(std::stoll(map.remoteAddress));
|
|
431
432
|
if (map.additionalProperties.is_string())
|
|
432
433
|
{
|
|
433
434
|
std::cout << "BACNET-IP Additional Properties is a string.\n";
|
|
@@ -438,6 +439,7 @@ bool BACNETClient::parseRemoteDefinition(const IOMap &map, BACnetRemotePoint &po
|
|
|
438
439
|
config = map.additionalProperties;
|
|
439
440
|
std::cout << "BACNET-IP Additional Properties is an object.\n";
|
|
440
441
|
}
|
|
442
|
+
|
|
441
443
|
if (parseJsonRemote(config, point))
|
|
442
444
|
{
|
|
443
445
|
return true;
|
|
@@ -465,10 +467,6 @@ bool BACNETClient::parseJsonRemote(const json& config, BACnetRemotePoint& point)
|
|
|
465
467
|
extractNumber(config, "ObjectInstance", instance)) {
|
|
466
468
|
point.objectInstance = instance;
|
|
467
469
|
}
|
|
468
|
-
else
|
|
469
|
-
{
|
|
470
|
-
std::cout << "BACNET-IP no object instance\n";
|
|
471
|
-
}
|
|
472
470
|
|
|
473
471
|
auto propertyToken = extractString(config, "propertyId");
|
|
474
472
|
if (!propertyToken) {
|
|
@@ -226,12 +226,13 @@ namespace Nodalis
|
|
|
226
226
|
// Required numeric fields.
|
|
227
227
|
if (!TryGetUInt(props, "objectType", out var objType) && !TryGetUInt(props, "ObjectType", out objType))
|
|
228
228
|
return false;
|
|
229
|
-
if (!TryGetUInt(props, "objectInstance", out var objInst) && !TryGetUInt(props, "ObjectInstance", out objInst))
|
|
230
|
-
return false;
|
|
231
229
|
if (!TryGetUInt(props, "propertyId", out var propId) && !TryGetUInt(props, "PropertyId", out propId))
|
|
232
230
|
return false;
|
|
231
|
+
if (!uint.TryParse(remoteAddress, out var objInst))
|
|
232
|
+
return false;
|
|
233
233
|
|
|
234
234
|
point.ObjectType = (BacnetObjectTypes)objType;
|
|
235
|
+
|
|
235
236
|
point.ObjectInstance = objInst;
|
|
236
237
|
point.PropertyId = (BacnetPropertyIds)propId;
|
|
237
238
|
|
|
@@ -253,13 +253,12 @@ export class BacnetClient extends IOClient {
|
|
|
253
253
|
const stringRemote = parseRemoteString(map.remoteAddress);
|
|
254
254
|
|
|
255
255
|
const objectTypeRaw = config.objectType ?? config.ObjectType ?? stringRemote?.objectType ?? 0;
|
|
256
|
-
const objectInstanceRaw = config.objectInstance ?? config.ObjectInstance ?? stringRemote?.objectInstance ?? 0;
|
|
257
256
|
const propertyIdRaw = config.propertyId ?? config.PropertyId ?? stringRemote?.propertyId ?? 85;
|
|
258
257
|
const arrayIndexRaw = config.arrayIndex ?? config.ArrayIndex ?? stringRemote?.arrayIndex ?? null;
|
|
259
258
|
const valueType = String(config.valueType ?? config.ValueType ?? "e").toLowerCase();
|
|
260
259
|
|
|
261
260
|
const objectType = resolveEnumValue(this.bacnet?.enum?.ObjectType, objectTypeRaw, parseInteger(objectTypeRaw, 0));
|
|
262
|
-
const objectInstance = parseInteger(
|
|
261
|
+
const objectInstance = parseInteger(map.remoteAddress, null);
|
|
263
262
|
const propertyId = resolveEnumValue(this.bacnet?.enum?.PropertyIdentifier, propertyIdRaw, parseInteger(propertyIdRaw, 85));
|
|
264
263
|
const arrayIndex = parseInteger(arrayIndexRaw, null);
|
|
265
264
|
|
|
@@ -32,7 +32,7 @@ export class MTIProgrammer extends Programmer {
|
|
|
32
32
|
async program() {
|
|
33
33
|
let result = true;
|
|
34
34
|
try {
|
|
35
|
-
runMticp([
|
|
35
|
+
await runMticp([
|
|
36
36
|
"action=sendxml",
|
|
37
37
|
`src=${this.options.source}`,
|
|
38
38
|
`dst=${this.options.destination}`
|
|
@@ -43,6 +43,7 @@ export class MTIProgrammer extends Programmer {
|
|
|
43
43
|
})
|
|
44
44
|
.catch(err => {
|
|
45
45
|
// Print any error from the binary and exit non-zero
|
|
46
|
+
result = false;
|
|
46
47
|
console.error(err.message || err);
|
|
47
48
|
});
|
|
48
49
|
}
|