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 CHANGED
@@ -1,8 +1,10 @@
1
1
  # Changelog
2
2
 
3
- ## [1.0.20] 2026-03-03
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodalis-compiler",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
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",
@@ -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 = fs.readdirSync(sourcePath, { withFileTypes: true })
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 = fs.readdirSync(sourcePath, { withFileTypes: true })
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 = fs.readdirSync(sourcePath, { withFileTypes: true })
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]+|!|&&|\|\||==|!=|[<>=+\-*/(),&|^])$/i.test(e)) return e;
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 boolMatch = String(value).match(new RegExp(`^${BOOL_TYPE_PATTERN}#(TRUE|FALSE|1|0)$`, 'i'));
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 = String(value).replace(
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) return typedValue.replace(/_/g, '');
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',