@tsplc/compiler 0.0.0-stage → 0.2.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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -2
  3. package/backends/codesys/config.ts +54 -0
  4. package/backends/codesys/emit.ts +1063 -0
  5. package/backends/codesys/engineering.ts +88 -0
  6. package/backends/codesys/http.ts +598 -0
  7. package/backends/codesys/index.ts +12 -0
  8. package/backends/codesys/io-paths.ts +103 -0
  9. package/backends/codesys/names.ts +76 -0
  10. package/backends/codesys/numeric.ts +356 -0
  11. package/backends/codesys/plan.ts +512 -0
  12. package/backends/codesys/publication.ts +59 -0
  13. package/backends/codesys/storage.ts +63 -0
  14. package/backends/codesys/task-access.ts +118 -0
  15. package/backends/codesys/types.ts +178 -0
  16. package/backends/codesys/vendor.ts +108 -0
  17. package/dist/cli.js +1396 -0
  18. package/dist/cli.js.map +16 -0
  19. package/dist/clock.js +8 -0
  20. package/dist/clock.js.map +10 -0
  21. package/dist/codesys.js +2587 -0
  22. package/dist/codesys.js.map +22 -0
  23. package/dist/elaborate-capture.js +118 -0
  24. package/dist/elaborate-capture.js.map +10 -0
  25. package/dist/index.js +6859 -0
  26. package/dist/index.js.map +25 -0
  27. package/dist/ir.js +1888 -0
  28. package/dist/ir.js.map +22 -0
  29. package/dist/math.js +24 -0
  30. package/dist/math.js.map +10 -0
  31. package/dist/project.js +429 -0
  32. package/dist/project.js.map +11 -0
  33. package/ir/aggregates.ts +333 -0
  34. package/ir/allocation.ts +74 -0
  35. package/ir/core-model.ts +116 -0
  36. package/ir/core.ts +63 -0
  37. package/ir/declarations.ts +201 -0
  38. package/ir/diagnostics.ts +43 -0
  39. package/ir/domains.ts +96 -0
  40. package/ir/index.ts +7 -0
  41. package/ir/link.ts +176 -0
  42. package/ir/model.ts +268 -0
  43. package/ir/normalization-error.ts +10 -0
  44. package/ir/routine.ts +826 -0
  45. package/ir/scalars.ts +675 -0
  46. package/ir/types.ts +70 -0
  47. package/package.json +106 -4
  48. package/project/artifacts.ts +277 -0
  49. package/project/cli.ts +305 -0
  50. package/project/index.ts +1 -0
  51. package/project/plant.ts +437 -0
  52. package/project/project.ts +312 -0
  53. package/project/server.ts +83 -0
  54. package/src/frontend/async.ts +1060 -0
  55. package/src/frontend/blocks.ts +419 -0
  56. package/src/frontend/configuration.ts +167 -0
  57. package/src/frontend/const-functions.ts +153 -0
  58. package/src/frontend/cooperative.ts +75 -0
  59. package/src/frontend/elaborate/capture.ts +138 -0
  60. package/src/frontend/elaborate/graph.ts +94 -0
  61. package/src/frontend/elaborate/index.ts +302 -0
  62. package/src/frontend/elaborate/layouts.ts +390 -0
  63. package/src/frontend/elaborate/model.ts +177 -0
  64. package/src/frontend/elaborate/specialize.ts +2280 -0
  65. package/src/frontend/exposure.ts +147 -0
  66. package/src/frontend/module-graph.ts +63 -0
  67. package/src/frontend/promises.ts +751 -0
  68. package/src/frontend/services.ts +435 -0
  69. package/src/frontend/types.ts +578 -0
  70. package/src/frontend/typescript.ts +2442 -0
  71. package/src/index.ts +7 -0
  72. package/stdlib/clock.ts +2 -0
  73. package/stdlib/math.ts +19 -0
  74. package/types/async.d.ts +81 -0
  75. package/types/clock.d.ts +4 -0
  76. package/types/config.d.ts +2 -0
  77. package/types/data-types.d.ts +31 -0
  78. package/types/globals.d.ts +40 -0
  79. package/types/index.d.ts +111 -0
  80. package/types/plc.d.ts +5 -0
package/project/cli.ts ADDED
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env bun
2
+ import { buildServer } from "./server.ts";
3
+ import { parseArgs } from "node:util";
4
+ import { resolve } from "node:path";
5
+ import {
6
+ buildTargetFor,
7
+ controllerFor,
8
+ loadTsplcConfig,
9
+ rawBuildConfig,
10
+ simulationConfig,
11
+ } from "@tsplc/config";
12
+ import {
13
+ buildProject as buildSt,
14
+ configureDocker,
15
+ configureRuntime,
16
+ inspectController,
17
+ installLibraries,
18
+ loadSources,
19
+ resolveConfig,
20
+ runProject,
21
+ updateController,
22
+ } from "@tsplc/build";
23
+ import { checkHostConfig } from "@tsplc/compiler";
24
+ import { type Diagnostic, formatDiagnostic, hasErrors } from "@tsplc/compiler/ir";
25
+ import { deviceTree } from "@tsplc/typegen";
26
+ import { loadHardware, resolveIo } from "@tsplc/typegen/hardware";
27
+ import { generateProject } from "@tsplc/typegen";
28
+ import { plantCommands, runPlantCommand } from "./plant.ts";
29
+ import { buildProject, checkProject, cleanProject, projectTargets } from "./project.ts";
30
+
31
+ async function main() {
32
+ const { values, positionals } = parseArgs({
33
+ allowPositionals: true,
34
+ options: {
35
+ config: { type: "string" },
36
+ help: { type: "boolean", short: "h" },
37
+ "emit-only": { type: "boolean" },
38
+ format: { type: "string" },
39
+ read: { type: "string", multiple: true },
40
+ "initialize-user": { type: "boolean" },
41
+ "frozen-lockfile": { type: "boolean" },
42
+ check: { type: "boolean" },
43
+ plc: { type: "boolean" },
44
+ server: { type: "boolean" },
45
+ simulate: { type: "boolean" },
46
+ runtime: { type: "boolean" },
47
+ download: { type: "boolean" },
48
+ restore: { type: "boolean" },
49
+ select: { type: "boolean" },
50
+ yes: { type: "boolean", short: "y" },
51
+ },
52
+ });
53
+ const command = positionals[0],
54
+ file = values.config ?? "tsplc.config.ts";
55
+ if (!command || values.help) {
56
+ console.log(
57
+ `tsplc install|typegen|check|build|run|download|online-change|inspect|clean|configure [--config tsplc.config.ts] [--plc|--server] [--simulate] [--runtime] [--emit-only] [--format json] [--read symbol]
58
+
59
+ Plant agent (TSPLC_AGENT_URL, TSPLC_AGENT_TOKEN):
60
+ tsplc deploy [--download] [--select] [--initialize-user]
61
+ tsplc target | status | login | logout
62
+ tsplc vars [path] | read <expression>... | write <expression> <value>
63
+ tsplc force <expression> <value> | unforce [expression]... [--restore]
64
+ tsplc start | stop | reset warm|cold (-y skips confirmations)`,
65
+ );
66
+ return 0;
67
+ }
68
+ if (plantCommands.has(command)) {
69
+ const failed = await runPlantCommand(command, positionals.slice(1), values, {
70
+ file,
71
+ load: () => loadTsplcConfig(file),
72
+ });
73
+ return failed ? report(failed, values.format) : 0;
74
+ }
75
+ if (positionals.length > 1) throw new Error("Specify one command.");
76
+ if (
77
+ ![
78
+ "install",
79
+ "typegen",
80
+ "check",
81
+ "build",
82
+ "run",
83
+ "download",
84
+ "online-change",
85
+ "inspect",
86
+ "clean",
87
+ "configure",
88
+ ].includes(command)
89
+ )
90
+ throw new Error(`Unknown command ${command}.`);
91
+ if (values.format && !["json", "text"].includes(values.format))
92
+ throw new Error("--format must be json or text.");
93
+ if (values["emit-only"] && command !== "build")
94
+ throw new Error("--emit-only is supported by build only.");
95
+ if (values.runtime && command !== "configure")
96
+ throw new Error("--runtime applies only to configure.");
97
+ if ((values.plc || values.server) && command !== "build")
98
+ throw new Error("--plc and --server apply only to build.");
99
+ if (values.simulate && !["run", "download", "online-change"].includes(command))
100
+ throw new Error("--simulate applies only to run, download and online-change.");
101
+ if (values.plc && values.server)
102
+ throw new Error("Choose --plc or --server, or omit both for a full build.");
103
+ if (values.server && values["emit-only"])
104
+ throw new Error("--emit-only applies to PLC emission only.");
105
+ const log = console.log,
106
+ previousLogTarget = process.env["TSPLC_LOG_STDERR"];
107
+ if (values.format === "json") {
108
+ console.log = console.error;
109
+ process.env["TSPLC_LOG_STDERR"] = "1";
110
+ }
111
+ let result: unknown;
112
+ try {
113
+ if (command === "configure") {
114
+ await (values.runtime ? configureRuntime() : configureDocker());
115
+ result = { ok: true };
116
+ } else if (command === "clean") {
117
+ result = await cleanProject(file);
118
+ } else {
119
+ if (["check", "build", "run", "download", "online-change"].includes(command)) {
120
+ const diagnostics = checkHostConfig(resolve(file));
121
+ if (hasErrors(diagnostics)) throw new DiagnosticFailure({ diagnostics });
122
+ }
123
+ const loaded = await loadTsplcConfig(file);
124
+ const { config, configFile, root } = loaded;
125
+ // Generated types keep describing the hardware project; only the download omits fieldbus I/O.
126
+ const target = values.simulate ? { ...loaded, config: simulationConfig(config) } : loaded;
127
+ if (command === "install")
128
+ result = {
129
+ libraries: await installLibraries(config.dependencies, root, {
130
+ frozen: values["frozen-lockfile"],
131
+ }),
132
+ };
133
+ else if (command === "typegen") {
134
+ const targets = await projectTargets(file, { loaded });
135
+ if (config.compiler && !values.check) {
136
+ await generateProject(file, { hardwareOnly: true, loaded, targets });
137
+ const emitted = await buildProject(file, { emitOnly: true, loaded });
138
+ if (hasErrors(emitted.diagnostics)) throw new DiagnosticFailure(emitted);
139
+ }
140
+ result = await generateProject(file, { check: values.check, loaded, targets });
141
+ } else if (command === "check") {
142
+ result = config.compiler
143
+ ? await checkProject(file, { loaded })
144
+ : {
145
+ objects: (
146
+ await loadSources(
147
+ resolveConfig(
148
+ {
149
+ ...rawBuildConfig(config),
150
+ io: resolveIo(config, await loadHardware(config, root)),
151
+ },
152
+ configFile,
153
+ ),
154
+ )
155
+ ).objects.length,
156
+ };
157
+ } else if (command === "build" && values.server) {
158
+ result = await buildServer(config, root);
159
+ } else if (command === "build" || command === "run") {
160
+ if (config.compiler) {
161
+ const built = await buildProject(file, {
162
+ emitOnly: values["emit-only"],
163
+ frozenLockfile: values["frozen-lockfile"],
164
+ loaded: target,
165
+ controller: controllerFor(config, command),
166
+ buildTarget: buildTargetFor(command),
167
+ });
168
+ if (hasErrors(built.diagnostics)) throw new DiagnosticFailure(built);
169
+ await generateProject(file, {
170
+ loaded,
171
+ targets: await projectTargets(file, { loaded }),
172
+ });
173
+ result = built;
174
+ } else {
175
+ if (values["emit-only"]) throw new Error("Raw Structured Text is already emitted.");
176
+ if (!config.build) throw new Error("Configure build settings.");
177
+ const build = rawBuildConfig(config);
178
+ const libraries = await installLibraries(config.dependencies, root, {
179
+ frozen: values["frozen-lockfile"],
180
+ });
181
+ const hardware = await loadHardware(target.config, root);
182
+ result = await buildSt(
183
+ resolveConfig(
184
+ {
185
+ ...build,
186
+ libraries,
187
+ io: resolveIo(target.config, hardware),
188
+ deviceTree: await deviceTree(target.config, hardware),
189
+ controller: controllerFor(config, command),
190
+ },
191
+ configFile,
192
+ ),
193
+ );
194
+ await generateProject(file, { hardwareOnly: !config.typegen, loaded });
195
+ }
196
+ if (command === "build" && !values.plc && !values["emit-only"] && config.server?.runtime) {
197
+ const backend = await buildServer(config, root);
198
+ result = { ...(typeof result === "object" && result !== null ? result : {}), ...backend };
199
+ }
200
+ if (command === "run") {
201
+ if (!config.build) throw new Error("Configure build to run this project.");
202
+ await runProject(
203
+ resolveConfig(config.compiler ? config.build : rawBuildConfig(config), configFile),
204
+ config.name,
205
+ );
206
+ }
207
+ } else if (command === "inspect") {
208
+ if (!config.build) throw new Error("Configure build settings and controller connection.");
209
+ // Inspection needs only the connection/baseline. It must remain usable
210
+ // when local sources, descriptors or dependency caches are unavailable.
211
+ result = await inspectController(
212
+ resolveConfig(
213
+ { ...config.build, controller: controllerFor(config, command) },
214
+ configFile,
215
+ ),
216
+ values.read,
217
+ );
218
+ } else if (["download", "online-change"].includes(command)) {
219
+ if (!config.build) throw new Error("Configure build settings and controller connection.");
220
+ let settings: ReturnType<typeof resolveConfig>;
221
+ if (config.compiler) {
222
+ const emitted = await buildProject(file, {
223
+ emitOnly: true,
224
+ forController: true,
225
+ frozenLockfile: values["frozen-lockfile"],
226
+ loaded: target,
227
+ buildTarget: buildTargetFor(command),
228
+ });
229
+ if (hasErrors(emitted.diagnostics)) throw new DiagnosticFailure(emitted);
230
+ if (!emitted.controllerConfig)
231
+ throw new Error("Configure build settings for deployment.");
232
+ // Emission already resolves the generated entry/task paths. Passing
233
+ // this through the input schema again would reject its configFile.
234
+ settings = { ...emitted.controllerConfig, io: {} };
235
+ } else
236
+ settings = {
237
+ ...resolveConfig(
238
+ {
239
+ ...rawBuildConfig(config),
240
+ io: resolveIo(target.config, await loadHardware(target.config, root)),
241
+ },
242
+ configFile,
243
+ ),
244
+ libraries: await installLibraries(config.dependencies, root, {
245
+ frozen: values["frozen-lockfile"],
246
+ }),
247
+ };
248
+ result = await updateController(
249
+ { ...settings, controller: controllerFor(config, command) },
250
+ command as "download" | "online-change",
251
+ {
252
+ initializeUser: values["initialize-user"],
253
+ read: values.read,
254
+ },
255
+ );
256
+ } else throw new Error(`Unknown command ${command}.`);
257
+ }
258
+ } finally {
259
+ console.log = log;
260
+ if (previousLogTarget === undefined) delete process.env["TSPLC_LOG_STDERR"];
261
+ else process.env["TSPLC_LOG_STDERR"] = previousLogTarget;
262
+ }
263
+ return report(result, values.format);
264
+ }
265
+ class DiagnosticFailure extends Error {
266
+ constructor(readonly result: unknown) {
267
+ super("PLC validation failed.");
268
+ }
269
+ }
270
+ function report(result: unknown, format?: string) {
271
+ const diagnostics =
272
+ result && typeof result === "object" && "diagnostics" in result
273
+ ? (result.diagnostics as Diagnostic[])
274
+ : [];
275
+ const ok = !hasErrors(diagnostics);
276
+ if (format === "json")
277
+ console.log(
278
+ JSON.stringify({ ok, ...(result && typeof result === "object" ? result : { result }) }),
279
+ );
280
+ else {
281
+ for (const diagnostic of diagnostics) console.error(formatDiagnostic(diagnostic));
282
+ if (ok) console.log("tsPLC completed.");
283
+ }
284
+ return ok ? 0 : 1;
285
+ }
286
+ try {
287
+ process.exitCode = await main();
288
+ } catch (cause) {
289
+ // main() may fail before its options parse, so detect JSON output from raw argv.
290
+ const json = process.argv.includes("json") || process.argv.includes("--format=json");
291
+ if (cause instanceof DiagnosticFailure) {
292
+ process.exitCode = report(cause.result, json ? "json" : undefined);
293
+ } else {
294
+ const message = cause instanceof Error ? cause.message : String(cause);
295
+ if (json)
296
+ console.log(
297
+ JSON.stringify({
298
+ ok: false,
299
+ diagnostics: [{ origin: "config", code: "PLC5003", severity: "error", message }],
300
+ }),
301
+ );
302
+ else console.error(message);
303
+ process.exitCode = 1;
304
+ }
305
+ }
@@ -0,0 +1 @@
1
+ export { checkProject, buildProject, cleanProject, projectTargets } from "./project.ts";