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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodalis-compiler",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
4
4
  "description": "Compiles IEC-61131-3/10 languages into code that can be used as a PLC on multiple platforms.",
5
5
  "icon": "nodalis.png",
6
6
  "main": "src/nodalis.js",
@@ -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
- return [`// unsupported: ${stmt.type}`];
182
+ throw createTranspileError(`Unsupported statement type '${stmt.type}'`, stmt);
183
183
  }
184
184
  }
185
185
  catch(e){
186
- console.error(e + "\n" + JSON.stringify(stmt));
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
- return [`// Unsupported statement type: ${stmt.type}`];
199
+ throw createTranspileError(`Unsupported statement type '${stmt.type}'`, stmt);
200
200
  }
201
201
  } catch (e) {
202
- console.error("Error transpiling statement", stmt, e);
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 new Error(`Expected '${value}', but got '${token?.value}'`);
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 new Error(`Expected address after AT, got '${addrToken?.value}'`);
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
- consume(); // Skip unknown
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
- // Remove single-line comments (//...)
44
- code = code.replace(/\/\/.*$/gm, '');
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(code)) !== null) {
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(objectInstanceRaw, 0);
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
  }