@tsplc/compiler 0.2.10 → 0.2.11
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/backends/codesys/emit.ts +24 -41
- package/backends/codesys/global-names.ts +49 -0
- package/backends/codesys/plan.ts +4 -89
- package/backends/codesys/task-access.ts +125 -0
- package/dist/cli.js +14 -3
- package/dist/cli.js.map +3 -3
- package/dist/codesys.js +360 -307
- package/dist/codesys.js.map +9 -8
- package/dist/index.js +23 -7
- package/dist/index.js.map +4 -4
- package/dist/ir.js +52 -2
- package/dist/ir.js.map +4 -4
- package/ir/declarations.ts +12 -5
- package/ir/model.ts +40 -1
- package/package.json +6 -6
- package/project/plant.ts +15 -2
- package/src/frontend/async.ts +9 -4
- package/src/frontend/typescript.ts +21 -7
- package/types/globals.d.ts +6 -0
- package/types/index.d.ts +37 -1
package/ir/declarations.ts
CHANGED
|
@@ -111,11 +111,18 @@ export class ModuleDeclarations {
|
|
|
111
111
|
block.locals.length ||
|
|
112
112
|
!matches(block.inputs, expected.inputs) ||
|
|
113
113
|
!matches(block.outputs, expected.outputs) ||
|
|
114
|
-
block.outputs.some((
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
block.outputs.some(({ initial }) => {
|
|
115
|
+
switch (initial?.kind) {
|
|
116
|
+
case "bool":
|
|
117
|
+
return initial.value !== false;
|
|
118
|
+
case "duration":
|
|
119
|
+
return initial.nanoseconds !== "0";
|
|
120
|
+
case "integer":
|
|
121
|
+
return initial.value !== "0";
|
|
122
|
+
default:
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
})
|
|
119
126
|
)
|
|
120
127
|
reject("Invalid canonical standard block definition.", block.span);
|
|
121
128
|
}
|
package/ir/model.ts
CHANGED
|
@@ -158,7 +158,7 @@ export interface PlcFunction {
|
|
|
158
158
|
export type MathHelper = "min" | "max" | "clamp";
|
|
159
159
|
|
|
160
160
|
// Native vendor blocks under their TypeScript names. Ports are [name, native name, type];
|
|
161
|
-
// every output starts
|
|
161
|
+
// every output starts at its type's default, matching the vendor library blocks.
|
|
162
162
|
const timer = (input: string, output: string) =>
|
|
163
163
|
({
|
|
164
164
|
inputs: [
|
|
@@ -184,6 +184,45 @@ export const standardBlocks = {
|
|
|
184
184
|
inputs: [["signal", "CLK", "BOOL"]],
|
|
185
185
|
outputs: [["fell", "Q", "BOOL"]],
|
|
186
186
|
},
|
|
187
|
+
UpCounter: {
|
|
188
|
+
native: "CTU",
|
|
189
|
+
inputs: [
|
|
190
|
+
["countUp", "CU", "BOOL"],
|
|
191
|
+
["reset", "RESET", "BOOL"],
|
|
192
|
+
["preset", "PV", "WORD"],
|
|
193
|
+
],
|
|
194
|
+
outputs: [
|
|
195
|
+
["reachedPreset", "Q", "BOOL"],
|
|
196
|
+
["value", "CV", "WORD"],
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
DownCounter: {
|
|
200
|
+
native: "CTD",
|
|
201
|
+
inputs: [
|
|
202
|
+
["countDown", "CD", "BOOL"],
|
|
203
|
+
["load", "LOAD", "BOOL"],
|
|
204
|
+
["preset", "PV", "WORD"],
|
|
205
|
+
],
|
|
206
|
+
outputs: [
|
|
207
|
+
["atZero", "Q", "BOOL"],
|
|
208
|
+
["value", "CV", "WORD"],
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
UpDownCounter: {
|
|
212
|
+
native: "CTUD",
|
|
213
|
+
inputs: [
|
|
214
|
+
["countUp", "CU", "BOOL"],
|
|
215
|
+
["countDown", "CD", "BOOL"],
|
|
216
|
+
["reset", "RESET", "BOOL"],
|
|
217
|
+
["load", "LOAD", "BOOL"],
|
|
218
|
+
["preset", "PV", "WORD"],
|
|
219
|
+
],
|
|
220
|
+
outputs: [
|
|
221
|
+
["reachedPreset", "QU", "BOOL"],
|
|
222
|
+
["atZero", "QD", "BOOL"],
|
|
223
|
+
["value", "CV", "WORD"],
|
|
224
|
+
],
|
|
225
|
+
},
|
|
187
226
|
Blinker: {
|
|
188
227
|
native: "Util.BLINK",
|
|
189
228
|
inputs: [
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"url": "git+https://github.com/livingforjesus/tsplc.git",
|
|
18
18
|
"directory": "packages/compiler"
|
|
19
19
|
},
|
|
20
|
-
"version": "0.2.
|
|
20
|
+
"version": "0.2.11",
|
|
21
21
|
"type": "module",
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
@@ -90,14 +90,14 @@
|
|
|
90
90
|
"bun": ">=1.4.3"
|
|
91
91
|
},
|
|
92
92
|
"dependencies": {
|
|
93
|
-
"@tsplc/agent": "0.2.
|
|
94
|
-
"@tsplc/build": "0.2.
|
|
95
|
-
"@tsplc/config": "0.2.
|
|
96
|
-
"@tsplc/typegen": "0.2.
|
|
93
|
+
"@tsplc/agent": "0.2.11",
|
|
94
|
+
"@tsplc/build": "0.2.11",
|
|
95
|
+
"@tsplc/config": "0.2.11",
|
|
96
|
+
"@tsplc/typegen": "0.2.11",
|
|
97
97
|
"typescript": "6.0.3",
|
|
98
98
|
"zod": "4.3.6",
|
|
99
99
|
"@types/bun": "1.4.2",
|
|
100
|
-
"@tsplc/server": "0.2.
|
|
100
|
+
"@tsplc/server": "0.2.11"
|
|
101
101
|
},
|
|
102
102
|
"bin": {
|
|
103
103
|
"tsplc": "./dist/cli.js"
|
package/project/plant.ts
CHANGED
|
@@ -107,6 +107,15 @@ const askCredentials = async (): Promise<Credentials> => ({
|
|
|
107
107
|
|
|
108
108
|
const progress = (message: string) => console.log(` ${message}`);
|
|
109
109
|
|
|
110
|
+
/** Prints progress with the minutes and seconds since it was made, so the slow steps show. */
|
|
111
|
+
const timedProgress = () => {
|
|
112
|
+
const start = Date.now();
|
|
113
|
+
return (message: string) => {
|
|
114
|
+
const seconds = Math.round((Date.now() - start) / 1000);
|
|
115
|
+
progress(`${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")} ${message}`);
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
110
119
|
const connect = () => {
|
|
111
120
|
const environment = z
|
|
112
121
|
.object({
|
|
@@ -239,8 +248,10 @@ const deploy = async (
|
|
|
239
248
|
if (status.login) throw new Error("A login is open; log out before deploying: tsplc logout");
|
|
240
249
|
const target = !status.target || flags.select ? await selectTarget(agent) : status.target;
|
|
241
250
|
console.log(`Building the release for ${target.host}…`);
|
|
251
|
+
const building = timedProgress();
|
|
242
252
|
const { release, diagnostics } = await prepareRelease(file, loaded, flags);
|
|
243
253
|
if (!release) return { diagnostics };
|
|
254
|
+
building("Built");
|
|
244
255
|
if (
|
|
245
256
|
flags.download &&
|
|
246
257
|
!(await confirm(
|
|
@@ -258,8 +269,10 @@ const deploy = async (
|
|
|
258
269
|
console.log(
|
|
259
270
|
release.manifest.server ? "Deploying the server and the program…" : "Deploying the program…",
|
|
260
271
|
);
|
|
272
|
+
const deploying = timedProgress();
|
|
273
|
+
deploying("Uploading");
|
|
261
274
|
try {
|
|
262
|
-
return await agent.deploy(release, settings,
|
|
275
|
+
return await agent.deploy(release, settings, deploying);
|
|
263
276
|
} catch (error) {
|
|
264
277
|
if (
|
|
265
278
|
!(error instanceof AgentOperationError && error.downloadRequired) ||
|
|
@@ -269,7 +282,7 @@ const deploy = async (
|
|
|
269
282
|
))
|
|
270
283
|
)
|
|
271
284
|
throw error;
|
|
272
|
-
return agent.deploy(release, { ...settings, download: true },
|
|
285
|
+
return agent.deploy(release, { ...settings, download: true }, timedProgress());
|
|
273
286
|
}
|
|
274
287
|
};
|
|
275
288
|
|
package/src/frontend/async.ts
CHANGED
|
@@ -932,17 +932,22 @@ protected execute(input:{}):void {}
|
|
|
932
932
|
const milliseconds =
|
|
933
933
|
Number(interval.arguments[0]!.text) *
|
|
934
934
|
(official(interval.expression, "seconds") ? 1000 : 1);
|
|
935
|
+
const taskPriority = Number(priority.text);
|
|
935
936
|
if (
|
|
936
937
|
!Number.isSafeInteger(milliseconds) ||
|
|
937
938
|
milliseconds <= 0 ||
|
|
938
939
|
milliseconds > 4294967295 ||
|
|
939
|
-
Number(
|
|
940
|
-
|
|
940
|
+
!Number.isInteger(taskPriority) ||
|
|
941
|
+
taskPriority < 1 ||
|
|
942
|
+
taskPriority > 31
|
|
941
943
|
)
|
|
942
|
-
fail(
|
|
944
|
+
fail(
|
|
945
|
+
init,
|
|
946
|
+
"Background interval must be positive and priority must be an integer in 1..31.",
|
|
947
|
+
);
|
|
943
948
|
const schedule = {
|
|
944
949
|
intervalNanoseconds: String(BigInt(milliseconds) * 1000000n),
|
|
945
|
-
priority:
|
|
950
|
+
priority: taskPriority,
|
|
946
951
|
mailbox: dec.name.text,
|
|
947
952
|
worker,
|
|
948
953
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { runtimeSources } from "./module-graph.ts";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
6
|
import {
|
|
7
7
|
instancedBlock,
|
|
@@ -202,6 +202,24 @@ export const typeCheckOnly: ts.CompilerOptions = {
|
|
|
202
202
|
tsBuildInfoFile: undefined,
|
|
203
203
|
};
|
|
204
204
|
|
|
205
|
+
const sdkFor = (project: string, options: ts.CompilerOptions): string => {
|
|
206
|
+
const name = "@tsplc/compiler/globals";
|
|
207
|
+
const resolveSdk = (from: string) => createRequire(from).resolve(name).replaceAll("\\", "/");
|
|
208
|
+
try {
|
|
209
|
+
const installed = resolveSdk(project);
|
|
210
|
+
const resolved = ts.resolveModuleName(name, project, options, ts.sys).resolvedModule;
|
|
211
|
+
// TypeScript may preserve a workspace symlink. A path alias may only stand in
|
|
212
|
+
// for the SDK if it resolves to the same installed declaration.
|
|
213
|
+
if (resolved && realpathSync(resolved.resolvedFileName) === realpathSync(installed))
|
|
214
|
+
return resolved.resolvedFileName;
|
|
215
|
+
return installed;
|
|
216
|
+
} catch (cause) {
|
|
217
|
+
if (!(cause instanceof Error) || !("code" in cause) || cause.code !== "MODULE_NOT_FOUND")
|
|
218
|
+
throw cause;
|
|
219
|
+
return resolveSdk(import.meta.url);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
|
|
205
223
|
export function analyze(options: FrontendOptions): FrontendResult {
|
|
206
224
|
const filename = resolve(options.tsconfig);
|
|
207
225
|
const config = ts.readConfigFile(filename, ts.sys.readFile);
|
|
@@ -214,11 +232,6 @@ export function analyze(options: FrontendOptions): FrontendResult {
|
|
|
214
232
|
filename,
|
|
215
233
|
);
|
|
216
234
|
if (parsed.errors.length) return { diagnostics: parsed.errors.map(tsDiagnostic) };
|
|
217
|
-
// TypeScript stores SourceFile paths with forward slashes on every host.
|
|
218
|
-
// Normalize require.resolve once so canonical SDK identity also works on Windows.
|
|
219
|
-
const sdk = createRequire(import.meta.url)
|
|
220
|
-
.resolve("@tsplc/compiler/globals")
|
|
221
|
-
.replaceAll("\\", "/");
|
|
222
235
|
const programFiles = Object.values(options.programs).map((entry) =>
|
|
223
236
|
resolve(dirname(filename), entry.module),
|
|
224
237
|
);
|
|
@@ -229,7 +242,6 @@ export function analyze(options: FrontendOptions): FrontendResult {
|
|
|
229
242
|
(file) => /\.d\.[cm]?ts$/.test(file) && file.startsWith(`${dirname(filename)}/`),
|
|
230
243
|
);
|
|
231
244
|
const projectFiles = scope === "project" ? parsed.fileNames : ambient;
|
|
232
|
-
const rootNames = [...new Set([...projectFiles, ...programFiles, sdk])];
|
|
233
245
|
const compilerOptions: ts.CompilerOptions = {
|
|
234
246
|
...parsed.options,
|
|
235
247
|
...typeCheckOnly,
|
|
@@ -251,6 +263,8 @@ export function analyze(options: FrontendOptions): FrontendResult {
|
|
|
251
263
|
// Nothing is emitted, and a service client's router type imports modules outside these (TS6059).
|
|
252
264
|
delete compilerOptions.rootDir;
|
|
253
265
|
delete compilerOptions.outDir;
|
|
266
|
+
const sdk = sdkFor(filename, compilerOptions);
|
|
267
|
+
const rootNames = [...new Set([...projectFiles, ...programFiles, sdk])];
|
|
254
268
|
let program = ts.createProgram(rootNames, compilerOptions);
|
|
255
269
|
const plcFiles = (program: ts.Program) => {
|
|
256
270
|
const files = new Set(runtimeSources(program));
|
package/types/globals.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ declare global {
|
|
|
24
24
|
const RisingEdge: typeof import("./index").RisingEdge;
|
|
25
25
|
type FallingEdge = import("./index").FallingEdge;
|
|
26
26
|
const FallingEdge: typeof import("./index").FallingEdge;
|
|
27
|
+
type UpCounter = import("./index").UpCounter;
|
|
28
|
+
const UpCounter: typeof import("./index").UpCounter;
|
|
29
|
+
type DownCounter = import("./index").DownCounter;
|
|
30
|
+
const DownCounter: typeof import("./index").DownCounter;
|
|
31
|
+
type UpDownCounter = import("./index").UpDownCounter;
|
|
32
|
+
const UpDownCounter: typeof import("./index").UpDownCounter;
|
|
27
33
|
const ref: typeof import("./index").ref;
|
|
28
34
|
const retain: typeof import("./index").retain;
|
|
29
35
|
const ms: typeof import("./index").ms;
|
package/types/index.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export declare abstract class FunctionBlock<I, O = Record<never, never>> {
|
|
|
68
68
|
protected abstract execute(input: I): keyof O extends never ? void : O;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/** Starts on, then alternates `onTime` and `offTime`. Keeps its last `on` when disabled. CODESYS Util.BLINK. */
|
|
72
72
|
export declare class Blinker extends FunctionBlock<
|
|
73
73
|
{ enabled: BOOL; offTime: TIME; onTime: TIME },
|
|
74
74
|
{ on: BOOL }
|
|
@@ -109,3 +109,39 @@ export declare class RisingEdge extends FunctionBlock<{ signal: BOOL }, { rose:
|
|
|
109
109
|
export declare class FallingEdge extends FunctionBlock<{ signal: BOOL }, { fell: BOOL }> {
|
|
110
110
|
protected execute(input: { signal: BOOL }): { fell: BOOL };
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
/** Counts rising `countUp` edges. `reset` clears `value`. `reachedPreset` indicates `value >= preset`. CODESYS CTU. */
|
|
114
|
+
export declare class UpCounter extends FunctionBlock<
|
|
115
|
+
{ countUp: BOOL; reset: BOOL; preset: WORD },
|
|
116
|
+
{ reachedPreset: BOOL; value: WORD }
|
|
117
|
+
> {
|
|
118
|
+
protected execute(input: { countUp: BOOL; reset: BOOL; preset: WORD }): {
|
|
119
|
+
reachedPreset: BOOL;
|
|
120
|
+
value: WORD;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Counts down on rising `countDown` edges. `load` sets `value` to `preset`. `atZero` indicates `value = 0`. CODESYS CTD. */
|
|
125
|
+
export declare class DownCounter extends FunctionBlock<
|
|
126
|
+
{ countDown: BOOL; load: BOOL; preset: WORD },
|
|
127
|
+
{ atZero: BOOL; value: WORD }
|
|
128
|
+
> {
|
|
129
|
+
protected execute(input: { countDown: BOOL; load: BOOL; preset: WORD }): {
|
|
130
|
+
atZero: BOOL;
|
|
131
|
+
value: WORD;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Counts rising edges in either direction. `reset` clears `value`, and `load` sets `value` to `preset`. CODESYS CTUD. */
|
|
136
|
+
export declare class UpDownCounter extends FunctionBlock<
|
|
137
|
+
{ countUp: BOOL; countDown: BOOL; reset: BOOL; load: BOOL; preset: WORD },
|
|
138
|
+
{ reachedPreset: BOOL; atZero: BOOL; value: WORD }
|
|
139
|
+
> {
|
|
140
|
+
protected execute(input: {
|
|
141
|
+
countUp: BOOL;
|
|
142
|
+
countDown: BOOL;
|
|
143
|
+
reset: BOOL;
|
|
144
|
+
load: BOOL;
|
|
145
|
+
preset: WORD;
|
|
146
|
+
}): { reachedPreset: BOOL; atZero: BOOL; value: WORD };
|
|
147
|
+
}
|