@tsplc/compiler 0.2.9 → 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 +50 -48
- package/dist/cli.js.map +5 -5
- 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 +22 -9
- package/project/server.ts +43 -13
- 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/dist/codesys.js
CHANGED
|
@@ -144,7 +144,56 @@ function direct(address) {
|
|
|
144
144
|
return { kind: "direct", address };
|
|
145
145
|
}
|
|
146
146
|
// packages/compiler/backends/codesys/emit.ts
|
|
147
|
-
import { defaultValue, instancedBlock as instancedBlock5, standardBlocks } from "@tsplc/compiler/ir";
|
|
147
|
+
import { defaultValue, instancedBlock as instancedBlock5, standardBlocks as standardBlocks2 } from "@tsplc/compiler/ir";
|
|
148
|
+
|
|
149
|
+
// packages/compiler/backends/codesys/global-names.ts
|
|
150
|
+
import { standardBlocks } from "@tsplc/compiler/ir";
|
|
151
|
+
|
|
152
|
+
// packages/compiler/backends/codesys/storage.ts
|
|
153
|
+
import { basename } from "node:path";
|
|
154
|
+
import { instancedBlock } from "@tsplc/compiler/ir";
|
|
155
|
+
var moduleName = (variable) => basename(variable.span.file).replace(/\.[^.]+$/, "");
|
|
156
|
+
function storageLayout(core) {
|
|
157
|
+
const types = new Map(core.types.map((type) => [type.id, type]));
|
|
158
|
+
const blocks = new Map(core.blocks.map((block) => [block.id, block]));
|
|
159
|
+
const retention = new Map;
|
|
160
|
+
const blockRetained = (id) => {
|
|
161
|
+
const cached = retention.get(id);
|
|
162
|
+
if (cached !== undefined)
|
|
163
|
+
return cached;
|
|
164
|
+
const block = blocks.get(id);
|
|
165
|
+
if (!block)
|
|
166
|
+
throw new Error("Missing verified block during retention planning.");
|
|
167
|
+
const retained = block.state.some((field) => {
|
|
168
|
+
const type = instancedBlock(field.type, types);
|
|
169
|
+
return field.retain || type !== undefined && blockRetained(type.block);
|
|
170
|
+
});
|
|
171
|
+
retention.set(id, retained);
|
|
172
|
+
return retained;
|
|
173
|
+
};
|
|
174
|
+
const retainedBlocks = new Set(core.blocks.filter((block) => blockRetained(block.id)).map((block) => block.id));
|
|
175
|
+
const retainedVariables = new Set([...core.variables, ...core.blocks.flatMap((block) => block.state)].filter((variable) => {
|
|
176
|
+
const type = instancedBlock(variable.type, types);
|
|
177
|
+
return variable.retain || type !== undefined && retainedBlocks.has(type.block);
|
|
178
|
+
}).map((variable) => variable.id));
|
|
179
|
+
const groups = new Map;
|
|
180
|
+
for (const variable of core.variables) {
|
|
181
|
+
if (variable.storage === "input" || variable.storage === "output")
|
|
182
|
+
continue;
|
|
183
|
+
const storage = variable.storage === "constant" ? "constants" : retainedVariables.has(variable.id) ? "retained" : "state";
|
|
184
|
+
const id = `${variable.span.file}:${storage}`;
|
|
185
|
+
let group = groups.get(id);
|
|
186
|
+
if (!group) {
|
|
187
|
+
group = { id, module: moduleName(variable), storage, variables: [] };
|
|
188
|
+
groups.set(id, group);
|
|
189
|
+
}
|
|
190
|
+
group.variables.push(variable);
|
|
191
|
+
}
|
|
192
|
+
return { retainedBlocks, retainedVariables, globalLists: [...groups.values()] };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// packages/compiler/backends/codesys/types.ts
|
|
196
|
+
import { instancedBlock as instancedBlock2 } from "@tsplc/compiler/ir";
|
|
148
197
|
|
|
149
198
|
// packages/compiler/backends/codesys/numeric.ts
|
|
150
199
|
import {
|
|
@@ -424,60 +473,212 @@ END_IF;
|
|
|
424
473
|
}
|
|
425
474
|
}
|
|
426
475
|
|
|
476
|
+
// packages/compiler/backends/codesys/types.ts
|
|
477
|
+
class CodesysTypes {
|
|
478
|
+
types;
|
|
479
|
+
objects = [];
|
|
480
|
+
names = new Map;
|
|
481
|
+
fields = new Map;
|
|
482
|
+
constructor(types, names, blockNames) {
|
|
483
|
+
this.types = types;
|
|
484
|
+
for (const type of types.values()) {
|
|
485
|
+
if (type.kind === "block") {
|
|
486
|
+
const name = blockNames.get(type.block);
|
|
487
|
+
if (!name)
|
|
488
|
+
throw new Error("Missing verified block name");
|
|
489
|
+
this.names.set(type.id, name);
|
|
490
|
+
}
|
|
491
|
+
if (["record", "array", "enum", "string"].includes(type.kind)) {
|
|
492
|
+
const label = type.id.split(":").at(-1);
|
|
493
|
+
if (type.kind === "record" || type.kind === "enum") {
|
|
494
|
+
const prefix = type.kind === "record" ? "ST" : "E";
|
|
495
|
+
this.names.set(type.id, names.allocateObject(prefix, label));
|
|
496
|
+
} else
|
|
497
|
+
this.names.set(type.id, names.allocate(`T_${label}`));
|
|
498
|
+
}
|
|
499
|
+
if (type.kind === "record" || type.kind === "enum") {
|
|
500
|
+
const names = new Names, fields = new Map;
|
|
501
|
+
for (const member of type.kind === "record" ? type.fields : type.members) {
|
|
502
|
+
fields.set(member.name, names.allocate(member.name, true));
|
|
503
|
+
}
|
|
504
|
+
this.fields.set(type.id, fields);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
for (const type of types.values())
|
|
508
|
+
if (type.kind === "array" && instancedBlock2(type.id, types))
|
|
509
|
+
this.names.set(type.id, `ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)}`);
|
|
510
|
+
const emitted = new Set;
|
|
511
|
+
const emit = (type) => {
|
|
512
|
+
if (instancedBlock2(type.id, types) || !this.names.has(type.id) || emitted.has(type.id))
|
|
513
|
+
return;
|
|
514
|
+
if (type.kind === "array")
|
|
515
|
+
emit(this.type(type.element));
|
|
516
|
+
if (type.kind === "record") {
|
|
517
|
+
for (const field of type.fields)
|
|
518
|
+
emit(this.type(field.type));
|
|
519
|
+
}
|
|
520
|
+
emitted.add(type.id);
|
|
521
|
+
const name = this.name(type.id);
|
|
522
|
+
let declaration;
|
|
523
|
+
if (type.kind === "record") {
|
|
524
|
+
const fields = type.fields.map((field) => ` ${this.field(type.id, field.name)} : ${this.name(field.type)};`).join(`
|
|
525
|
+
`);
|
|
526
|
+
declaration = `TYPE ${name} :
|
|
527
|
+
STRUCT
|
|
528
|
+
${fields}
|
|
529
|
+
END_STRUCT
|
|
530
|
+
END_TYPE
|
|
531
|
+
`;
|
|
532
|
+
} else if (type.kind === "array") {
|
|
533
|
+
declaration = `TYPE ${name} : ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)};
|
|
534
|
+
END_TYPE
|
|
535
|
+
`;
|
|
536
|
+
} else if (type.kind === "string") {
|
|
537
|
+
declaration = `TYPE ${name} : ${type.encoding === "utf8" ? "STRING" : "WSTRING"}(${type.capacity});
|
|
538
|
+
END_TYPE
|
|
539
|
+
`;
|
|
540
|
+
} else if (type.kind === "enum") {
|
|
541
|
+
const members = type.members.map((member) => ` ${this.field(type.id, member.name)} := ${member.value}`).join(`,
|
|
542
|
+
`);
|
|
543
|
+
declaration = `{attribute 'qualified_only'}
|
|
544
|
+
{attribute 'strict'}
|
|
545
|
+
TYPE ${name} : (
|
|
546
|
+
${members}
|
|
547
|
+
) DINT;
|
|
548
|
+
END_TYPE
|
|
549
|
+
`;
|
|
550
|
+
} else
|
|
551
|
+
throw new Error("Unsupported verified data type");
|
|
552
|
+
this.objects.push({ id: type.id, kind: "dataType", name, declaration });
|
|
553
|
+
};
|
|
554
|
+
for (const type of types.values())
|
|
555
|
+
emit(type);
|
|
556
|
+
}
|
|
557
|
+
type(id) {
|
|
558
|
+
const type = this.types.get(id);
|
|
559
|
+
if (!type)
|
|
560
|
+
throw new Error("Missing verified type");
|
|
561
|
+
return type;
|
|
562
|
+
}
|
|
563
|
+
name(id) {
|
|
564
|
+
return this.names.get(id) ?? id;
|
|
565
|
+
}
|
|
566
|
+
field(id, name) {
|
|
567
|
+
const field = this.fields.get(id)?.get(name);
|
|
568
|
+
if (!field)
|
|
569
|
+
throw new Error("Missing verified field");
|
|
570
|
+
return field;
|
|
571
|
+
}
|
|
572
|
+
declarationInitializer(value, id) {
|
|
573
|
+
const type = this.type(id);
|
|
574
|
+
if ((type.kind === "record" || type.kind === "array") && this.isZero(value))
|
|
575
|
+
return "";
|
|
576
|
+
return ` := ${this.literal(value, id)}`;
|
|
577
|
+
}
|
|
578
|
+
isZero(value) {
|
|
579
|
+
if (value.kind === "bool")
|
|
580
|
+
return !value.value;
|
|
581
|
+
if (value.kind === "integer")
|
|
582
|
+
return value.value === "0";
|
|
583
|
+
if (value.kind === "float")
|
|
584
|
+
return Object.is(value.value, 0);
|
|
585
|
+
if (value.kind === "string")
|
|
586
|
+
return value.value === "";
|
|
587
|
+
if (value.kind === "array")
|
|
588
|
+
return value.items.every((item) => this.isZero(item));
|
|
589
|
+
if (value.kind === "record")
|
|
590
|
+
return Object.values(value.fields).every((item) => this.isZero(item));
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
literal(value, id) {
|
|
594
|
+
const type = this.type(id);
|
|
595
|
+
if (type.kind === "enum" && value.kind === "integer") {
|
|
596
|
+
const member = type.members.find((member) => member.value === value.value);
|
|
597
|
+
if (!member)
|
|
598
|
+
throw new Error("Missing verified enum member");
|
|
599
|
+
return `${this.name(id)}.${this.field(id, member.name)}`;
|
|
600
|
+
}
|
|
601
|
+
if (type.kind === "string" && value.kind === "string") {
|
|
602
|
+
const wide = type.encoding === "utf16", quote = wide ? '"' : "'";
|
|
603
|
+
let text = "";
|
|
604
|
+
for (const char of value.value) {
|
|
605
|
+
const code = char.charCodeAt(0);
|
|
606
|
+
if (char === "$")
|
|
607
|
+
text += "$$";
|
|
608
|
+
else if (char === quote)
|
|
609
|
+
text += `$${char}`;
|
|
610
|
+
else if (code < 32 || code === 127)
|
|
611
|
+
text += `$${code.toString(16).toUpperCase().padStart(wide ? 4 : 2, "0")}`;
|
|
612
|
+
else
|
|
613
|
+
text += char;
|
|
614
|
+
}
|
|
615
|
+
return `${quote}${text}${quote}`;
|
|
616
|
+
}
|
|
617
|
+
if (type.kind === "array" && value.kind === "array") {
|
|
618
|
+
return `[${value.items.map((item) => this.literal(item, type.element)).join(", ")}]`;
|
|
619
|
+
}
|
|
620
|
+
if (type.kind === "record" && value.kind === "record") {
|
|
621
|
+
const fields = type.fields.map((field) => {
|
|
622
|
+
const initial = value.fields[field.name];
|
|
623
|
+
if (!initial)
|
|
624
|
+
throw new Error("Missing verified record initializer");
|
|
625
|
+
return `${this.field(id, field.name)} := ${this.literal(initial, field.type)}`;
|
|
626
|
+
});
|
|
627
|
+
return `(${fields.join(", ")})`;
|
|
628
|
+
}
|
|
629
|
+
if (type.kind === "duration" && value.kind === "duration") {
|
|
630
|
+
return `T#${BigInt(value.nanoseconds) / 1000000n}ms`;
|
|
631
|
+
}
|
|
632
|
+
if (type.kind === "calendar" && value.kind === "calendar") {
|
|
633
|
+
if (type.domain === "date")
|
|
634
|
+
return `D#${value.value}`;
|
|
635
|
+
if (type.domain === "timeOfDay")
|
|
636
|
+
return `TOD#${value.value}`;
|
|
637
|
+
return `DT#${value.value.replace("T", "-")}`;
|
|
638
|
+
}
|
|
639
|
+
return scalarLiteral(value, id);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// packages/compiler/backends/codesys/global-names.ts
|
|
644
|
+
var planGlobalNames = (core, programNames = {}) => {
|
|
645
|
+
const names = new Names;
|
|
646
|
+
for (const name of Object.values(programNames))
|
|
647
|
+
names.reserve(name);
|
|
648
|
+
const typeMap = new Map(core.types.map((type) => [type.id, type]));
|
|
649
|
+
const blockNames = new Map(core.blocks.map((block) => [
|
|
650
|
+
block.id,
|
|
651
|
+
block.standard ? standardBlocks[block.standard].native : names.allocateObject("FB", block.name)
|
|
652
|
+
]));
|
|
653
|
+
for (const name of blockNames.values())
|
|
654
|
+
names.reserve(name);
|
|
655
|
+
const targetTypes = new CodesysTypes(typeMap, names, blockNames);
|
|
656
|
+
const { globalLists, retainedVariables } = storageLayout(core);
|
|
657
|
+
const hasExports = (group) => group.variables.some((variable) => variable.exported && !variable.exposure);
|
|
658
|
+
const globals = globalLists.map((group) => {
|
|
659
|
+
const splitByStorage = globalLists.some((other) => other.module === group.module && other.storage !== group.storage && hasExports(other));
|
|
660
|
+
const name = hasExports(group) ? names.allocate(pascalCase(splitByStorage ? `${group.module}-${group.storage}` : group.module)) : names.allocateObject("GVL", `${group.module}_${group.storage}`);
|
|
661
|
+
const fields = new Names;
|
|
662
|
+
for (const name of blockNames.values())
|
|
663
|
+
fields.reserve(name);
|
|
664
|
+
return {
|
|
665
|
+
...group,
|
|
666
|
+
name,
|
|
667
|
+
fields: new Map(group.variables.map((item) => [item.id, fields.allocate(item.name)]))
|
|
668
|
+
};
|
|
669
|
+
});
|
|
670
|
+
return { names, typeMap, blockNames, targetTypes, globals, retainedVariables };
|
|
671
|
+
};
|
|
672
|
+
|
|
427
673
|
// packages/compiler/backends/codesys/plan.ts
|
|
428
674
|
import {
|
|
429
|
-
instancedBlock as
|
|
675
|
+
instancedBlock as instancedBlock4,
|
|
430
676
|
isVerifiedCore
|
|
431
677
|
} from "@tsplc/compiler/ir";
|
|
432
678
|
import { ioLayout as ioLayout2 } from "@tsplc/typegen/hardware";
|
|
433
679
|
|
|
434
680
|
// packages/compiler/backends/codesys/io-paths.ts
|
|
435
681
|
import { ioLayout } from "@tsplc/typegen/hardware";
|
|
436
|
-
|
|
437
|
-
// packages/compiler/backends/codesys/storage.ts
|
|
438
|
-
import { basename } from "node:path";
|
|
439
|
-
import { instancedBlock } from "@tsplc/compiler/ir";
|
|
440
|
-
var moduleName = (variable) => basename(variable.span.file).replace(/\.[^.]+$/, "");
|
|
441
|
-
function storageLayout(core) {
|
|
442
|
-
const types = new Map(core.types.map((type) => [type.id, type]));
|
|
443
|
-
const blocks = new Map(core.blocks.map((block) => [block.id, block]));
|
|
444
|
-
const retention = new Map;
|
|
445
|
-
const blockRetained = (id) => {
|
|
446
|
-
const cached = retention.get(id);
|
|
447
|
-
if (cached !== undefined)
|
|
448
|
-
return cached;
|
|
449
|
-
const block = blocks.get(id);
|
|
450
|
-
if (!block)
|
|
451
|
-
throw new Error("Missing verified block during retention planning.");
|
|
452
|
-
const retained = block.state.some((field) => {
|
|
453
|
-
const type = instancedBlock(field.type, types);
|
|
454
|
-
return field.retain || type !== undefined && blockRetained(type.block);
|
|
455
|
-
});
|
|
456
|
-
retention.set(id, retained);
|
|
457
|
-
return retained;
|
|
458
|
-
};
|
|
459
|
-
const retainedBlocks = new Set(core.blocks.filter((block) => blockRetained(block.id)).map((block) => block.id));
|
|
460
|
-
const retainedVariables = new Set([...core.variables, ...core.blocks.flatMap((block) => block.state)].filter((variable) => {
|
|
461
|
-
const type = instancedBlock(variable.type, types);
|
|
462
|
-
return variable.retain || type !== undefined && retainedBlocks.has(type.block);
|
|
463
|
-
}).map((variable) => variable.id));
|
|
464
|
-
const groups = new Map;
|
|
465
|
-
for (const variable of core.variables) {
|
|
466
|
-
if (variable.storage === "input" || variable.storage === "output")
|
|
467
|
-
continue;
|
|
468
|
-
const storage = variable.storage === "constant" ? "constants" : retainedVariables.has(variable.id) ? "retained" : "state";
|
|
469
|
-
const id = `${variable.span.file}:${storage}`;
|
|
470
|
-
let group = groups.get(id);
|
|
471
|
-
if (!group) {
|
|
472
|
-
group = { id, module: moduleName(variable), storage, variables: [] };
|
|
473
|
-
groups.set(id, group);
|
|
474
|
-
}
|
|
475
|
-
group.variables.push(variable);
|
|
476
|
-
}
|
|
477
|
-
return { retainedBlocks, retainedVariables, globalLists: [...groups.values()] };
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// packages/compiler/backends/codesys/io-paths.ts
|
|
481
682
|
var pathPattern = /^([\w$-]+)\.([A-Za-z_$][\w$]*)((?:\.[A-Za-z_$][\w$]*|\[-?\d+\])*)$/;
|
|
482
683
|
var isStoragePath = (key) => key.includes(".") || key.includes("[");
|
|
483
684
|
function resolveStoragePath(core, key) {
|
|
@@ -1006,7 +1207,54 @@ IF digits=0 THEN malformed:=TRUE;END_IF;IF negative THEN numberValue:=-numberVal
|
|
|
1006
1207
|
};
|
|
1007
1208
|
|
|
1008
1209
|
// packages/compiler/backends/codesys/task-access.ts
|
|
1009
|
-
import { instancedBlock as
|
|
1210
|
+
import { instancedBlock as instancedBlock3 } from "@tsplc/compiler/ir";
|
|
1211
|
+
function schedulePrograms(core, tasks, report) {
|
|
1212
|
+
const background = new Map;
|
|
1213
|
+
for (const program of core.programs) {
|
|
1214
|
+
if (!program.background)
|
|
1215
|
+
continue;
|
|
1216
|
+
const { priority, intervalNanoseconds } = program.background;
|
|
1217
|
+
const name = `TsplcBackground_${priority}_${BigInt(intervalNanoseconds) / 1000000n}`;
|
|
1218
|
+
const existing = background.get(name);
|
|
1219
|
+
if (existing) {
|
|
1220
|
+
existing.programs.push(program.name);
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
background.set(name, {
|
|
1224
|
+
name,
|
|
1225
|
+
priority,
|
|
1226
|
+
intervalNanoseconds,
|
|
1227
|
+
programs: [program.name]
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
return appendContinuations(core, [...tasks, ...background.values()], report);
|
|
1231
|
+
}
|
|
1232
|
+
function taskStorageOwnership(core, report) {
|
|
1233
|
+
const owners = new Map;
|
|
1234
|
+
const globals = new Map(core.variables.map((item) => [item.id, item]));
|
|
1235
|
+
const functions = new Map(core.functions.map((item) => [item.id, item]));
|
|
1236
|
+
const blocks = new Map(core.blocks.map((block) => [block.id, block.runtime?.kind]));
|
|
1237
|
+
const runtimes = new Map(core.types.flatMap((type) => type.kind === "block" ? [[type.id, blocks.get(type.block)]] : []));
|
|
1238
|
+
return (task, program) => {
|
|
1239
|
+
const fn = functions.get(program.entry);
|
|
1240
|
+
if (!fn)
|
|
1241
|
+
throw new Error("Verified core has a missing entry.");
|
|
1242
|
+
const worker = program.background?.worker;
|
|
1243
|
+
const host = worker && core.variables.find((item) => item.name === worker && item.span.file === fn.span.file && runtimes.get(item.type) === "backgroundHost");
|
|
1244
|
+
for (const id of accessedStorage(fn, core, host ? host.id : undefined)) {
|
|
1245
|
+
const item = globals.get(id);
|
|
1246
|
+
if (!item || item.storage === "constant" || runtimes.get(item.type) === "mailbox")
|
|
1247
|
+
continue;
|
|
1248
|
+
const ownsWorker = worker === item.name && item.span.file === fn.span.file;
|
|
1249
|
+
if (program.background && (!ownsWorker || host && item.id === host.id))
|
|
1250
|
+
report(`Background task ${program.name} accesses scan-owned storage ${item.name}; pass value snapshots through the mailbox.`, item.span);
|
|
1251
|
+
const owner = owners.get(id);
|
|
1252
|
+
if (owner && owner !== task)
|
|
1253
|
+
report(`Storage ${item.name} is shared across cyclic tasks ${owner} and ${task}.`, item.span);
|
|
1254
|
+
owners.set(id, task);
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1010
1258
|
function accessedStorage(fn, core, scheduledHost) {
|
|
1011
1259
|
const scheduledCall = fn.body.find((item) => item.kind === "invoke" && item.instance === scheduledHost);
|
|
1012
1260
|
const result = new Set, seen = new Set;
|
|
@@ -1095,7 +1343,7 @@ function accessedStorage(fn, core, scheduledHost) {
|
|
|
1095
1343
|
const variable = variables.get(item.instance);
|
|
1096
1344
|
if (!variable)
|
|
1097
1345
|
throw new Error("Missing verified instance during task planning.");
|
|
1098
|
-
const type =
|
|
1346
|
+
const type = instancedBlock3(variable.type, types);
|
|
1099
1347
|
const block = type && blocks.get(type.block);
|
|
1100
1348
|
if (block && !seen.has(block.id)) {
|
|
1101
1349
|
seen.add(block.id);
|
|
@@ -1108,6 +1356,44 @@ function accessedStorage(fn, core, scheduledHost) {
|
|
|
1108
1356
|
body(fn.body);
|
|
1109
1357
|
return result;
|
|
1110
1358
|
}
|
|
1359
|
+
function appendContinuations(core, tasks, report) {
|
|
1360
|
+
const continuations = core.programs.flatMap((program) => program.continuation ? [{ name: program.name, scope: program.continuation }] : []);
|
|
1361
|
+
if (!continuations.length)
|
|
1362
|
+
return tasks;
|
|
1363
|
+
const programs = new Map(core.programs.map((program) => [program.name, program]));
|
|
1364
|
+
const functions = new Map(core.functions.map((routine) => [routine.id, routine]));
|
|
1365
|
+
const placements = tasks.map((task) => {
|
|
1366
|
+
const roots = new Set;
|
|
1367
|
+
for (const name of task.programs) {
|
|
1368
|
+
const program = programs.get(name);
|
|
1369
|
+
if (!program || program.background)
|
|
1370
|
+
continue;
|
|
1371
|
+
const entry = functions.get(program.entry);
|
|
1372
|
+
if (entry)
|
|
1373
|
+
for (const id of accessedStorage(entry, core))
|
|
1374
|
+
roots.add(id);
|
|
1375
|
+
}
|
|
1376
|
+
return { task, roots, additions: [] };
|
|
1377
|
+
});
|
|
1378
|
+
for (const { name, scope } of continuations) {
|
|
1379
|
+
const root = core.variables.find((variable) => variable.name === scope.instance && variable.span.file === scope.file);
|
|
1380
|
+
if (!root) {
|
|
1381
|
+
report(`Missing continuation owner ${scope.instance}.`);
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
const owners = placements.filter(({ roots }) => roots.has(root.id));
|
|
1385
|
+
const [owner] = owners;
|
|
1386
|
+
if (!owner || owners.length !== 1) {
|
|
1387
|
+
report(`Continuation owner ${scope.instance} must belong to exactly one caller task.`, root.span);
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
owner.additions.push(name);
|
|
1391
|
+
}
|
|
1392
|
+
return placements.map(({ task, additions }) => ({
|
|
1393
|
+
...task,
|
|
1394
|
+
programs: task.programs.concat(additions)
|
|
1395
|
+
}));
|
|
1396
|
+
}
|
|
1111
1397
|
|
|
1112
1398
|
// packages/compiler/backends/codesys/publication.ts
|
|
1113
1399
|
var globals = (core) => core.variables.filter((variable) => variable.storage === "module" && variable.exported);
|
|
@@ -1168,49 +1454,12 @@ function planCodesys(core, deployment, value) {
|
|
|
1168
1454
|
return {
|
|
1169
1455
|
diagnostics: parsed.error.issues.map((issue) => diagnostic(`${issue.path.join(".")}: ${issue.message}`))
|
|
1170
1456
|
};
|
|
1171
|
-
const backgroundTasks = new Map;
|
|
1172
|
-
for (const program of core.programs) {
|
|
1173
|
-
if (!program.background)
|
|
1174
|
-
continue;
|
|
1175
|
-
const { priority, intervalNanoseconds } = program.background;
|
|
1176
|
-
const name = `TsplcBackground_${priority}_${BigInt(intervalNanoseconds) / 1000000n}`;
|
|
1177
|
-
const existing = backgroundTasks.get(name);
|
|
1178
|
-
backgroundTasks.set(name, {
|
|
1179
|
-
name,
|
|
1180
|
-
priority,
|
|
1181
|
-
intervalNanoseconds,
|
|
1182
|
-
programs: [...existing?.programs ?? [], program.name]
|
|
1183
|
-
});
|
|
1184
|
-
}
|
|
1185
|
-
deployment = { tasks: [...deployment.tasks, ...backgroundTasks.values()] };
|
|
1186
1457
|
const options = parsed.data;
|
|
1187
1458
|
const diagnostics = [];
|
|
1188
1459
|
const report = (message, span) => {
|
|
1189
1460
|
diagnostics.push(diagnostic(message, span));
|
|
1190
1461
|
};
|
|
1191
|
-
|
|
1192
|
-
for (const continuation of core.programs.filter((program) => program.continuation)) {
|
|
1193
|
-
const scope = continuation.continuation;
|
|
1194
|
-
const root = core.variables.find((variable) => variable.name === scope.instance && variable.span.file === scope.file);
|
|
1195
|
-
if (!root) {
|
|
1196
|
-
report(`Missing continuation owner ${scope.instance}.`);
|
|
1197
|
-
continue;
|
|
1198
|
-
}
|
|
1199
|
-
const owners = callerTasks.filter((task) => task.programs.some((name) => {
|
|
1200
|
-
const program = core.programs.find((program) => program.name === name);
|
|
1201
|
-
if (!program || program.background)
|
|
1202
|
-
return false;
|
|
1203
|
-
const fn = core.functions.find((fn) => fn.id === program.entry);
|
|
1204
|
-
return fn && accessedStorage(fn, core).has(root.id);
|
|
1205
|
-
}));
|
|
1206
|
-
if (owners.length !== 1) {
|
|
1207
|
-
report(`Continuation owner ${scope.instance} must belong to exactly one caller task.`, root.span);
|
|
1208
|
-
continue;
|
|
1209
|
-
}
|
|
1210
|
-
deployment = {
|
|
1211
|
-
tasks: deployment.tasks.map((task) => task.name === owners[0].name ? { ...task, programs: [...task.programs, continuation.name] } : task)
|
|
1212
|
-
};
|
|
1213
|
-
}
|
|
1462
|
+
deployment = { tasks: schedulePrograms(core, deployment.tasks, report) };
|
|
1214
1463
|
const http = new Map;
|
|
1215
1464
|
for (const block of core.blocks)
|
|
1216
1465
|
if (block.runtime?.kind === "http" || block.runtime?.kind === "rpc") {
|
|
@@ -1367,10 +1616,7 @@ function validateTasks(core, deployment, report) {
|
|
|
1367
1616
|
const programs = new Map(core.programs.map((program) => [program.name, program]));
|
|
1368
1617
|
const assignment = new Map;
|
|
1369
1618
|
const taskNames = new Set;
|
|
1370
|
-
const
|
|
1371
|
-
const globals = new Map(core.variables.map((item) => [item.id, item]));
|
|
1372
|
-
const functions = new Map(core.functions.map((item) => [item.id, item]));
|
|
1373
|
-
const blockRuntimes = new Map(core.types.flatMap((type) => type.kind === "block" ? [[type.id, core.blocks.find((block) => block.id === type.block)?.runtime?.kind]] : []));
|
|
1619
|
+
const checkOwnership = taskStorageOwnership(core, report);
|
|
1374
1620
|
for (const task of deployment.tasks) {
|
|
1375
1621
|
if (!isIecIdentifier(task.name))
|
|
1376
1622
|
report(`Invalid task name ${task.name}.`);
|
|
@@ -1397,24 +1643,7 @@ function validateTasks(core, deployment, report) {
|
|
|
1397
1643
|
if (assignment.has(name))
|
|
1398
1644
|
report(`Program ${name} must belong to exactly one task.`);
|
|
1399
1645
|
assignment.set(name, task.name);
|
|
1400
|
-
|
|
1401
|
-
if (!fn)
|
|
1402
|
-
throw new Error("Verified core has a missing entry.");
|
|
1403
|
-
const host = program.background && core.variables.find((item) => item.name === program.background?.worker && item.span.file === fn.span.file && blockRuntimes.get(item.type) === "backgroundHost");
|
|
1404
|
-
for (const id of accessedStorage(fn, core, host ? host.id : undefined)) {
|
|
1405
|
-
const item = globals.get(id);
|
|
1406
|
-
if (!item || item.storage === "constant")
|
|
1407
|
-
continue;
|
|
1408
|
-
if (blockRuntimes.get(item.type) === "mailbox")
|
|
1409
|
-
continue;
|
|
1410
|
-
const ownsWorker = program.background?.worker === item.name && item.span.file === fn.span.file;
|
|
1411
|
-
if (program.background && (!ownsWorker || item.id === host?.id))
|
|
1412
|
-
report(`Background task ${program.name} accesses scan-owned storage ${item.name}; pass value snapshots through the mailbox.`, item.span);
|
|
1413
|
-
const owner = stateOwners.get(id);
|
|
1414
|
-
if (owner && owner !== task.name)
|
|
1415
|
-
report(`Storage ${item.name} is shared across cyclic tasks ${owner} and ${task.name}.`, item.span);
|
|
1416
|
-
stateOwners.set(id, task.name);
|
|
1417
|
-
}
|
|
1646
|
+
checkOwnership(task.name, program);
|
|
1418
1647
|
}
|
|
1419
1648
|
}
|
|
1420
1649
|
for (const name of programs.keys())
|
|
@@ -1442,7 +1671,7 @@ function validateStorage(core, options, report) {
|
|
|
1442
1671
|
const blockHasInOut = (id) => {
|
|
1443
1672
|
const block = blocks.get(id);
|
|
1444
1673
|
return block.inputs.some((input) => input.mode === "inOut") || block.state.some((field) => {
|
|
1445
|
-
const type =
|
|
1674
|
+
const type = instancedBlock4(field.type, types);
|
|
1446
1675
|
return type !== undefined && blockHasInOut(type.block);
|
|
1447
1676
|
});
|
|
1448
1677
|
};
|
|
@@ -1451,7 +1680,7 @@ function validateStorage(core, options, report) {
|
|
|
1451
1680
|
if (layout.retainedBlocks.has(block.id) && blockHasInOut(block.id))
|
|
1452
1681
|
report("Retained function blocks cannot contain direct or nested InOut references.", block.span);
|
|
1453
1682
|
for (const variable of core.variables) {
|
|
1454
|
-
const type =
|
|
1683
|
+
const type = instancedBlock4(variable.type, types);
|
|
1455
1684
|
if (variable.retain && type && blockHasInOut(type.block))
|
|
1456
1685
|
report("Retained function blocks cannot contain direct or nested InOut references.", variable.span);
|
|
1457
1686
|
}
|
|
@@ -1543,174 +1772,6 @@ function validateConstants(core, report) {
|
|
|
1543
1772
|
});
|
|
1544
1773
|
}
|
|
1545
1774
|
|
|
1546
|
-
// packages/compiler/backends/codesys/types.ts
|
|
1547
|
-
import { instancedBlock as instancedBlock4 } from "@tsplc/compiler/ir";
|
|
1548
|
-
class CodesysTypes {
|
|
1549
|
-
types;
|
|
1550
|
-
objects = [];
|
|
1551
|
-
names = new Map;
|
|
1552
|
-
fields = new Map;
|
|
1553
|
-
constructor(types, names, blockNames) {
|
|
1554
|
-
this.types = types;
|
|
1555
|
-
for (const type of types.values()) {
|
|
1556
|
-
if (type.kind === "block") {
|
|
1557
|
-
const name = blockNames.get(type.block);
|
|
1558
|
-
if (!name)
|
|
1559
|
-
throw new Error("Missing verified block name");
|
|
1560
|
-
this.names.set(type.id, name);
|
|
1561
|
-
}
|
|
1562
|
-
if (["record", "array", "enum", "string"].includes(type.kind)) {
|
|
1563
|
-
const label = type.id.split(":").at(-1);
|
|
1564
|
-
if (type.kind === "record" || type.kind === "enum") {
|
|
1565
|
-
const prefix = type.kind === "record" ? "ST" : "E";
|
|
1566
|
-
this.names.set(type.id, names.allocateObject(prefix, label));
|
|
1567
|
-
} else
|
|
1568
|
-
this.names.set(type.id, names.allocate(`T_${label}`));
|
|
1569
|
-
}
|
|
1570
|
-
if (type.kind === "record" || type.kind === "enum") {
|
|
1571
|
-
const names = new Names, fields = new Map;
|
|
1572
|
-
for (const member of type.kind === "record" ? type.fields : type.members) {
|
|
1573
|
-
fields.set(member.name, names.allocate(member.name, true));
|
|
1574
|
-
}
|
|
1575
|
-
this.fields.set(type.id, fields);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
for (const type of types.values())
|
|
1579
|
-
if (type.kind === "array" && instancedBlock4(type.id, types))
|
|
1580
|
-
this.names.set(type.id, `ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)}`);
|
|
1581
|
-
const emitted = new Set;
|
|
1582
|
-
const emit = (type) => {
|
|
1583
|
-
if (instancedBlock4(type.id, types) || !this.names.has(type.id) || emitted.has(type.id))
|
|
1584
|
-
return;
|
|
1585
|
-
if (type.kind === "array")
|
|
1586
|
-
emit(this.type(type.element));
|
|
1587
|
-
if (type.kind === "record") {
|
|
1588
|
-
for (const field of type.fields)
|
|
1589
|
-
emit(this.type(field.type));
|
|
1590
|
-
}
|
|
1591
|
-
emitted.add(type.id);
|
|
1592
|
-
const name = this.name(type.id);
|
|
1593
|
-
let declaration;
|
|
1594
|
-
if (type.kind === "record") {
|
|
1595
|
-
const fields = type.fields.map((field) => ` ${this.field(type.id, field.name)} : ${this.name(field.type)};`).join(`
|
|
1596
|
-
`);
|
|
1597
|
-
declaration = `TYPE ${name} :
|
|
1598
|
-
STRUCT
|
|
1599
|
-
${fields}
|
|
1600
|
-
END_STRUCT
|
|
1601
|
-
END_TYPE
|
|
1602
|
-
`;
|
|
1603
|
-
} else if (type.kind === "array") {
|
|
1604
|
-
declaration = `TYPE ${name} : ARRAY[${type.lowerBound}..${type.lowerBound + type.length - 1}] OF ${this.name(type.element)};
|
|
1605
|
-
END_TYPE
|
|
1606
|
-
`;
|
|
1607
|
-
} else if (type.kind === "string") {
|
|
1608
|
-
declaration = `TYPE ${name} : ${type.encoding === "utf8" ? "STRING" : "WSTRING"}(${type.capacity});
|
|
1609
|
-
END_TYPE
|
|
1610
|
-
`;
|
|
1611
|
-
} else if (type.kind === "enum") {
|
|
1612
|
-
const members = type.members.map((member) => ` ${this.field(type.id, member.name)} := ${member.value}`).join(`,
|
|
1613
|
-
`);
|
|
1614
|
-
declaration = `{attribute 'qualified_only'}
|
|
1615
|
-
{attribute 'strict'}
|
|
1616
|
-
TYPE ${name} : (
|
|
1617
|
-
${members}
|
|
1618
|
-
) DINT;
|
|
1619
|
-
END_TYPE
|
|
1620
|
-
`;
|
|
1621
|
-
} else
|
|
1622
|
-
throw new Error("Unsupported verified data type");
|
|
1623
|
-
this.objects.push({ id: type.id, kind: "dataType", name, declaration });
|
|
1624
|
-
};
|
|
1625
|
-
for (const type of types.values())
|
|
1626
|
-
emit(type);
|
|
1627
|
-
}
|
|
1628
|
-
type(id) {
|
|
1629
|
-
const type = this.types.get(id);
|
|
1630
|
-
if (!type)
|
|
1631
|
-
throw new Error("Missing verified type");
|
|
1632
|
-
return type;
|
|
1633
|
-
}
|
|
1634
|
-
name(id) {
|
|
1635
|
-
return this.names.get(id) ?? id;
|
|
1636
|
-
}
|
|
1637
|
-
field(id, name) {
|
|
1638
|
-
const field = this.fields.get(id)?.get(name);
|
|
1639
|
-
if (!field)
|
|
1640
|
-
throw new Error("Missing verified field");
|
|
1641
|
-
return field;
|
|
1642
|
-
}
|
|
1643
|
-
declarationInitializer(value, id) {
|
|
1644
|
-
const type = this.type(id);
|
|
1645
|
-
if ((type.kind === "record" || type.kind === "array") && this.isZero(value))
|
|
1646
|
-
return "";
|
|
1647
|
-
return ` := ${this.literal(value, id)}`;
|
|
1648
|
-
}
|
|
1649
|
-
isZero(value) {
|
|
1650
|
-
if (value.kind === "bool")
|
|
1651
|
-
return !value.value;
|
|
1652
|
-
if (value.kind === "integer")
|
|
1653
|
-
return value.value === "0";
|
|
1654
|
-
if (value.kind === "float")
|
|
1655
|
-
return Object.is(value.value, 0);
|
|
1656
|
-
if (value.kind === "string")
|
|
1657
|
-
return value.value === "";
|
|
1658
|
-
if (value.kind === "array")
|
|
1659
|
-
return value.items.every((item) => this.isZero(item));
|
|
1660
|
-
if (value.kind === "record")
|
|
1661
|
-
return Object.values(value.fields).every((item) => this.isZero(item));
|
|
1662
|
-
return false;
|
|
1663
|
-
}
|
|
1664
|
-
literal(value, id) {
|
|
1665
|
-
const type = this.type(id);
|
|
1666
|
-
if (type.kind === "enum" && value.kind === "integer") {
|
|
1667
|
-
const member = type.members.find((member) => member.value === value.value);
|
|
1668
|
-
if (!member)
|
|
1669
|
-
throw new Error("Missing verified enum member");
|
|
1670
|
-
return `${this.name(id)}.${this.field(id, member.name)}`;
|
|
1671
|
-
}
|
|
1672
|
-
if (type.kind === "string" && value.kind === "string") {
|
|
1673
|
-
const wide = type.encoding === "utf16", quote = wide ? '"' : "'";
|
|
1674
|
-
let text = "";
|
|
1675
|
-
for (const char of value.value) {
|
|
1676
|
-
const code = char.charCodeAt(0);
|
|
1677
|
-
if (char === "$")
|
|
1678
|
-
text += "$$";
|
|
1679
|
-
else if (char === quote)
|
|
1680
|
-
text += `$${char}`;
|
|
1681
|
-
else if (code < 32 || code === 127)
|
|
1682
|
-
text += `$${code.toString(16).toUpperCase().padStart(wide ? 4 : 2, "0")}`;
|
|
1683
|
-
else
|
|
1684
|
-
text += char;
|
|
1685
|
-
}
|
|
1686
|
-
return `${quote}${text}${quote}`;
|
|
1687
|
-
}
|
|
1688
|
-
if (type.kind === "array" && value.kind === "array") {
|
|
1689
|
-
return `[${value.items.map((item) => this.literal(item, type.element)).join(", ")}]`;
|
|
1690
|
-
}
|
|
1691
|
-
if (type.kind === "record" && value.kind === "record") {
|
|
1692
|
-
const fields = type.fields.map((field) => {
|
|
1693
|
-
const initial = value.fields[field.name];
|
|
1694
|
-
if (!initial)
|
|
1695
|
-
throw new Error("Missing verified record initializer");
|
|
1696
|
-
return `${this.field(id, field.name)} := ${this.literal(initial, field.type)}`;
|
|
1697
|
-
});
|
|
1698
|
-
return `(${fields.join(", ")})`;
|
|
1699
|
-
}
|
|
1700
|
-
if (type.kind === "duration" && value.kind === "duration") {
|
|
1701
|
-
return `T#${BigInt(value.nanoseconds) / 1000000n}ms`;
|
|
1702
|
-
}
|
|
1703
|
-
if (type.kind === "calendar" && value.kind === "calendar") {
|
|
1704
|
-
if (type.domain === "date")
|
|
1705
|
-
return `D#${value.value}`;
|
|
1706
|
-
if (type.domain === "timeOfDay")
|
|
1707
|
-
return `TOD#${value.value}`;
|
|
1708
|
-
return `DT#${value.value.replace("T", "-")}`;
|
|
1709
|
-
}
|
|
1710
|
-
return scalarLiteral(value, id);
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
1775
|
// packages/compiler/backends/codesys/emit.ts
|
|
1715
1776
|
function required(value) {
|
|
1716
1777
|
if (value === undefined)
|
|
@@ -1734,7 +1795,7 @@ function emitCodesys(plan) {
|
|
|
1734
1795
|
|
|
1735
1796
|
class CodesysEmitter {
|
|
1736
1797
|
plan;
|
|
1737
|
-
names
|
|
1798
|
+
names;
|
|
1738
1799
|
networkName;
|
|
1739
1800
|
rpcChannels = [];
|
|
1740
1801
|
references = new Map;
|
|
@@ -1753,7 +1814,8 @@ class CodesysEmitter {
|
|
|
1753
1814
|
routineScopes = new Map;
|
|
1754
1815
|
targetTypes;
|
|
1755
1816
|
numeric;
|
|
1756
|
-
|
|
1817
|
+
globals;
|
|
1818
|
+
retainedVariables;
|
|
1757
1819
|
constructor(plan) {
|
|
1758
1820
|
this.plan = plan;
|
|
1759
1821
|
const { core, options } = plan;
|
|
@@ -1762,19 +1824,15 @@ class CodesysEmitter {
|
|
|
1762
1824
|
if (!fn.owner && [...accessedStorage(fn, core)].some((id) => persistent.has(id)))
|
|
1763
1825
|
this.programRoutines.add(fn.id);
|
|
1764
1826
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
this.typeMap =
|
|
1827
|
+
const { names, typeMap, blockNames, targetTypes, globals, retainedVariables } = planGlobalNames(core, options.programNames);
|
|
1828
|
+
this.names = names;
|
|
1829
|
+
this.typeMap = typeMap;
|
|
1830
|
+
this.blockNames = blockNames;
|
|
1831
|
+
this.targetTypes = targetTypes;
|
|
1832
|
+
this.globals = globals;
|
|
1833
|
+
this.retainedVariables = retainedVariables;
|
|
1768
1834
|
this.blocks = new Map(core.blocks.map((block) => [block.id, block]));
|
|
1769
|
-
this.blockNames = new Map(core.blocks.map((block) => [
|
|
1770
|
-
block.id,
|
|
1771
|
-
block.standard ? standardBlocks[block.standard].native : this.names.allocateObject("FB", block.name)
|
|
1772
|
-
]));
|
|
1773
|
-
for (const name of this.blockNames.values())
|
|
1774
|
-
this.names.reserve(name);
|
|
1775
|
-
this.targetTypes = new CodesysTypes(this.typeMap, this.names, this.blockNames);
|
|
1776
1835
|
this.numeric = new NumericLowering(this.typeMap, (label) => this.names.allocate(label));
|
|
1777
|
-
this.layout = storageLayout(core);
|
|
1778
1836
|
this.objects.push(...this.targetTypes.objects);
|
|
1779
1837
|
}
|
|
1780
1838
|
emit() {
|
|
@@ -1899,7 +1957,7 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
|
|
|
1899
1957
|
const program = core.programs.find((item) => item.id === object.id);
|
|
1900
1958
|
if (program)
|
|
1901
1959
|
source = program.entry.split(":")[0] ?? "";
|
|
1902
|
-
const group = this.
|
|
1960
|
+
const group = this.globals.find((item) => item.id === object.id);
|
|
1903
1961
|
if (group) {
|
|
1904
1962
|
source = group.variables[0]?.id.split(":")[0] ?? "";
|
|
1905
1963
|
}
|
|
@@ -1915,23 +1973,18 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
|
|
|
1915
1973
|
}
|
|
1916
1974
|
emitGlobals() {
|
|
1917
1975
|
const { options } = this.plan;
|
|
1918
|
-
const {
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
const splitByStorage = layout.globalLists.some((other) => other.module === group.module && other.storage !== group.storage && hasExports(other));
|
|
1922
|
-
const name = hasExports(group) ? names.allocate(pascalCase(splitByStorage ? `${group.module}-${group.storage}` : group.module)) : names.allocateObject("GVL", `${group.module}_${group.storage}`);
|
|
1976
|
+
const { globals, references, mappings, targetTypes, typeMap, objects } = this;
|
|
1977
|
+
for (const group of globals) {
|
|
1978
|
+
const { name, fields } = group;
|
|
1923
1979
|
const published = new Set(this.plan.publication[options.buildTarget]);
|
|
1924
1980
|
const publish = group.variables.some((variable) => published.has(variable.id));
|
|
1925
|
-
const fields = new Names;
|
|
1926
|
-
for (const name of this.blockNames.values())
|
|
1927
|
-
fields.reserve(name);
|
|
1928
1981
|
const lines = [
|
|
1929
1982
|
...publish ? ["{attribute 'linkalways'}"] : [],
|
|
1930
1983
|
"{attribute 'qualified_only'}",
|
|
1931
1984
|
group.storage === "constants" ? "VAR_GLOBAL CONSTANT" : group.storage === "retained" ? "VAR_GLOBAL RETAIN" : "VAR_GLOBAL"
|
|
1932
1985
|
];
|
|
1933
1986
|
for (const item of group.variables) {
|
|
1934
|
-
const field = fields.
|
|
1987
|
+
const field = required(fields.get(item.id));
|
|
1935
1988
|
references.set(item.id, `${name}.${field}`);
|
|
1936
1989
|
if (published.has(item.id))
|
|
1937
1990
|
lines.push(` {attribute 'symbol' := '${item.exposure ? "read" : "readwrite"}'}`);
|
|
@@ -2076,7 +2129,7 @@ export const serviceChannels=${JSON.stringify(this.rpcChannels, null, 2)} as con
|
|
|
2076
2129
|
fields.reserve(objectName);
|
|
2077
2130
|
for (const method of core.functions.filter((fn) => fn.owner?.block === block.id))
|
|
2078
2131
|
functionNames.set(method.id, fields.allocate(method.name));
|
|
2079
|
-
const native = block.standard &&
|
|
2132
|
+
const native = block.standard && standardBlocks2[block.standard];
|
|
2080
2133
|
for (const item of [...block.inputs, ...block.outputs, ...block.state])
|
|
2081
2134
|
mapped.set(item.id, native ? required([...native.inputs, ...native.outputs].find(([name]) => name === item.name))[1] : fields.allocate(item.name, true));
|
|
2082
2135
|
if (block.runtime?.kind === "mailbox")
|
|
@@ -2171,7 +2224,7 @@ END_VAR`
|
|
|
2171
2224
|
blocks,
|
|
2172
2225
|
blockFields,
|
|
2173
2226
|
numeric,
|
|
2174
|
-
|
|
2227
|
+
retainedVariables
|
|
2175
2228
|
} = this;
|
|
2176
2229
|
const block = "inputs" in fn ? fn : undefined;
|
|
2177
2230
|
const method = "owner" in fn ? fn.owner : undefined;
|
|
@@ -2450,8 +2503,8 @@ END_VAR`
|
|
|
2450
2503
|
if (block.runtime?.kind === "mailbox")
|
|
2451
2504
|
declaration.push("VAR", ` ${required(this.mailboxLocks.get(block.id))} : __XWORD;`, "END_VAR");
|
|
2452
2505
|
section("VAR_OUTPUT", block.outputs);
|
|
2453
|
-
section("VAR", block.state.filter((item) => !
|
|
2454
|
-
section("VAR RETAIN", block.state.filter((item) =>
|
|
2506
|
+
section("VAR", block.state.filter((item) => !retainedVariables.has(item.id)));
|
|
2507
|
+
section("VAR RETAIN", block.state.filter((item) => retainedVariables.has(item.id)));
|
|
2455
2508
|
}
|
|
2456
2509
|
if (declarations.length) {
|
|
2457
2510
|
declaration.push(block || program ? "VAR_TEMP" : "VAR");
|
|
@@ -2582,5 +2635,5 @@ export {
|
|
|
2582
2635
|
publicationSelectors
|
|
2583
2636
|
};
|
|
2584
2637
|
|
|
2585
|
-
//# debugId=
|
|
2638
|
+
//# debugId=C98587762BFC22D464756E2164756E21
|
|
2586
2639
|
//# sourceMappingURL=codesys.js.map
|