nodalis-compiler 1.0.19 → 1.0.21
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 -2
- package/src/compilers/CPPCompiler.js +2 -2
- package/src/compilers/Compiler.js +10 -0
- package/src/compilers/JSCompiler.js +18 -15
- 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/src/programmers/utils.js +26 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.0.
|
|
3
|
+
## [1.0.21] 2026-03-03
|
|
4
4
|
- Changed IEC parser to interpret Function Blocks that are actually standard functions to a formal function call.
|
|
5
|
+
- Fixed nodejs/jint compiles to put executables in a bin folder.
|
|
6
|
+
- Fixed syntax errors with repeat.
|
|
5
7
|
|
|
6
8
|
## [1.0.17] 2026-02-25
|
|
7
9
|
- Added support for compilation of multiple ST files as a single project.
|
package/package.json
CHANGED
|
@@ -255,7 +255,7 @@ void loop() {
|
|
|
255
255
|
fs.cpSync(path.join(supportDir, 'gpio.cpp'), path.join(outputPath, 'gpio.cpp'), { force: true });
|
|
256
256
|
fs.cpSync(path.join(supportDir, 'json.hpp'), path.join(outputPath, 'json.hpp'), { force: true });
|
|
257
257
|
|
|
258
|
-
if (
|
|
258
|
+
if (this.isExecutableOutput()) {
|
|
259
259
|
const arduinoCli = compilerConfig.arduino_cli || compilerConfig.arduinoCli || 'arduino-cli';
|
|
260
260
|
const arduinoFqbn = this.resolveArduinoFqbn(target, compilerConfig);
|
|
261
261
|
if (!arduinoFqbn) {
|
|
@@ -266,7 +266,7 @@ void loop() {
|
|
|
266
266
|
this.ensureArduinoCoreInstalled(arduinoCli, arduinoFqbn);
|
|
267
267
|
|
|
268
268
|
const buildDir = path.join(outputPath, 'build');
|
|
269
|
-
const binDir =
|
|
269
|
+
const binDir = this.getExecutableOutputPath();
|
|
270
270
|
fs.mkdirSync(buildDir, { recursive: true });
|
|
271
271
|
fs.mkdirSync(binDir, { recursive: true });
|
|
272
272
|
const arduinoCompileCmd = `${arduinoCli} compile --fqbn "${arduinoFqbn}" "${outputPath}" --build-path "${buildDir}" --output-dir "${binDir}" --export-binaries`;
|
|
@@ -260,7 +260,7 @@ int main() {
|
|
|
260
260
|
const pathTo = name => path.join(outputPath, name);
|
|
261
261
|
const targetInfo = this.resolveTarget(target);
|
|
262
262
|
|
|
263
|
-
if (
|
|
263
|
+
if (this.isExecutableOutput()) {
|
|
264
264
|
const requestedTarget = target ?? `${targetInfo.os}-${targetInfo.arch}`;
|
|
265
265
|
const hostOs = this.getHostOS();
|
|
266
266
|
const hostArch = this.getHostArch();
|
|
@@ -271,7 +271,7 @@ int main() {
|
|
|
271
271
|
const archFlags = this.getArchFlags(targetInfo.os, targetInfo.arch, compiler);
|
|
272
272
|
const formatFlags = (flags = []) => (flags.length ? `${flags.join(' ')} ` : '');
|
|
273
273
|
const isWindowsTarget = targetInfo.os === 'windows';
|
|
274
|
-
const binDir =
|
|
274
|
+
const binDir = this.getExecutableOutputPath();
|
|
275
275
|
fs.mkdirSync(binDir, { recursive: true });
|
|
276
276
|
|
|
277
277
|
// Step 2: Compile open62541.c with C compiler
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// See the License for the specific language governing permissions and
|
|
15
15
|
// limitations under the License.
|
|
16
16
|
|
|
17
|
+
import path from 'path';
|
|
18
|
+
|
|
17
19
|
export const IECLanguage = Object.freeze({
|
|
18
20
|
LADDER_DIAGRAM: 'LD',
|
|
19
21
|
STRUCTURED_TEXT: 'ST',
|
|
@@ -60,6 +62,14 @@ export class Compiler {
|
|
|
60
62
|
this.options = options;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
isExecutableOutput() {
|
|
66
|
+
return this.options?.outputType === OutputType.EXECUTABLE;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getExecutableOutputPath() {
|
|
70
|
+
return path.join(this.options.outputPath, 'bin');
|
|
71
|
+
}
|
|
72
|
+
|
|
63
73
|
/** @returns {string[]} */
|
|
64
74
|
get supportedLanguages() {
|
|
65
75
|
throw new Error('supportedLanguages must be implemented by subclass.');
|
|
@@ -224,8 +224,8 @@ export function run(){
|
|
|
224
224
|
}
|
|
225
225
|
fs.mkdirSync(outputPath, { recursive: true });
|
|
226
226
|
fs.writeFileSync(jsFile, jsCode);
|
|
227
|
-
const binDir =
|
|
228
|
-
if (
|
|
227
|
+
const binDir = this.getExecutableOutputPath();
|
|
228
|
+
if (this.isExecutableOutput()) {
|
|
229
229
|
fs.mkdirSync(binDir, { recursive: true });
|
|
230
230
|
}
|
|
231
231
|
if(sourcePath.toLowerCase().endsWith(".iec") || sourcePath.toLowerCase().endsWith(".xml") || directoryBundleMode){
|
|
@@ -243,34 +243,37 @@ export function run(){
|
|
|
243
243
|
];
|
|
244
244
|
|
|
245
245
|
let coreDir = path.resolve(__dirname + '/support/nodejs');
|
|
246
|
+
const runtimeOutputDir = this.isExecutableOutput() ? binDir : outputPath;
|
|
246
247
|
|
|
247
248
|
for (const file of coreFiles) {
|
|
248
|
-
fs.copyFileSync(path.join(coreDir, file), path.join(
|
|
249
|
+
fs.copyFileSync(path.join(coreDir, file), path.join(runtimeOutputDir, file));
|
|
249
250
|
}
|
|
250
|
-
if (
|
|
251
|
-
fs.copyFileSync(jsFile, path.join(
|
|
251
|
+
if (this.isExecutableOutput()) {
|
|
252
|
+
fs.copyFileSync(jsFile, path.join(runtimeOutputDir, 'nodalisplc.js'));
|
|
252
253
|
}
|
|
253
|
-
writePackageJson(
|
|
254
|
-
installDependencies(
|
|
254
|
+
writePackageJson(runtimeOutputDir, plcname);
|
|
255
|
+
installDependencies(runtimeOutputDir);
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
|
|
258
|
-
if (target === "jint" &&
|
|
259
|
+
if (target === "jint" && this.isExecutableOutput()) {
|
|
259
260
|
const supportDir = path.resolve(__dirname, "support/jint/Nodalis");
|
|
260
261
|
const buildScript = os.platform() === "win32" ? "build.bat" : "build.sh";
|
|
262
|
+
const projectOutputDir = outputPath;
|
|
263
|
+
const runtimeOutputDir = binDir;
|
|
261
264
|
|
|
262
|
-
//
|
|
263
|
-
fs.cpSync(supportDir,
|
|
265
|
+
// Keep the Jint project sources in the main output directory.
|
|
266
|
+
fs.cpSync(supportDir, projectOutputDir, { recursive: true, force: true });
|
|
264
267
|
|
|
265
|
-
//
|
|
266
|
-
const buildPath = path.resolve(path.join(
|
|
268
|
+
// Build from the main output directory, then move publish artifacts under bin.
|
|
269
|
+
const buildPath = path.resolve(path.join(projectOutputDir, buildScript));
|
|
267
270
|
if(buildPath.endsWith(".sh")){
|
|
268
271
|
fs.chmodSync(buildPath, 0o755); // make executable
|
|
269
272
|
}
|
|
270
|
-
execSync(buildPath, { cwd: path.resolve(
|
|
273
|
+
execSync(buildPath, { cwd: path.resolve(projectOutputDir), stdio: "inherit", shell: true });
|
|
271
274
|
|
|
272
275
|
// 3. Copy the generated JS file to each publish folder
|
|
273
|
-
const publishRoot = path.join(
|
|
276
|
+
const publishRoot = path.join(projectOutputDir, "publish");
|
|
274
277
|
|
|
275
278
|
const platforms = fs.readdirSync(publishRoot, { withFileTypes: true })
|
|
276
279
|
.filter(d => d.isDirectory())
|
|
@@ -301,7 +304,7 @@ export function run(){
|
|
|
301
304
|
|
|
302
305
|
for (const platformDir of platforms) {
|
|
303
306
|
const platformName = path.basename(platformDir);
|
|
304
|
-
const platformBinDir = path.join(
|
|
307
|
+
const platformBinDir = path.join(runtimeOutputDir, platformName);
|
|
305
308
|
fs.mkdirSync(platformBinDir, { recursive: true });
|
|
306
309
|
fs.cpSync(platformDir, platformBinDir, { recursive: true, force: true });
|
|
307
310
|
}
|
|
@@ -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',
|
package/src/programmers/utils.js
CHANGED
|
@@ -235,24 +235,41 @@ export async function inferEntryPoint(sourcePath, runtime = 'auto', providedEntr
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const candidates = ['nodalisplc', 'nodalisplc.exe', 'nodalisplc.js'];
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
const candidateDirectories = ['bin', ''];
|
|
239
|
+
for (const directory of candidateDirectories) {
|
|
240
|
+
for (const candidate of candidates) {
|
|
241
|
+
const relativeCandidate = directory ? path.join(directory, candidate) : candidate;
|
|
242
|
+
if (await exists(path.join(sourcePath, relativeCandidate))) {
|
|
243
|
+
return toPosixPath(relativeCandidate);
|
|
244
|
+
}
|
|
241
245
|
}
|
|
242
246
|
}
|
|
243
247
|
|
|
248
|
+
const inferredRuntime = inferRuntime('', runtime);
|
|
249
|
+
if (inferredRuntime === 'node') {
|
|
250
|
+
const binNodeEntry = path.join('bin', 'nodalisplc.js');
|
|
251
|
+
if (await exists(path.join(sourcePath, binNodeEntry))) {
|
|
252
|
+
return toPosixPath(binNodeEntry);
|
|
253
|
+
}
|
|
254
|
+
return "nodalisplc.js"
|
|
255
|
+
}
|
|
256
|
+
|
|
244
257
|
const entries = await fs.readdir(sourcePath, { withFileTypes: true });
|
|
245
258
|
const files = entries.filter(entry => entry.isFile()).map(entry => entry.name);
|
|
246
|
-
if (files.length
|
|
247
|
-
|
|
259
|
+
if (files.length > 0) {
|
|
260
|
+
return files[0];
|
|
248
261
|
}
|
|
249
262
|
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
252
|
-
|
|
263
|
+
const binEntriesPath = path.join(sourcePath, 'bin');
|
|
264
|
+
if (await exists(binEntriesPath)) {
|
|
265
|
+
const binEntries = await fs.readdir(binEntriesPath, { withFileTypes: true });
|
|
266
|
+
const binFiles = binEntries.filter(entry => entry.isFile()).map(entry => entry.name);
|
|
267
|
+
if (binFiles.length > 0) {
|
|
268
|
+
return toPosixPath(path.join('bin', binFiles[0]));
|
|
269
|
+
}
|
|
253
270
|
}
|
|
254
271
|
|
|
255
|
-
|
|
272
|
+
throw new Error(`Could not infer entry point for source directory: ${sourcePath}`);
|
|
256
273
|
}
|
|
257
274
|
|
|
258
275
|
export function quoteForPosixSingle(value) {
|