@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
@@ -0,0 +1,147 @@
1
+ import { runtimeSources } from "./module-graph.ts";
2
+ import ts from "typescript";
3
+ import { createHash } from "node:crypto";
4
+ import { dirname } from "node:path";
5
+ import { error, type Diagnostic } from "@tsplc/compiler/ir";
6
+
7
+ /** Telemetry is sampled by its authored task and published read-only by the backend. */
8
+ export const lowerExposure = (program: ts.Program, sdk: string) => {
9
+ const checker = program.getTypeChecker();
10
+ const sources = new Map<string, string>();
11
+ const diagnostics: Diagnostic[] = [];
12
+ const variables = new Map<string, { name: string; version: string }>();
13
+ const names = new Set<string>();
14
+ const symbol = (node: ts.Node) => {
15
+ let value = checker.getSymbolAtLocation(node);
16
+ while (value && value.flags & ts.SymbolFlags.Alias) value = checker.getAliasedSymbol(value);
17
+ return value;
18
+ };
19
+ for (const source of runtimeSources(program)) {
20
+ if (source.isDeclarationFile) continue;
21
+ const edits: { start: number; end: number; text: string }[] = [];
22
+ const extra: string[] = [];
23
+ const visit = (node: ts.Node): void => {
24
+ if (
25
+ !ts.isCallExpression(node) ||
26
+ symbol(node.expression)?.name !== "expose" ||
27
+ !symbol(node.expression)?.declarations?.some((value) =>
28
+ value.getSourceFile().fileName.startsWith(dirname(sdk) + "/"),
29
+ )
30
+ ) {
31
+ ts.forEachChild(node, visit);
32
+ return;
33
+ }
34
+ const fail = (message: string) => {
35
+ const start = node.getStart(source),
36
+ position = source.getLineAndCharacterOfPosition(start);
37
+ diagnostics.push(
38
+ error("PLC1014", message, {
39
+ file: source.fileName,
40
+ start,
41
+ end: node.end,
42
+ line: position.line + 1,
43
+ column: position.character + 1,
44
+ }),
45
+ );
46
+ };
47
+ const [label, values] = node.arguments;
48
+ if (
49
+ !label ||
50
+ !ts.isStringLiteral(label) ||
51
+ !/^[A-Za-z_][A-Za-z0-9_]*$/.test(label.text) ||
52
+ label.text === "__proto__" ||
53
+ !values ||
54
+ !ts.isObjectLiteralExpression(values) ||
55
+ !ts.isExpressionStatement(node.parent)
56
+ ) {
57
+ fail(
58
+ "expose requires a literal machine name and a flat telemetry object in a synchronous cyclic routine.",
59
+ );
60
+ return;
61
+ }
62
+ let owner: ts.Node | undefined = node.parent;
63
+ while (owner && !ts.isFunctionLike(owner)) owner = owner.parent;
64
+ if (
65
+ !owner ||
66
+ (ts.canHaveModifiers(owner) &&
67
+ ts.getModifiers(owner)?.some((value) => value.kind === ts.SyntaxKind.AsyncKeyword))
68
+ ) {
69
+ fail("Sample telemetry from a synchronous cyclic routine, outside async continuations.");
70
+ return;
71
+ }
72
+ if (names.has(label.text)) {
73
+ fail(`Duplicate machine exposure ${label.text}.`);
74
+ return;
75
+ }
76
+ names.add(label.text);
77
+ const fields: { name: string; type: string; value: string }[] = [];
78
+ for (const property of values.properties) {
79
+ if (
80
+ !ts.isPropertyAssignment(property) ||
81
+ !ts.isIdentifier(property.name) ||
82
+ ["contractVersion", "__proto__"].includes(property.name.text)
83
+ ) {
84
+ fail("Telemetry uses named scalar fields; contractVersion and __proto__ are reserved.");
85
+ return;
86
+ }
87
+ const value = property.initializer;
88
+ const type = checker.getTypeAtLocation(value);
89
+ let annotation: string | undefined;
90
+ if (type.flags & ts.TypeFlags.BooleanLike) annotation = "BOOL";
91
+ const declaration = symbol(
92
+ ts.isPropertyAccessExpression(value) ? value.name : value,
93
+ )?.valueDeclaration;
94
+ if (
95
+ declaration &&
96
+ (ts.isPropertySignature(declaration) ||
97
+ ts.isPropertyDeclaration(declaration) ||
98
+ ts.isVariableDeclaration(declaration) ||
99
+ ts.isParameter(declaration)) &&
100
+ declaration.type
101
+ ) {
102
+ const text = declaration.type.getText(declaration.getSourceFile());
103
+ if (/^(BOOL|SINT|USINT|BYTE|INT|UINT|WORD|DINT|UDINT|DWORD|STRING<\d+>)$/.test(text))
104
+ annotation = text;
105
+ }
106
+ if (!annotation) {
107
+ fail(
108
+ `Telemetry ${property.name.text} needs a concrete BOOL, integer up to 32 bits or bounded STRING annotation.`,
109
+ );
110
+ return;
111
+ }
112
+ fields.push({ name: property.name.text, type: annotation, value: value.getText(source) });
113
+ }
114
+ if (!fields.length) {
115
+ fail("Expose at least one telemetry field.");
116
+ return;
117
+ }
118
+ const name = `tsplcExposed_${label.text}`;
119
+ const record = `TsplcTelemetry_${label.text}`;
120
+ const version = createHash("sha256")
121
+ .update(
122
+ JSON.stringify({
123
+ abi: 1,
124
+ name: label.text,
125
+ fields: fields.map(({ name, type }) => ({ name, type })),
126
+ }),
127
+ )
128
+ .digest("hex");
129
+ extra.push(
130
+ `export interface ${record}{contractVersion:STRING<64>;${fields.map((field) => `${field.name}:${field.type}`).join(";")}}\nexport let ${name}:${record}=defaultValue<${record}>();`,
131
+ );
132
+ variables.set(`${source.fileName}:${name}`, { name: label.text, version });
133
+ edits.push({
134
+ start: node.getStart(source),
135
+ end: node.end,
136
+ text: `{${name}.contractVersion=${JSON.stringify(version)};${fields.map((field) => `${name}.${field.name}=${field.value};`).join("")}}`,
137
+ });
138
+ };
139
+ visit(source);
140
+ if (!edits.length) continue;
141
+ let text = source.text;
142
+ for (const edit of edits.sort((a, b) => b.start - a.start))
143
+ text = text.slice(0, edit.start) + edit.text + text.slice(edit.end);
144
+ sources.set(source.fileName, text + "\n" + extra.join("\n"));
145
+ }
146
+ return { sources, diagnostics, variables };
147
+ };
@@ -0,0 +1,63 @@
1
+ import ts from "typescript";
2
+
3
+ /** Runtime imports define execution; type-only imports provide layouts without allocating state. */
4
+ export const runtimeSources = (program: ts.Program): readonly ts.SourceFile[] => {
5
+ const checker = program.getTypeChecker();
6
+ const visited = new Set<ts.SourceFile>();
7
+ // A block array allocates through its type, so its block's module runs even when type-imported.
8
+ const visitBlockArrays = (node: ts.Node): void => {
9
+ if (
10
+ ts.isTypeReferenceNode(node) &&
11
+ node.typeName.getText() === "FixedArray" &&
12
+ node.typeArguments?.[0] &&
13
+ ts.isTypeReferenceNode(node.typeArguments[0])
14
+ ) {
15
+ let symbol = checker.getSymbolAtLocation(node.typeArguments[0].typeName);
16
+ while (symbol && symbol.flags & ts.SymbolFlags.Alias)
17
+ symbol = checker.getAliasedSymbol(symbol);
18
+ const block = symbol?.declarations?.find(ts.isClassDeclaration);
19
+ if (block) visit(block.getSourceFile());
20
+ }
21
+ ts.forEachChild(node, visitBlockArrays);
22
+ };
23
+ const visit = (source: ts.SourceFile): void => {
24
+ if (visited.has(source)) return;
25
+ visited.add(source);
26
+ if (source.isDeclarationFile) return;
27
+ visitBlockArrays(source);
28
+ for (const statement of source.statements) {
29
+ if (ts.isImportDeclaration(statement)) {
30
+ if (statement.importClause?.isTypeOnly) continue;
31
+ const bindings = statement.importClause?.namedBindings;
32
+ if (
33
+ !statement.importClause?.name &&
34
+ bindings &&
35
+ ts.isNamedImports(bindings) &&
36
+ bindings.elements.every((value) => value.isTypeOnly)
37
+ )
38
+ continue;
39
+ const target = checker
40
+ .getSymbolAtLocation(statement.moduleSpecifier)
41
+ ?.declarations?.find(ts.isSourceFile);
42
+ if (target) visit(target);
43
+ }
44
+ if (ts.isExportDeclaration(statement) && statement.moduleSpecifier && !statement.isTypeOnly) {
45
+ if (
46
+ statement.exportClause &&
47
+ ts.isNamedExports(statement.exportClause) &&
48
+ statement.exportClause.elements.every((value) => value.isTypeOnly)
49
+ )
50
+ continue;
51
+ const target = checker
52
+ .getSymbolAtLocation(statement.moduleSpecifier)
53
+ ?.declarations?.find(ts.isSourceFile);
54
+ if (target) visit(target);
55
+ }
56
+ }
57
+ };
58
+ for (const file of program.getRootFileNames()) {
59
+ const source = program.getSourceFile(file);
60
+ if (source) visit(source);
61
+ }
62
+ return program.getSourceFiles().filter((source) => visited.has(source));
63
+ };