nodalis-compiler 1.0.20 → 1.0.22
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 +3 -1
- package/package.json +1 -1
- package/src/compilers/ArduinoCompiler.js +2 -3
- package/src/compilers/CPPCompiler.js +2 -3
- package/src/compilers/Compiler.js +21 -0
- package/src/compilers/JSCompiler.js +2 -3
- package/src/compilers/st-parser/expressionConverter.js +1 -1
- package/src/compilers/st-parser/parser.js +2 -1
- package/src/compilers/st-parser/tokenizer.js +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.0.
|
|
3
|
+
## [1.0.22] 2026-03-03
|
|
4
4
|
- Changed IEC parser to interpret Function Blocks that are actually standard functions to a formal function call.
|
|
5
5
|
- Fixed nodejs/jint compiles to put executables in a bin folder.
|
|
6
|
+
- Fixed syntax errors with repeat.
|
|
7
|
+
- Prevent stale files in ST bundle compile.
|
|
6
8
|
|
|
7
9
|
## [1.0.17] 2026-02-25
|
|
8
10
|
- Added support for compilation of multiple ST files as a single project.
|
package/package.json
CHANGED
|
@@ -115,6 +115,7 @@ export class ArduinoCompiler extends Compiler {
|
|
|
115
115
|
let sourceCode = '';
|
|
116
116
|
let bundleEntryProgram = '';
|
|
117
117
|
if (directoryBundleMode) {
|
|
118
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
118
119
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
119
120
|
sourceCode = combinedSource;
|
|
120
121
|
bundleEntryProgram = entryProgramName;
|
|
@@ -285,9 +286,7 @@ void loop() {
|
|
|
285
286
|
}
|
|
286
287
|
|
|
287
288
|
loadStructuredTextBundle(sourcePath, resourceName) {
|
|
288
|
-
const stFiles =
|
|
289
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
290
|
-
.map((entry) => entry.name);
|
|
289
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
291
290
|
|
|
292
291
|
if (stFiles.length === 0) {
|
|
293
292
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -103,6 +103,7 @@ export class CPPCompiler extends Compiler {
|
|
|
103
103
|
let bundleEntryProgram = '';
|
|
104
104
|
|
|
105
105
|
if (directoryBundleMode) {
|
|
106
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
106
107
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
107
108
|
sourceCode = combinedSource;
|
|
108
109
|
bundleEntryProgram = entryProgramName;
|
|
@@ -400,9 +401,7 @@ int main() {
|
|
|
400
401
|
}
|
|
401
402
|
|
|
402
403
|
loadStructuredTextBundle(sourcePath, resourceName) {
|
|
403
|
-
const stFiles =
|
|
404
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
405
|
-
.map((entry) => entry.name);
|
|
404
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
406
405
|
|
|
407
406
|
if (stFiles.length === 0) {
|
|
408
407
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// See the License for the specific language governing permissions and
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
|
+
import fs from 'fs';
|
|
17
18
|
import path from 'path';
|
|
18
19
|
|
|
19
20
|
export const IECLanguage = Object.freeze({
|
|
@@ -70,6 +71,26 @@ export class Compiler {
|
|
|
70
71
|
return path.join(this.options.outputPath, 'bin');
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
getStructuredTextBundleArtifactName() {
|
|
75
|
+
return 'nodalisplc.st';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
cleanupStructuredTextBundleArtifacts(sourcePath) {
|
|
79
|
+
const artifactPath = path.join(sourcePath, this.getStructuredTextBundleArtifactName());
|
|
80
|
+
if (fs.existsSync(artifactPath) && fs.lstatSync(artifactPath).isFile()) {
|
|
81
|
+
fs.rmSync(artifactPath, { force: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
listStructuredTextBundleFiles(sourcePath) {
|
|
86
|
+
const bundleArtifactName = this.getStructuredTextBundleArtifactName().toLowerCase();
|
|
87
|
+
return fs.readdirSync(sourcePath, { withFileTypes: true })
|
|
88
|
+
.filter((entry) => entry.isFile()
|
|
89
|
+
&& entry.name.toLowerCase().endsWith('.st')
|
|
90
|
+
&& entry.name.toLowerCase() !== bundleArtifactName)
|
|
91
|
+
.map((entry) => entry.name);
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
/** @returns {string[]} */
|
|
74
95
|
get supportedLanguages() {
|
|
75
96
|
throw new Error('supportedLanguages must be implemented by subclass.');
|
|
@@ -65,6 +65,7 @@ export class JSCompiler extends Compiler {
|
|
|
65
65
|
let sourceCode = '';
|
|
66
66
|
let bundleEntryProgram = '';
|
|
67
67
|
if (directoryBundleMode) {
|
|
68
|
+
this.cleanupStructuredTextBundleArtifacts(sourcePath);
|
|
68
69
|
const { combinedSource, entryProgramName } = this.loadStructuredTextBundle(sourcePath, resourceName);
|
|
69
70
|
sourceCode = combinedSource;
|
|
70
71
|
bundleEntryProgram = entryProgramName;
|
|
@@ -314,9 +315,7 @@ export function run(){
|
|
|
314
315
|
}
|
|
315
316
|
|
|
316
317
|
JSCompiler.prototype.loadStructuredTextBundle = function (sourcePath, resourceName) {
|
|
317
|
-
const stFiles =
|
|
318
|
-
.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.st'))
|
|
319
|
-
.map((entry) => entry.name);
|
|
318
|
+
const stFiles = this.listStructuredTextBundleFiles(sourcePath);
|
|
320
319
|
|
|
321
320
|
if (stFiles.length === 0) {
|
|
322
321
|
throw new Error(`No .st files found in source directory "${sourcePath}".`);
|
|
@@ -127,7 +127,7 @@ export function convertExpression(expr, isjsfb = false, jsfbVars = [], isjs=fals
|
|
|
127
127
|
if (/^%[IQM][XBWDL]?\d+(\.\d+)?$/i.test(e)) return getReadAddressExpression(e);
|
|
128
128
|
|
|
129
129
|
// Don't wrap literals or operators
|
|
130
|
-
if (/^(true|false|null|[+-]?\d+|[+-]?(?:(?:\d+\.\d*|\d*\.\d+)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)|0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-f]
|
|
130
|
+
if (/^(true|false|null|[+-]?\d+|[+-]?(?:(?:\d+\.\d*|\d*\.\d+)(?:[eE][+-]?\d+)?|\d+[eE][+-]?\d+)|0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-f]+|!|&&|\|\||==|!=|>=|<=|>|<|=|\+|-|\*|\/|%|\(|\)|,|&|\||\^)$/i.test(e)) return e;
|
|
131
131
|
|
|
132
132
|
// Don't wrap known function expressions (e.g., getBit)
|
|
133
133
|
if (/^getBit\(/.test(e)) return e;
|
|
@@ -292,9 +292,10 @@ function parseStatementsUntil(endTokens) {
|
|
|
292
292
|
const body = parseStatements('UNTIL');
|
|
293
293
|
expect('UNTIL');
|
|
294
294
|
const condition = [];
|
|
295
|
-
while (peek() && peek().value !== '
|
|
295
|
+
while (peek() && peek().value.toUpperCase() !== 'END_REPEAT') {
|
|
296
296
|
condition.push(consume().value);
|
|
297
297
|
}
|
|
298
|
+
expect('END_REPEAT');
|
|
298
299
|
if (peek()?.value === ';') consume();
|
|
299
300
|
return { type: 'REPEAT', condition, body };
|
|
300
301
|
}
|
|
@@ -72,20 +72,25 @@ while ((match = regex.exec(code)) !== null) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function normalizeNumericLiteral(value) {
|
|
75
|
-
const
|
|
75
|
+
const literal = String(value);
|
|
76
|
+
const boolMatch = literal.match(new RegExp(`^${BOOL_TYPE_PATTERN}#(TRUE|FALSE|1|0)$`, 'i'));
|
|
76
77
|
if (boolMatch) {
|
|
77
78
|
const boolLiteral = boolMatch[1].toUpperCase();
|
|
78
79
|
if (boolLiteral === 'TRUE' || boolLiteral === '1') return 'TRUE';
|
|
79
80
|
return 'FALSE';
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
const typedValue =
|
|
83
|
+
const typedValue = literal.replace(
|
|
83
84
|
new RegExp(`^(?:${INTEGER_TYPE_PATTERN}|${REAL_TYPE_PATTERN}|${BOOL_TYPE_PATTERN})#`, 'i'),
|
|
84
85
|
''
|
|
85
86
|
);
|
|
86
87
|
|
|
87
88
|
const match = typedValue.match(/^(2|8|16)#([0-9a-f_]+)$/i);
|
|
88
|
-
if (!match)
|
|
89
|
+
if (!match) {
|
|
90
|
+
const normalized = typedValue.replace(/_/g, '');
|
|
91
|
+
if (/^[+-]?\d+$/.test(normalized)) return normalizeDecimalIntegerLiteral(normalized);
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
89
94
|
const radix = match[1];
|
|
90
95
|
const digits = match[2].replace(/_/g, '');
|
|
91
96
|
if (radix === '2') return `0b${digits}`;
|
|
@@ -93,6 +98,12 @@ function normalizeNumericLiteral(value) {
|
|
|
93
98
|
return `0x${digits}`;
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
function normalizeDecimalIntegerLiteral(value) {
|
|
102
|
+
const sign = value.startsWith('-') ? '-' : '';
|
|
103
|
+
const digits = value.replace(/^[+-]?/, '').replace(/^0+(?=\d)/, '');
|
|
104
|
+
return `${sign}${digits || '0'}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
function getTokenType(value) {
|
|
97
108
|
const keywords = new Set([
|
|
98
109
|
'PROGRAM', 'FUNCTION_BLOCK', 'FUNCTION', 'VAR_INPUT', 'VAR_OUTPUT', 'VAR', 'END_VAR',
|