nodalis-compiler 1.0.0

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.
Files changed (37) hide show
  1. package/README.md +134 -0
  2. package/package.json +59 -0
  3. package/src/compilers/CPPCompiler.js +272 -0
  4. package/src/compilers/Compiler.js +108 -0
  5. package/src/compilers/JSCompiler.js +293 -0
  6. package/src/compilers/iec-parser/parser.js +4254 -0
  7. package/src/compilers/st-parser/expressionConverter.js +155 -0
  8. package/src/compilers/st-parser/gcctranspiler.js +237 -0
  9. package/src/compilers/st-parser/jstranspiler.js +254 -0
  10. package/src/compilers/st-parser/parser.js +367 -0
  11. package/src/compilers/st-parser/tokenizer.js +78 -0
  12. package/src/compilers/support/generic/json.hpp +25526 -0
  13. package/src/compilers/support/generic/modbus.cpp +378 -0
  14. package/src/compilers/support/generic/modbus.h +124 -0
  15. package/src/compilers/support/generic/nodalis.cpp +421 -0
  16. package/src/compilers/support/generic/nodalis.h +798 -0
  17. package/src/compilers/support/generic/opcua.cpp +267 -0
  18. package/src/compilers/support/generic/opcua.h +50 -0
  19. package/src/compilers/support/generic/open62541.c +151897 -0
  20. package/src/compilers/support/generic/open62541.h +50357 -0
  21. package/src/compilers/support/jint/nodalis/Nodalis.sln +28 -0
  22. package/src/compilers/support/jint/nodalis/NodalisEngine/ModbusClient.cs +200 -0
  23. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.cs +817 -0
  24. package/src/compilers/support/jint/nodalis/NodalisEngine/NodalisEngine.csproj +16 -0
  25. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCClient.cs +172 -0
  26. package/src/compilers/support/jint/nodalis/NodalisEngine/OPCServer.cs +275 -0
  27. package/src/compilers/support/jint/nodalis/NodalisPLC/NodalisPLC.csproj +19 -0
  28. package/src/compilers/support/jint/nodalis/NodalisPLC/Program.cs +197 -0
  29. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.bat +5 -0
  30. package/src/compilers/support/jint/nodalis/NodalisPLC/bootstrap.sh +5 -0
  31. package/src/compilers/support/jint/nodalis/build.bat +25 -0
  32. package/src/compilers/support/jint/nodalis/build.sh +31 -0
  33. package/src/compilers/support/nodejs/IOClient.js +110 -0
  34. package/src/compilers/support/nodejs/modbus.js +115 -0
  35. package/src/compilers/support/nodejs/nodalis.js +662 -0
  36. package/src/compilers/support/nodejs/opcua.js +194 -0
  37. package/src/nodalis.js +174 -0
@@ -0,0 +1,194 @@
1
+ import { IOClient } from './IOClient.js';
2
+ import {
3
+ OPCUAClient,
4
+ AttributeIds,
5
+ DataType,
6
+ StatusCodes,
7
+ Variant,
8
+ OPCUAServer
9
+ } from 'node-opcua';
10
+
11
+ export class OPCClient extends IOClient {
12
+ constructor(endpointUrl) {
13
+ super('OPCUA');
14
+ this.endpointUrl = endpointUrl;
15
+ this.client = OPCUAClient.create({ endpoint_must_exist: false });
16
+ this.session = null;
17
+ }
18
+
19
+ async connect() {
20
+ try {
21
+ if(this.moduleID.length > 0){
22
+ this.endpointUrl = this.moduleID;
23
+ }
24
+ console.log(`Connecting to OPC UA server at ${this.endpointUrl}`);
25
+ await this.client.connect(this.endpointUrl);
26
+ this.session = await this.client.createSession();
27
+ this.connected = true;
28
+ console.log("OPC UA connected.");
29
+ } catch (err) {
30
+ console.error("OPC UA connection error:", err);
31
+ this.connected = false;
32
+ }
33
+ }
34
+
35
+ getNodeId(remote) {
36
+ // Expects node format: "SW2" -> "ns=1;s=SW2"
37
+ return `ns=1;s=${remote}`;
38
+ }
39
+
40
+ async readValue(remote, dataType, callback) {
41
+ if (!this.connected) return callback(null);
42
+ try {
43
+ const nodeId = this.getNodeId(remote);
44
+ const result = await this.session.read({
45
+ nodeId,
46
+ attributeId: AttributeIds.Value
47
+ });
48
+ callback(result.value.value);
49
+ } catch (err) {
50
+ console.error(`Read error [${remote}]`, err);
51
+ callback(null);
52
+ }
53
+ }
54
+
55
+ async writeValue(remote, value, dataType) {
56
+ if (!this.connected) return;
57
+ try {
58
+ const nodeId = this.getNodeId(remote);
59
+ const variant = new Variant({ dataType, value });
60
+ const statusCode = await this.session.write({
61
+ nodeId,
62
+ attributeId: AttributeIds.Value,
63
+ value: { value: variant }
64
+ });
65
+ if (statusCode !== StatusCodes.Good) {
66
+ console.warn(`Write to ${remote} failed: ${statusCode.toString()}`);
67
+ }
68
+ } catch (err) {
69
+ console.error(`Write error [${remote}]`, err);
70
+ }
71
+ }
72
+
73
+ readBit(remote, callback) {
74
+ this.readValue(remote, DataType.Boolean, callback);
75
+ }
76
+
77
+ writeBit(remote, value) {
78
+ this.writeValue(remote, !!value, DataType.Boolean);
79
+ }
80
+
81
+ readByte(remote, callback) {
82
+ this.readValue(remote, DataType.Byte, callback);
83
+ }
84
+
85
+ writeByte(remote, value) {
86
+ this.writeValue(remote, value, DataType.Byte);
87
+ }
88
+
89
+ readWord(remote, callback) {
90
+ this.readValue(remote, DataType.UInt16, callback);
91
+ }
92
+
93
+ writeWord(remote, value) {
94
+ this.writeValue(remote, value, DataType.UInt16);
95
+ }
96
+
97
+ readDWord(remote, callback) {
98
+ this.readValue(remote, DataType.UInt32, callback);
99
+ }
100
+
101
+ writeDWord(remote, value) {
102
+ this.writeValue(remote, value, DataType.UInt32);
103
+ }
104
+ }
105
+
106
+
107
+ export class OPCServer {
108
+ constructor() {
109
+ this.server = new OPCUAServer({
110
+ port: 4840,
111
+ resourcePath: "/UA/Nodalis",
112
+ buildInfo: {
113
+ productName: "NodalisServer",
114
+ buildNumber: "1",
115
+ buildDate: new Date()
116
+ }
117
+ });
118
+
119
+ this.addressMap = new Map(); // maps nodeId -> address string
120
+ this.readCallback = null;
121
+ this.writeCallback = null;
122
+ }
123
+
124
+ setReadWriteHandlers(readHandler, writeHandler) {
125
+ this.readCallback = readHandler;
126
+ this.writeCallback = writeHandler;
127
+ }
128
+
129
+ async initialize() {
130
+ await this.server.initialize();
131
+ const addressSpace = this.server.engine.addressSpace;
132
+ this.namespace = addressSpace.getOwnNamespace();
133
+ }
134
+
135
+ async start() {
136
+ await this.initialize();
137
+ await this.server.start();
138
+ console.log("OPCUA Server is now listening at", this.server.endpoints[0].endpointDescriptions()[0].endpointUrl);
139
+ }
140
+
141
+ async stop() {
142
+ await this.server.shutdown(1000);
143
+ console.log("OPCUA Server has shut down.");
144
+ }
145
+
146
+ mapVariable(varname, addr) {
147
+ const nodeId = `ns=1;s=${varname}`;
148
+ this.addressMap.set(nodeId, addr);
149
+
150
+ this.namespace.addVariable({
151
+ organizedBy: this.server.engine.addressSpace.rootFolder.objects,
152
+ nodeId: nodeId,
153
+ browseName: varname,
154
+ dataType: this._guessDataType(addr),
155
+ value: {
156
+ get: () => this._read(addr),
157
+ set: (variant) => this._write(addr, variant)
158
+ }
159
+ });
160
+ }
161
+
162
+ _read(addr) {
163
+ if (!this.readCallback) return new Variant({ dataType: DataType.Boolean, value: false });
164
+ const val = this.readCallback(addr);
165
+ return new Variant(this._valueToVariant(val));
166
+ }
167
+
168
+ _write(addr, variant) {
169
+ if (!this.writeCallback) return StatusCodes.BadNotWritable;
170
+ this.writeCallback(addr, variant.value);
171
+ return StatusCodes.Good;
172
+ }
173
+
174
+ _guessDataType(addr) {
175
+ if (addr.includes(".")) return DataType.Boolean;
176
+ switch (addr[2]) {
177
+ case 'X': return DataType.Byte;
178
+ case 'W': return DataType.UInt16;
179
+ case 'D': return DataType.UInt32;
180
+ default: return DataType.Boolean;
181
+ }
182
+ }
183
+
184
+ _valueToVariant(value) {
185
+ const type = typeof value;
186
+ if (type === "boolean") return { dataType: DataType.Boolean, value };
187
+ if (type === "number") {
188
+ if (value <= 255) return { dataType: DataType.Byte, value };
189
+ if (value <= 65535) return { dataType: DataType.UInt16, value };
190
+ return { dataType: DataType.UInt32, value };
191
+ }
192
+ return { dataType: DataType.Null, value: null };
193
+ }
194
+ }
package/src/nodalis.js ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable curly */
3
+ /* eslint-disable eqeqeq */
4
+ // Copyright [2025] Nathan Skipper
5
+ //
6
+ // Licensed under the Apache License, Version 2.0 (the "License");
7
+ // you may not use this file except in compliance with the License.
8
+ // You may obtain a copy of the License at
9
+ //
10
+ // http://www.apache.org/licenses/LICENSE-2.0
11
+ //
12
+ // Unless required by applicable law or agreed to in writing, software
13
+ // distributed under the License is distributed on an "AS IS" BASIS,
14
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ // See the License for the specific language governing permissions and
16
+ // limitations under the License.
17
+
18
+ import path from 'path';
19
+ import fs from 'fs';
20
+ import { fileURLToPath } from 'url';
21
+
22
+ // Updated compiler imports
23
+ import { CPPCompiler } from './compilers/CPPCompiler.js';
24
+ import { JSCompiler } from './compilers/JSCompiler.js';
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+
29
+ const availableCompilers = [
30
+ new CPPCompiler(),
31
+ new JSCompiler(),
32
+ ];
33
+
34
+ function validateFileExtension(language, sourcePath) {
35
+ const ext = path.extname(sourcePath).toLowerCase();
36
+ if (language === 'st') {
37
+ if (ext !== '.st' && ext !== '.iec') {
38
+ throw new Error(`Invalid file extension for language 'st'. Expected '.st' or '.iec', got '${ext}'`);
39
+ }
40
+ } else if (language === 'ld') {
41
+ if (ext !== '.iec') {
42
+ throw new Error(`Invalid file extension for language 'ld'. Expected '.iec', got '${ext}'`);
43
+ }
44
+ } else {
45
+ throw new Error(`Unknown language: ${language}`);
46
+ }
47
+ }
48
+
49
+ export class Nodalis {
50
+ constructor() {
51
+ this.compilers = availableCompilers;
52
+ }
53
+
54
+ listCompilers() {
55
+ return this.compilers.map(c => ({
56
+ name: c.constructor.name,
57
+ supportedTargets: c.supportedTargetDevices,
58
+ supportedOutputTypes: c.supportedOutputTypes,
59
+ supportedLanguages: c.supportedLanguages,
60
+ supportedProtocols: c.supportedProtocols,
61
+ compilerVersion: c.compilerVersion,
62
+ }));
63
+ }
64
+
65
+ getCompiler(target, outputType, language) {
66
+ return this.compilers.find(c =>
67
+ c.supportedTargetDevices.includes(target) &&
68
+ c.supportedOutputTypes.includes(outputType) &&
69
+ c.supportedLanguages.includes(language.toUpperCase())
70
+ );
71
+ }
72
+
73
+ async compile({ target, outputType, outputPath, resourceName, sourcePath, language }) {
74
+ validateFileExtension(language, sourcePath);
75
+
76
+ const compiler = this.getCompiler(target, outputType, language);
77
+ if (!compiler) {
78
+ throw new Error(`No compiler found for target "${target}", outputType "${outputType}", and language "${language}"`);
79
+ }
80
+
81
+ compiler.options = {
82
+ sourcePath,
83
+ outputPath,
84
+ resourceName,
85
+ target,
86
+ outputType,
87
+ language,
88
+ };
89
+
90
+ await compiler.compile();
91
+ }
92
+ }
93
+
94
+ // === CLI Entry Point ===
95
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
96
+
97
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
98
+ console.log(`
99
+ Usage:
100
+ node nodalis.js --action <action> [options]
101
+
102
+ Actions:
103
+ --action list-compilers
104
+ Lists all available compilers and their supported targets, languages, protocols, and versions.
105
+
106
+ --action compile
107
+ Required options:
108
+ --target Target platform (e.g. nodejs, generic-cpp)
109
+ --outputType Output type (e.g. code, executable)
110
+ --outputPath Directory to write the result
111
+ --resourceName Resource name (used for .iec projects)
112
+ --sourcePath Path to source file (.st or .iec)
113
+ --language st (Structured Text) or ld (Ladder Diagram)
114
+
115
+ Examples:
116
+ node nodalis.js --action list-compilers
117
+
118
+ node nodalis.js --action compile \\
119
+ --target nodejs \\
120
+ --outputType code \\
121
+ --outputPath ./out \\
122
+ --resourceName MyPLC \\
123
+ --sourcePath ./examples/pump.iec \\
124
+ --language st
125
+ `);
126
+ process.exit(0);
127
+ }
128
+
129
+ const args = process.argv.slice(2);
130
+ const argMap = {};
131
+
132
+ for (let i = 0; i < args.length; i += 2) {
133
+ const key = args[i].replace(/^--/, '');
134
+ const value = args[i + 1];
135
+ argMap[key] = value;
136
+ }
137
+
138
+ const app = new Nodalis();
139
+
140
+ switch (argMap.action) {
141
+ case 'list-compilers': {
142
+ const list = app.listCompilers();
143
+ console.log(JSON.stringify(list, null, 2));
144
+ break;
145
+ }
146
+
147
+ case 'compile': {
148
+ app.compile({
149
+ target: argMap.target,
150
+ outputType: argMap.outputType,
151
+ outputPath: argMap.outputPath,
152
+ resourceName: argMap.resourceName,
153
+ sourcePath: argMap.sourcePath,
154
+ language: argMap.language,
155
+ }).then(() => {
156
+ console.log('Compilation completed.');
157
+ }).catch(err => {
158
+ console.error(`Compilation failed: ${err.message}`);
159
+ });
160
+ break;
161
+ }
162
+
163
+ case 'deploy': {
164
+ console.log(`Deploy action is not yet implemented.`);
165
+ break;
166
+ }
167
+
168
+ default: {
169
+ console.error(`Unknown or missing action: ${argMap.action}`);
170
+ console.error(`Valid actions: list-compilers, compile, deploy`);
171
+ break;
172
+ }
173
+ }
174
+ }