nodalis-compiler
Version:
Compiles IEC-61131-3/10 languages into code that can be used as a PLC on multiple platforms.
156 lines (141 loc) • 4.28 kB
JavaScript
/* eslint-disable curly */
/* eslint-disable eqeqeq */
// Copyright [2025] Nathan Skipper
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @description Expression Converter
* @author Nathan Skipper, MTI
* @version 1.0.2
* @copyright Apache 2.0
*/
/**
* Converts an ST expression to be more understable to JS and C++
* @param {Array | string} expr An array of tokens or a string representing the expression.
* @param {boolean} isjsfb Expresses whether this expression is within a JS function block.
* @param {string[]} jsfbVars An array of variable names defined in the JS function block.
* @returns {string} Returns a converted expression.
*/
export function convertExpression(expr, isjsfb = false, jsfbVars = [], isjs=false) {
if (Array.isArray(expr)) {
if (!isjsfb) {
expr = expr.join(" ");
} else {
let jsexpr = "";
expr.forEach((e) => {
let ev = e.split(".")[0];
if (jsfbVars.includes(ev)) {
ev = "this." + e;
}
jsexpr += (jsexpr ? " " : "") + ev;
});
expr = jsexpr;//.replace(/([^\s])/g, ' $1 ').replace(/\s+/g, ' ').trim(); // ✨ ensure spacing
}
}
let results = expr
.replace(/\bAND\b/gi, '&&')
.replace(/\bOR\b/gi, '||')
.replace(/\bNOT\b/gi, '!')
.replace(/\bMOD\b/gi, '%')
.replace(/\bDIV\b/gi, '/')
.replace(/<>/g, '!=')
.replace(/:=/g, '=')
.replace(/\bTRUE\b/gi, 'true')
.replace(/\bFALSE\b/gi, 'false')
.replace(/\b(?<![><!])=(?!=)/g, '=='); // ✅ fix assignment/comparison
const tokens = results.split(/\s+/);
for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === '=' &&
tokens[i - 1] !== '<' &&
tokens[i - 1] !== '>' &&
tokens[i - 1] !== '!' &&
tokens[i + 1] !== '='
) {
tokens[i] = '==';
}
}
results = tokens.join(' ');
// Replace %I/Q/M references
const parts = results.split(/\s+/);
results = parts.map(e => {
// Don't touch raw address reads
if (/^%[IQM][XBWDL]?\d+(\.\d+)?$/i.test(e)) return getReadAddressExpression(e);
// Don't wrap literals or operators
if (/^(true|false|null|\d+|!|&&|\|\||==|!=|[<>=+\-*/()])$/i.test(e)) return e;
// Don't wrap known function expressions (e.g., getBit)
if (/^getBit\(/.test(e)) return e;
// Don't wrap dot-bit references already processed
if (/^&?[A-Za-z_]\w*\.\d+$/.test(e)) return e;
// Otherwise, wrap in resolve()
if(isjs)
return `resolve(${e})`;
else return e;
}).join(' ');
if (results.indexOf("read") === -1) {
results = results.replace(/\b(?!%)(([A-Za-z_]\w*)\.(\d+))\b/g, (_, full, base, bit) => {
return `getBit(&${base}, ${bit})`;
});
}
return results;
}
/**
*
* @param {string} addr
* @returns
*/
export function getReadAddressExpression(addr){
var result = `readDWord("${addr}")`;
try{
if(addr.indexOf(".")){
result = `readBit("${addr}")`;
}
else{
var width = addr.substring(2, 3).toUpperCase();
switch(width){
case "X":
result = `readByte("${addr}")`;
break;
case "W":
`readWord("${addr}")`;
break;
}
}
}
catch(e){
console.error(e);
}
return result;
}
export function getWriteAddressExpression(addr, value){
var result = `writeDWord("${addr}", ${value})`;
try{
if(addr.indexOf(".") > -1){
result = `writeBit("${addr}", ${value})`;
}
else{
var width = addr.substring(2, 3).toUpperCase();
switch(width){
case "X":
result = `writeByte("${addr}", ${value})`;
break;
case "W":
`writeWord("${addr}", ${value})`;
break;
}
}
}
catch(e){
console.error(e);
}
return result;
}