nodalis-compiler 1.0.23 → 1.0.24
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 +4 -0
- package/package.json +1 -1
- package/src/compilers/ArduinoCompiler.js +4 -2
- package/src/compilers/CPPCompiler.js +4 -2
- package/src/compilers/JSCompiler.js +4 -2
- package/src/compilers/st-parser/parser.js +120 -3
- package/src/nodalis.js +2 -1
- package/src/programmers/ArduinoProgrammer.js +1 -0
- package/src/programmers/FileProgrammer.js +1 -0
- package/src/programmers/MTIProgrammer.js +1 -0
- package/src/programmers/SSHProgrammer.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.24] 2026-03-04
|
|
4
|
+
- Added support for CONFIGURATION, RESOURCE, TASK, and PROGRAM (instance) keywords in ST.
|
|
5
|
+
- Added "required" arrays to programmers for communicating required parameters beyond the base params when calling program.
|
|
6
|
+
|
|
3
7
|
## [1.0.23] 2026-03-03
|
|
4
8
|
- Changed IEC parser to interpret Function Blocks that are actually standard functions to a formal function call.
|
|
5
9
|
- Fixed nodejs/jint compiles to put executables in a bin folder.
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
21
21
|
import { dirname } from 'node:path';
|
|
22
22
|
import { Compiler, IECLanguage, OutputType, CommunicationProtocol } from './Compiler.js';
|
|
23
23
|
import * as iec from './iec-parser/parser.js';
|
|
24
|
-
import { parseStructuredText } from './st-parser/parser.js';
|
|
24
|
+
import { parseStructuredText, buildCompilerMetadataDirectives } from './st-parser/parser.js';
|
|
25
25
|
import { transpile } from './st-parser/gcctranspiler.js';
|
|
26
26
|
import { DEFAULT_ARDUINO_FQBN } from './arduinoDefaults.js';
|
|
27
27
|
import { getManagedArduinoCliPath, getManagedArduinoCliExecOptions } from '../toolchains.js';
|
|
@@ -161,7 +161,9 @@ export class ArduinoCompiler extends Compiler {
|
|
|
161
161
|
let taskCode = '';
|
|
162
162
|
let mapCode = '';
|
|
163
163
|
|
|
164
|
-
const
|
|
164
|
+
const metadataDirectives = buildCompilerMetadataDirectives(parsed);
|
|
165
|
+
const metadataAwareSource = metadataDirectives.length > 0 ? `${metadataDirectives}${sourceCode}` : sourceCode;
|
|
166
|
+
const lines = metadataAwareSource.split('\n');
|
|
165
167
|
lines.forEach((line) => {
|
|
166
168
|
if (line.trim().startsWith('//Task=')) {
|
|
167
169
|
const task = JSON.parse(line.substring(line.indexOf('=') + 1).trim());
|
|
@@ -20,7 +20,7 @@ import fs from 'fs';
|
|
|
20
20
|
import path from "path";
|
|
21
21
|
import { Compiler, IECLanguage, OutputType, CommunicationProtocol } from './Compiler.js';
|
|
22
22
|
import * as iec from "./iec-parser/parser.js";
|
|
23
|
-
import { parseStructuredText } from './st-parser/parser.js';
|
|
23
|
+
import { parseStructuredText, buildCompilerMetadataDirectives } from './st-parser/parser.js';
|
|
24
24
|
import { transpile } from './st-parser/gcctranspiler.js';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { dirname } from 'node:path';
|
|
@@ -146,7 +146,9 @@ export class CPPCompiler extends Compiler {
|
|
|
146
146
|
if(typeof resourceName !== "undefined" && resourceName !== null){
|
|
147
147
|
plcname = resourceName;
|
|
148
148
|
}
|
|
149
|
-
const
|
|
149
|
+
const metadataDirectives = buildCompilerMetadataDirectives(parsed);
|
|
150
|
+
const metadataAwareSource = metadataDirectives.length > 0 ? `${metadataDirectives}${sourceCode}` : sourceCode;
|
|
151
|
+
const lines = metadataAwareSource.split("\n");
|
|
150
152
|
lines.forEach((line) => {
|
|
151
153
|
if(line.trim().startsWith("//Task=")){
|
|
152
154
|
var task = JSON.parse(line.substring(line.indexOf("=") + 1).trim());
|
|
@@ -19,7 +19,7 @@ import os from "os";
|
|
|
19
19
|
import path from "path";
|
|
20
20
|
import { Compiler, IECLanguage, OutputType, CommunicationProtocol } from './Compiler.js';
|
|
21
21
|
import * as iec from "./iec-parser/parser.js";
|
|
22
|
-
import { parseStructuredText } from './st-parser/parser.js';
|
|
22
|
+
import { parseStructuredText, buildCompilerMetadataDirectives } from './st-parser/parser.js';
|
|
23
23
|
import { transpile } from './st-parser/jstranspiler.js';
|
|
24
24
|
import which from "which";
|
|
25
25
|
import { fileURLToPath } from "url";
|
|
@@ -114,7 +114,9 @@ export class JSCompiler extends Compiler {
|
|
|
114
114
|
if(typeof resourceName !== "undefined" && resourceName !== null){
|
|
115
115
|
plcname = resourceName;
|
|
116
116
|
}
|
|
117
|
-
const
|
|
117
|
+
const metadataDirectives = buildCompilerMetadataDirectives(parsed);
|
|
118
|
+
const metadataAwareSource = metadataDirectives.length > 0 ? `${metadataDirectives}${sourceCode}` : sourceCode;
|
|
119
|
+
const lines = metadataAwareSource.split("\n");
|
|
118
120
|
lines.forEach((line) => {
|
|
119
121
|
if(line.trim().startsWith("//Task=")){
|
|
120
122
|
var task = JSON.parse(line.substring(line.indexOf("=") + 1).trim());
|
|
@@ -32,6 +32,15 @@ import { tokenize } from './tokenizer.js';
|
|
|
32
32
|
export function parseStructuredText(code) {
|
|
33
33
|
const tokens = tokenize(code);
|
|
34
34
|
let position = 0;
|
|
35
|
+
const TOP_LEVEL_STARTERS = new Set([
|
|
36
|
+
'PROGRAM',
|
|
37
|
+
'FUNCTION',
|
|
38
|
+
'FUNCTION_BLOCK',
|
|
39
|
+
'VAR_GLOBAL',
|
|
40
|
+
'CONFIGURATION',
|
|
41
|
+
'RESOURCE',
|
|
42
|
+
'TASK'
|
|
43
|
+
]);
|
|
35
44
|
|
|
36
45
|
function peek(offset = 0) {
|
|
37
46
|
return tokens[position + offset];
|
|
@@ -55,19 +64,101 @@ export function parseStructuredText(code) {
|
|
|
55
64
|
|
|
56
65
|
switch (token.value.toUpperCase()) {
|
|
57
66
|
case 'PROGRAM':
|
|
58
|
-
return
|
|
67
|
+
return parseProgramOrInstance();
|
|
59
68
|
case 'FUNCTION':
|
|
60
69
|
return parseFunction();
|
|
61
70
|
case 'FUNCTION_BLOCK':
|
|
62
71
|
return parseFunctionBlock();
|
|
63
72
|
case 'VAR_GLOBAL':
|
|
64
73
|
return parseGlobalVarSection();
|
|
74
|
+
case 'TASK':
|
|
75
|
+
return parseTask();
|
|
76
|
+
case 'CONFIGURATION':
|
|
77
|
+
return parseContainer('CONFIGURATION', 'END_CONFIGURATION');
|
|
78
|
+
case 'RESOURCE':
|
|
79
|
+
return parseContainer('RESOURCE', 'END_RESOURCE');
|
|
65
80
|
default:
|
|
66
81
|
consume();
|
|
67
82
|
return null;
|
|
68
83
|
}
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
function parseContainer(startKeyword, endKeyword) {
|
|
87
|
+
expect(startKeyword);
|
|
88
|
+
const blocks = [];
|
|
89
|
+
|
|
90
|
+
while (peek() && peek().value.toUpperCase() !== endKeyword) {
|
|
91
|
+
const upper = peek().value.toUpperCase();
|
|
92
|
+
if (TOP_LEVEL_STARTERS.has(upper)) {
|
|
93
|
+
const block = parseBlock();
|
|
94
|
+
if (Array.isArray(block)) blocks.push(...block);
|
|
95
|
+
else if (block) blocks.push(block);
|
|
96
|
+
} else {
|
|
97
|
+
consume();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
expect(endKeyword);
|
|
102
|
+
return blocks;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseTask() {
|
|
106
|
+
expect('TASK');
|
|
107
|
+
const name = consume().value;
|
|
108
|
+
let interval = '1000';
|
|
109
|
+
let priority = '1';
|
|
110
|
+
|
|
111
|
+
if (peek()?.value === '(') {
|
|
112
|
+
consume();
|
|
113
|
+
while (peek() && peek().value !== ')') {
|
|
114
|
+
const key = consume().value.toUpperCase();
|
|
115
|
+
if (peek()?.value === ':=') consume();
|
|
116
|
+
|
|
117
|
+
const valueTokens = [];
|
|
118
|
+
while (peek() && peek().value !== ',' && peek().value !== ')') {
|
|
119
|
+
valueTokens.push(consume().value);
|
|
120
|
+
}
|
|
121
|
+
const value = valueTokens.join('');
|
|
122
|
+
|
|
123
|
+
if (key === 'INTERVAL') interval = normalizeTaskInterval(value);
|
|
124
|
+
else if (key === 'PRIORITY') priority = normalizeNumericString(value, '1');
|
|
125
|
+
|
|
126
|
+
if (peek()?.value === ',') consume();
|
|
127
|
+
}
|
|
128
|
+
expect(')');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (peek()?.value === ';') consume();
|
|
132
|
+
return { type: 'TaskDeclaration', name, interval, priority };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function normalizeNumericString(value, fallback) {
|
|
136
|
+
const match = String(value || '').match(/-?\d+/);
|
|
137
|
+
return match ? match[0] : fallback;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizeTaskInterval(value) {
|
|
141
|
+
const text = String(value || '').trim();
|
|
142
|
+
if (!text) return '1000';
|
|
143
|
+
|
|
144
|
+
if (/^-?\d+$/.test(text)) return text;
|
|
145
|
+
|
|
146
|
+
const duration = text.match(/^T#?([+-]?\d+(?:\.\d+)?)(MS|S|M|H|D)?$/i);
|
|
147
|
+
if (!duration) return normalizeNumericString(text, '1000');
|
|
148
|
+
|
|
149
|
+
const amount = Number(duration[1]);
|
|
150
|
+
const unit = (duration[2] || 'MS').toUpperCase();
|
|
151
|
+
const multipliers = {
|
|
152
|
+
MS: 1,
|
|
153
|
+
S: 1000,
|
|
154
|
+
M: 60000,
|
|
155
|
+
H: 3600000,
|
|
156
|
+
D: 86400000
|
|
157
|
+
};
|
|
158
|
+
const ms = Math.round(amount * (multipliers[unit] || 1));
|
|
159
|
+
return String(ms);
|
|
160
|
+
}
|
|
161
|
+
|
|
71
162
|
function parseGlobalVarSection() {
|
|
72
163
|
expect('VAR_GLOBAL');
|
|
73
164
|
const variables = [];
|
|
@@ -342,9 +433,22 @@ function parseStatementsUntil(endTokens) {
|
|
|
342
433
|
return peek(1)?.value === ':' || peek(1)?.value === ',';
|
|
343
434
|
}
|
|
344
435
|
|
|
345
|
-
function
|
|
436
|
+
function parseProgramOrInstance() {
|
|
346
437
|
expect('PROGRAM');
|
|
347
438
|
const name = consume().value;
|
|
439
|
+
|
|
440
|
+
if (peek()?.value?.toUpperCase() === 'WITH' || peek()?.value === ':') {
|
|
441
|
+
let associatedTaskName = '';
|
|
442
|
+
if (peek()?.value?.toUpperCase() === 'WITH') {
|
|
443
|
+
consume();
|
|
444
|
+
associatedTaskName = consume().value;
|
|
445
|
+
}
|
|
446
|
+
expect(':');
|
|
447
|
+
const typeName = consume().value;
|
|
448
|
+
if (peek()?.value === ';') consume();
|
|
449
|
+
return { type: 'ProgramInstanceDeclaration', name, typeName, associatedTaskName };
|
|
450
|
+
}
|
|
451
|
+
|
|
348
452
|
const vars = [];
|
|
349
453
|
const stmts = [];
|
|
350
454
|
|
|
@@ -395,8 +499,21 @@ function parseStatementsUntil(endTokens) {
|
|
|
395
499
|
const body = [];
|
|
396
500
|
while (position < tokens.length) {
|
|
397
501
|
const block = parseBlock();
|
|
398
|
-
if (block) body.push(block);
|
|
502
|
+
if (Array.isArray(block)) body.push(...block);
|
|
503
|
+
else if (block) body.push(block);
|
|
399
504
|
}
|
|
400
505
|
|
|
401
506
|
return { type: 'Program', body };
|
|
402
507
|
}
|
|
508
|
+
|
|
509
|
+
export function buildCompilerMetadataDirectives(ast) {
|
|
510
|
+
const lines = [];
|
|
511
|
+
for (const block of ast?.body || []) {
|
|
512
|
+
if (block?.type === 'TaskDeclaration') {
|
|
513
|
+
lines.push(`//Task={"Name":"${block.name}", "Interval":"${block.interval}", "Priority":"${block.priority}"}`);
|
|
514
|
+
} else if (block?.type === 'ProgramInstanceDeclaration') {
|
|
515
|
+
lines.push(`//Instance={"TypeName":"${block.typeName}", "Name":"${block.name}", "AssociatedTaskName":"${block.associatedTaskName || ''}"}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return lines.join('\n') + (lines.length > 0 ? '\n' : '');
|
|
519
|
+
}
|
package/src/nodalis.js
CHANGED