@tsplc/iec 0.0.0-stage → 0.2.18
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/README.md +6 -2
- package/dist/index.js +100 -0
- package/dist/index.js.map +11 -0
- package/dist/st.js +183 -0
- package/dist/st.js.map +11 -0
- package/package.json +31 -5
- package/src/direct-addresses.ts +61 -0
- package/src/elementary-types.ts +120 -0
- package/src/index.ts +17 -0
- package/src/st/index.ts +19 -0
- package/src/st/lexical.ts +77 -0
- package/src/st/vocabulary.ts +128 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tsplc/iec
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Part of [tsPLC](https://github.com/rip-technologies/tsplc).
|
|
4
|
+
|
|
5
|
+
The IEC 61131-3 vocabulary the rest of the toolchain shares. `elementaryTypes` lists each elementary type with its family, width and signedness as CODESYS implements them, `elementaryType` finds one by its full name or its alias (`TOD`, `DT`), and `integerRange` gives the bounds of an integer or bit-string type. `sizePrefixes` gives the type each direct-address size prefix (`X`, `B`, `W`, `D`, `L`) names, and `ioScalar` and `ioArray` read the scalars and spans a process image binds. Each package selects the types it supports from here rather than keeping its own table.
|
|
6
|
+
|
|
7
|
+
See the [documentation](https://github.com/rip-technologies/tsplc/tree/main/docs). MIT licensed.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// packages/iec/src/elementary-types.ts
|
|
2
|
+
var elementaryTypes = [
|
|
3
|
+
{ name: "BOOL", family: "bool", bits: 1 },
|
|
4
|
+
{ name: "SINT", family: "integer", bits: 8, signed: true },
|
|
5
|
+
{ name: "INT", family: "integer", bits: 16, signed: true },
|
|
6
|
+
{ name: "DINT", family: "integer", bits: 32, signed: true },
|
|
7
|
+
{ name: "LINT", family: "integer", bits: 64, signed: true },
|
|
8
|
+
{ name: "USINT", family: "integer", bits: 8, signed: false },
|
|
9
|
+
{ name: "UINT", family: "integer", bits: 16, signed: false },
|
|
10
|
+
{ name: "UDINT", family: "integer", bits: 32, signed: false },
|
|
11
|
+
{ name: "ULINT", family: "integer", bits: 64, signed: false },
|
|
12
|
+
{ name: "BYTE", family: "bits", bits: 8, signed: false },
|
|
13
|
+
{ name: "WORD", family: "bits", bits: 16, signed: false },
|
|
14
|
+
{ name: "DWORD", family: "bits", bits: 32, signed: false },
|
|
15
|
+
{ name: "LWORD", family: "bits", bits: 64, signed: false },
|
|
16
|
+
{ name: "REAL", family: "real", bits: 32 },
|
|
17
|
+
{ name: "LREAL", family: "real", bits: 64 },
|
|
18
|
+
{ name: "TIME", family: "duration", bits: 32 },
|
|
19
|
+
{ name: "LTIME", family: "duration", bits: 64 },
|
|
20
|
+
{ name: "DATE", family: "date", bits: 32 },
|
|
21
|
+
{ name: "LDATE", family: "date", bits: 64 },
|
|
22
|
+
{ name: "TIME_OF_DAY", alias: "TOD", family: "timeOfDay", bits: 32 },
|
|
23
|
+
{ name: "LTIME_OF_DAY", alias: "LTOD", family: "timeOfDay", bits: 64 },
|
|
24
|
+
{ name: "DATE_AND_TIME", alias: "DT", family: "dateTime", bits: 32 },
|
|
25
|
+
{ name: "LDATE_AND_TIME", alias: "LDT", family: "dateTime", bits: 64 },
|
|
26
|
+
{ name: "CHAR", family: "char", bits: 8 },
|
|
27
|
+
{ name: "WCHAR", family: "char", bits: 16 },
|
|
28
|
+
{ name: "STRING", family: "string" },
|
|
29
|
+
{ name: "WSTRING", family: "string" }
|
|
30
|
+
];
|
|
31
|
+
var elementaryTypesByName = new Map(elementaryTypes.flatMap((type) => ("alias" in type) ? [
|
|
32
|
+
[type.name, type],
|
|
33
|
+
[type.alias, type]
|
|
34
|
+
] : [[type.name, type]]));
|
|
35
|
+
function elementaryType(name) {
|
|
36
|
+
return elementaryTypesByName.get(name);
|
|
37
|
+
}
|
|
38
|
+
var isIntegerValued = (type) => type?.family === "integer" || type?.family === "bits";
|
|
39
|
+
var integerLimits = {
|
|
40
|
+
8: { signed: [-128n, 127n], unsigned: [0n, 255n] },
|
|
41
|
+
16: { signed: [-32768n, 32767n], unsigned: [0n, 65535n] },
|
|
42
|
+
32: {
|
|
43
|
+
signed: [-2147483648n, 2147483647n],
|
|
44
|
+
unsigned: [0n, 4294967295n]
|
|
45
|
+
},
|
|
46
|
+
64: {
|
|
47
|
+
signed: [-9223372036854775808n, 9223372036854775807n],
|
|
48
|
+
unsigned: [0n, 18446744073709551615n]
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var integerRange = (layout) => integerLimits[layout.bits][layout.signed ? "signed" : "unsigned"];
|
|
52
|
+
|
|
53
|
+
// packages/iec/src/direct-addresses.ts
|
|
54
|
+
var sizePrefixes = new Map([
|
|
55
|
+
["X", elementaryType("BOOL")],
|
|
56
|
+
["B", elementaryType("BYTE")],
|
|
57
|
+
["W", elementaryType("WORD")],
|
|
58
|
+
["D", elementaryType("DWORD")],
|
|
59
|
+
["L", elementaryType("LWORD")]
|
|
60
|
+
]);
|
|
61
|
+
var sizePrefix = (bits) => [...sizePrefixes].find(([, type]) => type.bits === bits)?.[0];
|
|
62
|
+
var ioScalar = (type) => {
|
|
63
|
+
const scalar = elementaryType(type.trim().toUpperCase());
|
|
64
|
+
switch (scalar?.family) {
|
|
65
|
+
case "bool":
|
|
66
|
+
case "integer":
|
|
67
|
+
case "bits":
|
|
68
|
+
case "real":
|
|
69
|
+
return scalar;
|
|
70
|
+
case "duration":
|
|
71
|
+
case "date":
|
|
72
|
+
case "timeOfDay":
|
|
73
|
+
case "dateTime":
|
|
74
|
+
return scalar.bits === 32 ? scalar : undefined;
|
|
75
|
+
default:
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
function ioScalarBits(type) {
|
|
80
|
+
return ioScalar(type)?.bits;
|
|
81
|
+
}
|
|
82
|
+
var ioArray = (type) => {
|
|
83
|
+
const array = /^ARRAY\s*\[\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*\]\s*OF\s+(\w+)$/i.exec(type);
|
|
84
|
+
return array ? { element: array[3], length: Number(array[2]) - Number(array[1]) + 1 } : undefined;
|
|
85
|
+
};
|
|
86
|
+
export {
|
|
87
|
+
elementaryType,
|
|
88
|
+
elementaryTypes,
|
|
89
|
+
elementaryTypesByName,
|
|
90
|
+
integerRange,
|
|
91
|
+
ioArray,
|
|
92
|
+
ioScalar,
|
|
93
|
+
ioScalarBits,
|
|
94
|
+
isIntegerValued,
|
|
95
|
+
sizePrefix,
|
|
96
|
+
sizePrefixes
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
//# debugId=4E89C9555069E86664756E2164756E21
|
|
100
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/elementary-types.ts", "../src/direct-addresses.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"type IntegerBits = 8 | 16 | 32 | 64;\n\ntype ElementaryTypeShape =\n\t| { name: string; family: \"bool\"; bits: 1 }\n\t| { name: string; family: \"integer\"; bits: IntegerBits; signed: boolean }\n\t| { name: string; family: \"bits\"; bits: IntegerBits; signed: false }\n\t| { name: string; family: \"real\"; bits: 32 | 64 }\n\t| {\n\t\t\tname: string;\n\t\t\tfamily: \"duration\" | \"date\" | \"timeOfDay\" | \"dateTime\";\n\t\t\tbits: 32 | 64;\n\t\t\t/** The short name IEC also accepts, such as `TOD`. */\n\t\t\talias?: string;\n\t }\n\t| { name: string; family: \"char\"; bits: 8 | 16 }\n\t| { name: string; family: \"string\" };\n\n/**\n * The IEC 61131-3 elementary types under their full names, with the widths CODESYS gives them.\n * BOOL is one bit, as a direct address lays it out; STRING and WSTRING take their length from\n * each declaration.\n */\nexport const elementaryTypes = [\n\t{ name: \"BOOL\", family: \"bool\", bits: 1 },\n\t{ name: \"SINT\", family: \"integer\", bits: 8, signed: true },\n\t{ name: \"INT\", family: \"integer\", bits: 16, signed: true },\n\t{ name: \"DINT\", family: \"integer\", bits: 32, signed: true },\n\t{ name: \"LINT\", family: \"integer\", bits: 64, signed: true },\n\t{ name: \"USINT\", family: \"integer\", bits: 8, signed: false },\n\t{ name: \"UINT\", family: \"integer\", bits: 16, signed: false },\n\t{ name: \"UDINT\", family: \"integer\", bits: 32, signed: false },\n\t{ name: \"ULINT\", family: \"integer\", bits: 64, signed: false },\n\t{ name: \"BYTE\", family: \"bits\", bits: 8, signed: false },\n\t{ name: \"WORD\", family: \"bits\", bits: 16, signed: false },\n\t{ name: \"DWORD\", family: \"bits\", bits: 32, signed: false },\n\t{ name: \"LWORD\", family: \"bits\", bits: 64, signed: false },\n\t{ name: \"REAL\", family: \"real\", bits: 32 },\n\t{ name: \"LREAL\", family: \"real\", bits: 64 },\n\t{ name: \"TIME\", family: \"duration\", bits: 32 },\n\t{ name: \"LTIME\", family: \"duration\", bits: 64 },\n\t{ name: \"DATE\", family: \"date\", bits: 32 },\n\t{ name: \"LDATE\", family: \"date\", bits: 64 },\n\t{ name: \"TIME_OF_DAY\", alias: \"TOD\", family: \"timeOfDay\", bits: 32 },\n\t{ name: \"LTIME_OF_DAY\", alias: \"LTOD\", family: \"timeOfDay\", bits: 64 },\n\t{ name: \"DATE_AND_TIME\", alias: \"DT\", family: \"dateTime\", bits: 32 },\n\t{ name: \"LDATE_AND_TIME\", alias: \"LDT\", family: \"dateTime\", bits: 64 },\n\t{ name: \"CHAR\", family: \"char\", bits: 8 },\n\t{ name: \"WCHAR\", family: \"char\", bits: 16 },\n\t{ name: \"STRING\", family: \"string\" },\n\t{ name: \"WSTRING\", family: \"string\" },\n] as const satisfies readonly ElementaryTypeShape[];\n\nexport type ElementaryType = (typeof elementaryTypes)[number];\n\n/**\n * Every elementary type by each name a program may write for it, its alias included. Keys are\n * upper case: IEC names are case-insensitive, so callers upper-case what they read.\n */\nexport const elementaryTypesByName: ReadonlyMap<string, ElementaryType> =\n\tnew Map(\n\t\telementaryTypes.flatMap((type): [string, ElementaryType][] =>\n\t\t\t\"alias\" in type\n\t\t\t\t? [\n\t\t\t\t\t\t[type.name, type],\n\t\t\t\t\t\t[type.alias, type],\n\t\t\t\t\t]\n\t\t\t\t: [[type.name, type]],\n\t\t),\n\t);\n\n/** The elementary type a name or alias stands for; a full name always finds its own type. */\nexport function elementaryType<Name extends ElementaryType[\"name\"]>(\n\tname: Name,\n): Extract<ElementaryType, { name: Name }>;\nexport function elementaryType(name: string): ElementaryType | undefined;\nexport function elementaryType(name: string): ElementaryType | undefined {\n\treturn elementaryTypesByName.get(name);\n}\n\n/** Integers and bit strings: the types whose values are whole numbers in a fixed range. */\nexport type IntegerValuedType = Extract<\n\tElementaryType,\n\t{ family: \"integer\" | \"bits\" }\n>;\n\nexport const isIntegerValued = (\n\ttype: ElementaryType | undefined,\n): type is IntegerValuedType =>\n\ttype?.family === \"integer\" || type?.family === \"bits\";\n\n// Literal, so a range can be a type as well as a value.\nconst integerLimits = {\n\t8: { signed: [-128n, 127n], unsigned: [0n, 255n] },\n\t16: { signed: [-32_768n, 32_767n], unsigned: [0n, 65_535n] },\n\t32: {\n\t\tsigned: [-2_147_483_648n, 2_147_483_647n],\n\t\tunsigned: [0n, 4_294_967_295n],\n\t},\n\t64: {\n\t\tsigned: [-9_223_372_036_854_775_808n, 9_223_372_036_854_775_807n],\n\t\tunsigned: [0n, 18_446_744_073_709_551_615n],\n\t},\n} as const;\n\ninterface IntegerLayout {\n\tbits: IntegerBits;\n\tsigned: boolean;\n}\n\n/** The least and greatest value of an integer layout, as literal types. */\nexport type IntegerRange<Layout extends IntegerLayout> =\n\t(typeof integerLimits)[Layout[\"bits\"]][Layout[\"signed\"] extends true\n\t\t? \"signed\"\n\t\t: \"unsigned\"];\n\n/** The least and greatest value of an integer layout; bit strings are unsigned. */\nexport const integerRange = (\n\tlayout: IntegerLayout,\n): readonly [bigint, bigint] =>\n\tintegerLimits[layout.bits][layout.signed ? \"signed\" : \"unsigned\"];\n",
|
|
6
|
+
"import { type ElementaryType, elementaryType } from \"./elementary-types.ts\";\n\n/** BOOL or a bit string: the type of a location a direct address names. */\ntype LocationType = Extract<ElementaryType, { family: \"bool\" | \"bits\" }>;\n\n/**\n * Each size prefix of a direct address (`%IX0.0`, `%QB4`, `%IW8`, `%ID12`, `%IL16`) with the\n * type of the location it names, which carries its width.\n */\nexport const sizePrefixes: ReadonlyMap<string, LocationType> = new Map<\n\tstring,\n\tLocationType\n>([\n\t[\"X\", elementaryType(\"BOOL\")],\n\t[\"B\", elementaryType(\"BYTE\")],\n\t[\"W\", elementaryType(\"WORD\")],\n\t[\"D\", elementaryType(\"DWORD\")],\n\t[\"L\", elementaryType(\"LWORD\")],\n]);\n\n/** The size prefix of a location `bits` wide. */\nexport const sizePrefix = (bits: number) =>\n\t[...sizePrefixes].find(([, type]) => type.bits === bits)?.[0];\n\n/**\n * The type of a scalar a process image carries: BOOL, an integer, bit string or real, or one\n * of the 32-bit date and time types.\n */\nexport const ioScalar = (type: string) => {\n\tconst scalar = elementaryType(type.trim().toUpperCase());\n\tswitch (scalar?.family) {\n\t\tcase \"bool\":\n\t\tcase \"integer\":\n\t\tcase \"bits\":\n\t\tcase \"real\":\n\t\t\treturn scalar;\n\t\tcase \"duration\":\n\t\tcase \"date\":\n\t\tcase \"timeOfDay\":\n\t\tcase \"dateTime\":\n\t\t\treturn scalar.bits === 32 ? scalar : undefined;\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n};\n\nexport function ioScalarBits(type: string): number | undefined {\n\treturn ioScalar(type)?.bits;\n}\n\n/**\n * The element type and length of a one-dimensional `ARRAY[l..u] OF T`: a span of consecutive\n * scalars binds at its first element's address.\n */\nexport const ioArray = (type: string) => {\n\tconst array =\n\t\t/^ARRAY\\s*\\[\\s*(-?\\d+)\\s*\\.\\.\\s*(-?\\d+)\\s*\\]\\s*OF\\s+(\\w+)$/i.exec(type);\n\treturn array\n\t\t? { element: array[3]!, length: Number(array[2]) - Number(array[1]) + 1 }\n\t\t: undefined;\n};\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "4E89C9555069E86664756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/dist/st.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// packages/iec/src/st/lexical.ts
|
|
2
|
+
var lineCommentEnd = (source, start) => {
|
|
3
|
+
const length = source.slice(start).search(/[\r\n]/);
|
|
4
|
+
return length < 0 ? source.length : start + length;
|
|
5
|
+
};
|
|
6
|
+
var blockCommentEnd = (source, start) => {
|
|
7
|
+
let depth = 1;
|
|
8
|
+
let offset = start + 2;
|
|
9
|
+
while (offset < source.length && depth > 0) {
|
|
10
|
+
if (source.startsWith("(*", offset)) {
|
|
11
|
+
depth++;
|
|
12
|
+
offset += 2;
|
|
13
|
+
} else if (source.startsWith("*)", offset)) {
|
|
14
|
+
depth--;
|
|
15
|
+
offset += 2;
|
|
16
|
+
} else {
|
|
17
|
+
offset++;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return depth > 0 ? null : offset;
|
|
21
|
+
};
|
|
22
|
+
var stringEnd = (source, start) => {
|
|
23
|
+
const delimiter = source[start];
|
|
24
|
+
let offset = start + 1;
|
|
25
|
+
while (offset < source.length) {
|
|
26
|
+
if (source[offset] === "$") {
|
|
27
|
+
offset += 2;
|
|
28
|
+
} else if (source[offset++] === delimiter) {
|
|
29
|
+
return offset;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
var escapeString = (text, delimiter) => {
|
|
35
|
+
let escaped = "";
|
|
36
|
+
for (const character of text) {
|
|
37
|
+
const code = character.charCodeAt(0);
|
|
38
|
+
if (character === "$" || character === delimiter) {
|
|
39
|
+
escaped += `$${character}`;
|
|
40
|
+
} else if (code < 32 || code === 127) {
|
|
41
|
+
escaped += `$${code.toString(16).toUpperCase().padStart(delimiter === '"' ? 4 : 2, "0")}`;
|
|
42
|
+
} else {
|
|
43
|
+
escaped += character;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return escaped;
|
|
47
|
+
};
|
|
48
|
+
var directAddressEnd = (source, start) => {
|
|
49
|
+
const address = /^%[IQM][XBWDL]?(?:\d+(?:\.\d+)*|\*)/i.exec(source.slice(start))?.[0];
|
|
50
|
+
return address === undefined ? null : start + address.length;
|
|
51
|
+
};
|
|
52
|
+
var identifierPattern = /^[A-Za-z_][A-Za-z_0-9]*$/;
|
|
53
|
+
// packages/iec/src/st/vocabulary.ts
|
|
54
|
+
var objectKeywords = [
|
|
55
|
+
"PROGRAM",
|
|
56
|
+
"FUNCTION_BLOCK",
|
|
57
|
+
"FUNCTION",
|
|
58
|
+
"METHOD",
|
|
59
|
+
"PROPERTY",
|
|
60
|
+
"INTERFACE"
|
|
61
|
+
];
|
|
62
|
+
var globalSections = new Set(["VAR_GLOBAL", "VAR_CONFIG"]);
|
|
63
|
+
var variableSections = new Set([
|
|
64
|
+
...globalSections,
|
|
65
|
+
"VAR",
|
|
66
|
+
"VAR_INPUT",
|
|
67
|
+
"VAR_OUTPUT",
|
|
68
|
+
"VAR_IN_OUT",
|
|
69
|
+
"VAR_TEMP",
|
|
70
|
+
"VAR_STAT",
|
|
71
|
+
"VAR_INST",
|
|
72
|
+
"VAR_EXTERNAL",
|
|
73
|
+
"VAR_ACCESS",
|
|
74
|
+
"VAR_GENERIC"
|
|
75
|
+
]);
|
|
76
|
+
var modifiers = new Set([
|
|
77
|
+
"PUBLIC",
|
|
78
|
+
"PRIVATE",
|
|
79
|
+
"PROTECTED",
|
|
80
|
+
"INTERNAL",
|
|
81
|
+
"ABSTRACT",
|
|
82
|
+
"FINAL",
|
|
83
|
+
"OVERRIDE"
|
|
84
|
+
]);
|
|
85
|
+
var qualifiers = new Set([
|
|
86
|
+
"CONSTANT",
|
|
87
|
+
"RETAIN",
|
|
88
|
+
"NON_RETAIN",
|
|
89
|
+
"PERSISTENT"
|
|
90
|
+
]);
|
|
91
|
+
var keywords = new Set([
|
|
92
|
+
...objectKeywords,
|
|
93
|
+
...objectKeywords.map((keyword) => `END_${keyword}`),
|
|
94
|
+
...variableSections,
|
|
95
|
+
"END_VAR",
|
|
96
|
+
...modifiers,
|
|
97
|
+
...qualifiers,
|
|
98
|
+
"TYPE",
|
|
99
|
+
"END_TYPE",
|
|
100
|
+
"STRUCT",
|
|
101
|
+
"END_STRUCT",
|
|
102
|
+
"UNION",
|
|
103
|
+
"END_UNION",
|
|
104
|
+
"EXTENDS",
|
|
105
|
+
"IMPLEMENTS",
|
|
106
|
+
"IF",
|
|
107
|
+
"THEN",
|
|
108
|
+
"ELSIF",
|
|
109
|
+
"ELSE",
|
|
110
|
+
"END_IF",
|
|
111
|
+
"CASE",
|
|
112
|
+
"OF",
|
|
113
|
+
"END_CASE",
|
|
114
|
+
"FOR",
|
|
115
|
+
"TO",
|
|
116
|
+
"BY",
|
|
117
|
+
"DO",
|
|
118
|
+
"END_FOR",
|
|
119
|
+
"WHILE",
|
|
120
|
+
"END_WHILE",
|
|
121
|
+
"REPEAT",
|
|
122
|
+
"UNTIL",
|
|
123
|
+
"END_REPEAT",
|
|
124
|
+
"EXIT",
|
|
125
|
+
"CONTINUE",
|
|
126
|
+
"RETURN",
|
|
127
|
+
"AND",
|
|
128
|
+
"AND_THEN",
|
|
129
|
+
"OR",
|
|
130
|
+
"OR_ELSE",
|
|
131
|
+
"XOR",
|
|
132
|
+
"NOT",
|
|
133
|
+
"MOD",
|
|
134
|
+
"ARRAY",
|
|
135
|
+
"POINTER",
|
|
136
|
+
"REFERENCE",
|
|
137
|
+
"REF_TO",
|
|
138
|
+
"AT",
|
|
139
|
+
"TRUE",
|
|
140
|
+
"FALSE",
|
|
141
|
+
"THIS",
|
|
142
|
+
"SUPER"
|
|
143
|
+
]);
|
|
144
|
+
var reservedWords = new Set([
|
|
145
|
+
...keywords,
|
|
146
|
+
"FUNCTIONBLOCK",
|
|
147
|
+
"END_FUNCTIONBLOCK",
|
|
148
|
+
"ACTION",
|
|
149
|
+
"END_ACTION",
|
|
150
|
+
"CONFIGURATION",
|
|
151
|
+
"END_CONFIGURATION",
|
|
152
|
+
"RESOURCE",
|
|
153
|
+
"END_RESOURCE",
|
|
154
|
+
"TASK",
|
|
155
|
+
"WITH",
|
|
156
|
+
"SINGLE",
|
|
157
|
+
"INTERVAL",
|
|
158
|
+
"PRIORITY",
|
|
159
|
+
"READ_ONLY",
|
|
160
|
+
"READ_WRITE",
|
|
161
|
+
"PARAMS",
|
|
162
|
+
"JMP"
|
|
163
|
+
]);
|
|
164
|
+
var conditionalDirective = (pragma) => /^\{\s*(IF|ELSIF|ELSE|END_IF)\b/i.exec(pragma)?.[1]?.toUpperCase();
|
|
165
|
+
export {
|
|
166
|
+
blockCommentEnd,
|
|
167
|
+
conditionalDirective,
|
|
168
|
+
directAddressEnd,
|
|
169
|
+
escapeString,
|
|
170
|
+
globalSections,
|
|
171
|
+
identifierPattern,
|
|
172
|
+
keywords,
|
|
173
|
+
lineCommentEnd,
|
|
174
|
+
modifiers,
|
|
175
|
+
objectKeywords,
|
|
176
|
+
qualifiers,
|
|
177
|
+
reservedWords,
|
|
178
|
+
stringEnd,
|
|
179
|
+
variableSections
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
//# debugId=BA160C07C649F5B064756E2164756E21
|
|
183
|
+
//# sourceMappingURL=st.js.map
|
package/dist/st.js.map
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/st/lexical.ts", "../src/st/vocabulary.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/** Offset where a `//` comment starting at `start` ends: before the line break. */\nexport const lineCommentEnd = (source: string, start: number): number => {\n\tconst length = source.slice(start).search(/[\\r\\n]/);\n\treturn length < 0 ? source.length : start + length;\n};\n\n/** Offset just past a `(* *)` comment starting at `start`, or `null` when it never closes. Comments nest. */\nexport const blockCommentEnd = (\n\tsource: string,\n\tstart: number,\n): number | null => {\n\tlet depth = 1;\n\tlet offset = start + 2;\n\twhile (offset < source.length && depth > 0) {\n\t\tif (source.startsWith(\"(*\", offset)) {\n\t\t\tdepth++;\n\t\t\toffset += 2;\n\t\t} else if (source.startsWith(\"*)\", offset)) {\n\t\t\tdepth--;\n\t\t\toffset += 2;\n\t\t} else {\n\t\t\toffset++;\n\t\t}\n\t}\n\treturn depth > 0 ? null : offset;\n};\n\n/** Offset just past a `'` or `\"` string starting at `start`, or `null` when it never closes. */\nexport const stringEnd = (source: string, start: number): number | null => {\n\tconst delimiter = source[start];\n\tlet offset = start + 1;\n\twhile (offset < source.length) {\n\t\t// IEC strings escape characters (including quotes and $ itself) with $.\n\t\tif (source[offset] === \"$\") {\n\t\t\toffset += 2;\n\t\t} else if (source[offset++] === delimiter) {\n\t\t\treturn offset;\n\t\t}\n\t}\n\treturn null;\n};\n\n/**\n * `text` as it reads between the delimiters of an ST string literal, `'` for STRING and `\"` for\n * WSTRING: `$`, the delimiter and every control character escaped, the last as its code.\n */\nexport const escapeString = (text: string, delimiter: \"'\" | '\"') => {\n\tlet escaped = \"\";\n\tfor (const character of text) {\n\t\tconst code = character.charCodeAt(0);\n\t\tif (character === \"$\" || character === delimiter) {\n\t\t\tescaped += `$${character}`;\n\t\t} else if (code < 32 || code === 127) {\n\t\t\tescaped += `$${code\n\t\t\t\t.toString(16)\n\t\t\t\t.toUpperCase()\n\t\t\t\t.padStart(delimiter === '\"' ? 4 : 2, \"0\")}`;\n\t\t} else {\n\t\t\tescaped += character;\n\t\t}\n\t}\n\treturn escaped;\n};\n\n/** Offset just past a direct address such as `%IX0.0`, `%QW4` or `%I*` at `start`, or `null` when there is none. */\nexport const directAddressEnd = (\n\tsource: string,\n\tstart: number,\n): number | null => {\n\tconst address = /^%[IQM][XBWDL]?(?:\\d+(?:\\.\\d+)*|\\*)/i.exec(\n\t\tsource.slice(start),\n\t)?.[0];\n\treturn address === undefined ? null : start + address.length;\n};\n\n/** A complete IEC identifier: a letter or underscore, then letters, digits and underscores. */\nexport const identifierPattern = /^[A-Za-z_][A-Za-z_0-9]*$/;\n",
|
|
6
|
+
"/** Keywords that open a CODESYS object; each closes with `END_<keyword>`. */\nexport const objectKeywords = [\n\t\"PROGRAM\",\n\t\"FUNCTION_BLOCK\",\n\t\"FUNCTION\",\n\t\"METHOD\",\n\t\"PROPERTY\",\n\t\"INTERFACE\",\n] as const;\nexport type ObjectKeyword = (typeof objectKeywords)[number];\n\n/** Sections that make a file a global variable list rather than an object. */\nexport const globalSections = new Set([\"VAR_GLOBAL\", \"VAR_CONFIG\"]);\nexport const variableSections = new Set([\n\t...globalSections,\n\t\"VAR\",\n\t\"VAR_INPUT\",\n\t\"VAR_OUTPUT\",\n\t\"VAR_IN_OUT\",\n\t\"VAR_TEMP\",\n\t\"VAR_STAT\",\n\t\"VAR_INST\",\n\t\"VAR_EXTERNAL\",\n\t\"VAR_ACCESS\",\n\t\"VAR_GENERIC\",\n]);\n/** Access and inheritance modifiers written after an object keyword. */\nexport const modifiers = new Set([\n\t\"PUBLIC\",\n\t\"PRIVATE\",\n\t\"PROTECTED\",\n\t\"INTERNAL\",\n\t\"ABSTRACT\",\n\t\"FINAL\",\n\t\"OVERRIDE\",\n]);\n/** Qualifiers written after a variable section keyword. */\nexport const qualifiers = new Set([\n\t\"CONSTANT\",\n\t\"RETAIN\",\n\t\"NON_RETAIN\",\n\t\"PERSISTENT\",\n]);\n\n/** Words the Structured Text grammar gives a meaning. */\nexport const keywords = new Set([\n\t...objectKeywords,\n\t...objectKeywords.map((keyword) => `END_${keyword}`),\n\t...variableSections,\n\t\"END_VAR\",\n\t...modifiers,\n\t...qualifiers,\n\t\"TYPE\",\n\t\"END_TYPE\",\n\t\"STRUCT\",\n\t\"END_STRUCT\",\n\t\"UNION\",\n\t\"END_UNION\",\n\t\"EXTENDS\",\n\t\"IMPLEMENTS\",\n\t\"IF\",\n\t\"THEN\",\n\t\"ELSIF\",\n\t\"ELSE\",\n\t\"END_IF\",\n\t\"CASE\",\n\t\"OF\",\n\t\"END_CASE\",\n\t\"FOR\",\n\t\"TO\",\n\t\"BY\",\n\t\"DO\",\n\t\"END_FOR\",\n\t\"WHILE\",\n\t\"END_WHILE\",\n\t\"REPEAT\",\n\t\"UNTIL\",\n\t\"END_REPEAT\",\n\t\"EXIT\",\n\t\"CONTINUE\",\n\t\"RETURN\",\n\t\"AND\",\n\t\"AND_THEN\",\n\t\"OR\",\n\t\"OR_ELSE\",\n\t\"XOR\",\n\t\"NOT\",\n\t\"MOD\",\n\t\"ARRAY\",\n\t\"POINTER\",\n\t\"REFERENCE\",\n\t\"REF_TO\",\n\t\"AT\",\n\t\"TRUE\",\n\t\"FALSE\",\n\t\"THIS\",\n\t\"SUPER\",\n]);\n\n/**\n * Every word CODESYS reserves. Beyond the grammar keywords these are the export\n * format's ACTION, configuration and access-path keywords, Instruction List's\n * JMP, and the legacy FUNCTIONBLOCK spelling the importer still reads.\n */\nexport const reservedWords = new Set([\n\t...keywords,\n\t\"FUNCTIONBLOCK\",\n\t\"END_FUNCTIONBLOCK\",\n\t\"ACTION\",\n\t\"END_ACTION\",\n\t\"CONFIGURATION\",\n\t\"END_CONFIGURATION\",\n\t\"RESOURCE\",\n\t\"END_RESOURCE\",\n\t\"TASK\",\n\t\"WITH\",\n\t\"SINGLE\",\n\t\"INTERVAL\",\n\t\"PRIORITY\",\n\t\"READ_ONLY\",\n\t\"READ_WRITE\",\n\t\"PARAMS\",\n\t\"JMP\",\n]);\n\n/** The directive of a conditional-compilation pragma such as `{IF defined(DEBUG)}`. */\nexport const conditionalDirective = (pragma: string) =>\n\t/^\\{\\s*(IF|ELSIF|ELSE|END_IF)\\b/i.exec(pragma)?.[1]?.toUpperCase();\n"
|
|
7
|
+
],
|
|
8
|
+
"mappings": ";AACO,IAAM,iBAAiB,CAAC,QAAgB,UAA0B;AAAA,EACxE,MAAM,SAAS,OAAO,MAAM,KAAK,EAAE,OAAO,QAAQ;AAAA,EAClD,OAAO,SAAS,IAAI,OAAO,SAAS,QAAQ;AAAA;AAItC,IAAM,kBAAkB,CAC9B,QACA,UACmB;AAAA,EACnB,IAAI,QAAQ;AAAA,EACZ,IAAI,SAAS,QAAQ;AAAA,EACrB,OAAO,SAAS,OAAO,UAAU,QAAQ,GAAG;AAAA,IAC3C,IAAI,OAAO,WAAW,MAAM,MAAM,GAAG;AAAA,MACpC;AAAA,MACA,UAAU;AAAA,IACX,EAAO,SAAI,OAAO,WAAW,MAAM,MAAM,GAAG;AAAA,MAC3C;AAAA,MACA,UAAU;AAAA,IACX,EAAO;AAAA,MACN;AAAA;AAAA,EAEF;AAAA,EACA,OAAO,QAAQ,IAAI,OAAO;AAAA;AAIpB,IAAM,YAAY,CAAC,QAAgB,UAAiC;AAAA,EAC1E,MAAM,YAAY,OAAO;AAAA,EACzB,IAAI,SAAS,QAAQ;AAAA,EACrB,OAAO,SAAS,OAAO,QAAQ;AAAA,IAE9B,IAAI,OAAO,YAAY,KAAK;AAAA,MAC3B,UAAU;AAAA,IACX,EAAO,SAAI,OAAO,cAAc,WAAW;AAAA,MAC1C,OAAO;AAAA,IACR;AAAA,EACD;AAAA,EACA,OAAO;AAAA;AAOD,IAAM,eAAe,CAAC,MAAc,cAAyB;AAAA,EACnE,IAAI,UAAU;AAAA,EACd,WAAW,aAAa,MAAM;AAAA,IAC7B,MAAM,OAAO,UAAU,WAAW,CAAC;AAAA,IACnC,IAAI,cAAc,OAAO,cAAc,WAAW;AAAA,MACjD,WAAW,IAAI;AAAA,IAChB,EAAO,SAAI,OAAO,MAAM,SAAS,KAAK;AAAA,MACrC,WAAW,IAAI,KACb,SAAS,EAAE,EACX,YAAY,EACZ,SAAS,cAAc,MAAM,IAAI,GAAG,GAAG;AAAA,IAC1C,EAAO;AAAA,MACN,WAAW;AAAA;AAAA,EAEb;AAAA,EACA,OAAO;AAAA;AAID,IAAM,mBAAmB,CAC/B,QACA,UACmB;AAAA,EACnB,MAAM,UAAU,uCAAuC,KACtD,OAAO,MAAM,KAAK,CACnB,IAAI;AAAA,EACJ,OAAO,YAAY,YAAY,OAAO,QAAQ,QAAQ;AAAA;AAIhD,IAAM,oBAAoB;;AC3E1B,IAAM,iBAAiB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAIO,IAAM,iBAAiB,IAAI,IAAI,CAAC,cAAc,YAAY,CAAC;AAC3D,IAAM,mBAAmB,IAAI,IAAI;AAAA,EACvC,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAEM,IAAM,YAAY,IAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAEM,IAAM,aAAa,IAAI,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAGM,IAAM,WAAW,IAAI,IAAI;AAAA,EAC/B,GAAG;AAAA,EACH,GAAG,eAAe,IAAI,CAAC,YAAY,OAAO,SAAS;AAAA,EACnD,GAAG;AAAA,EACH;AAAA,EACA,GAAG;AAAA,EACH,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAOM,IAAM,gBAAgB,IAAI,IAAI;AAAA,EACpC,GAAG;AAAA,EACH;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD,CAAC;AAGM,IAAM,uBAAuB,CAAC,WACpC,kCAAkC,KAAK,MAAM,IAAI,IAAI,YAAY;",
|
|
9
|
+
"debugId": "BA160C07C649F5B064756E2164756E21",
|
|
10
|
+
"names": []
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
"name": "@tsplc/iec",
|
|
3
|
+
"version": "0.2.18",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "IEC 61131-3 vocabulary shared by the tsPLC toolchain: elementary types and Structured Text syntax.",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rip-technologies/tsplc.git",
|
|
10
|
+
"directory": "packages/iec"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./src/index.ts",
|
|
15
|
+
"bun": "./src/index.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./st": {
|
|
19
|
+
"types": "./src/st/index.ts",
|
|
20
|
+
"bun": "./src/st/index.ts",
|
|
21
|
+
"default": "./dist/st.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"src",
|
|
27
|
+
"!**/*.test.ts"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ElementaryType, elementaryType } from "./elementary-types.ts";
|
|
2
|
+
|
|
3
|
+
/** BOOL or a bit string: the type of a location a direct address names. */
|
|
4
|
+
type LocationType = Extract<ElementaryType, { family: "bool" | "bits" }>;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Each size prefix of a direct address (`%IX0.0`, `%QB4`, `%IW8`, `%ID12`, `%IL16`) with the
|
|
8
|
+
* type of the location it names, which carries its width.
|
|
9
|
+
*/
|
|
10
|
+
export const sizePrefixes: ReadonlyMap<string, LocationType> = new Map<
|
|
11
|
+
string,
|
|
12
|
+
LocationType
|
|
13
|
+
>([
|
|
14
|
+
["X", elementaryType("BOOL")],
|
|
15
|
+
["B", elementaryType("BYTE")],
|
|
16
|
+
["W", elementaryType("WORD")],
|
|
17
|
+
["D", elementaryType("DWORD")],
|
|
18
|
+
["L", elementaryType("LWORD")],
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/** The size prefix of a location `bits` wide. */
|
|
22
|
+
export const sizePrefix = (bits: number) =>
|
|
23
|
+
[...sizePrefixes].find(([, type]) => type.bits === bits)?.[0];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The type of a scalar a process image carries: BOOL, an integer, bit string or real, or one
|
|
27
|
+
* of the 32-bit date and time types.
|
|
28
|
+
*/
|
|
29
|
+
export const ioScalar = (type: string) => {
|
|
30
|
+
const scalar = elementaryType(type.trim().toUpperCase());
|
|
31
|
+
switch (scalar?.family) {
|
|
32
|
+
case "bool":
|
|
33
|
+
case "integer":
|
|
34
|
+
case "bits":
|
|
35
|
+
case "real":
|
|
36
|
+
return scalar;
|
|
37
|
+
case "duration":
|
|
38
|
+
case "date":
|
|
39
|
+
case "timeOfDay":
|
|
40
|
+
case "dateTime":
|
|
41
|
+
return scalar.bits === 32 ? scalar : undefined;
|
|
42
|
+
default:
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function ioScalarBits(type: string): number | undefined {
|
|
48
|
+
return ioScalar(type)?.bits;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The element type and length of a one-dimensional `ARRAY[l..u] OF T`: a span of consecutive
|
|
53
|
+
* scalars binds at its first element's address.
|
|
54
|
+
*/
|
|
55
|
+
export const ioArray = (type: string) => {
|
|
56
|
+
const array =
|
|
57
|
+
/^ARRAY\s*\[\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*\]\s*OF\s+(\w+)$/i.exec(type);
|
|
58
|
+
return array
|
|
59
|
+
? { element: array[3]!, length: Number(array[2]) - Number(array[1]) + 1 }
|
|
60
|
+
: undefined;
|
|
61
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
type IntegerBits = 8 | 16 | 32 | 64;
|
|
2
|
+
|
|
3
|
+
type ElementaryTypeShape =
|
|
4
|
+
| { name: string; family: "bool"; bits: 1 }
|
|
5
|
+
| { name: string; family: "integer"; bits: IntegerBits; signed: boolean }
|
|
6
|
+
| { name: string; family: "bits"; bits: IntegerBits; signed: false }
|
|
7
|
+
| { name: string; family: "real"; bits: 32 | 64 }
|
|
8
|
+
| {
|
|
9
|
+
name: string;
|
|
10
|
+
family: "duration" | "date" | "timeOfDay" | "dateTime";
|
|
11
|
+
bits: 32 | 64;
|
|
12
|
+
/** The short name IEC also accepts, such as `TOD`. */
|
|
13
|
+
alias?: string;
|
|
14
|
+
}
|
|
15
|
+
| { name: string; family: "char"; bits: 8 | 16 }
|
|
16
|
+
| { name: string; family: "string" };
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The IEC 61131-3 elementary types under their full names, with the widths CODESYS gives them.
|
|
20
|
+
* BOOL is one bit, as a direct address lays it out; STRING and WSTRING take their length from
|
|
21
|
+
* each declaration.
|
|
22
|
+
*/
|
|
23
|
+
export const elementaryTypes = [
|
|
24
|
+
{ name: "BOOL", family: "bool", bits: 1 },
|
|
25
|
+
{ name: "SINT", family: "integer", bits: 8, signed: true },
|
|
26
|
+
{ name: "INT", family: "integer", bits: 16, signed: true },
|
|
27
|
+
{ name: "DINT", family: "integer", bits: 32, signed: true },
|
|
28
|
+
{ name: "LINT", family: "integer", bits: 64, signed: true },
|
|
29
|
+
{ name: "USINT", family: "integer", bits: 8, signed: false },
|
|
30
|
+
{ name: "UINT", family: "integer", bits: 16, signed: false },
|
|
31
|
+
{ name: "UDINT", family: "integer", bits: 32, signed: false },
|
|
32
|
+
{ name: "ULINT", family: "integer", bits: 64, signed: false },
|
|
33
|
+
{ name: "BYTE", family: "bits", bits: 8, signed: false },
|
|
34
|
+
{ name: "WORD", family: "bits", bits: 16, signed: false },
|
|
35
|
+
{ name: "DWORD", family: "bits", bits: 32, signed: false },
|
|
36
|
+
{ name: "LWORD", family: "bits", bits: 64, signed: false },
|
|
37
|
+
{ name: "REAL", family: "real", bits: 32 },
|
|
38
|
+
{ name: "LREAL", family: "real", bits: 64 },
|
|
39
|
+
{ name: "TIME", family: "duration", bits: 32 },
|
|
40
|
+
{ name: "LTIME", family: "duration", bits: 64 },
|
|
41
|
+
{ name: "DATE", family: "date", bits: 32 },
|
|
42
|
+
{ name: "LDATE", family: "date", bits: 64 },
|
|
43
|
+
{ name: "TIME_OF_DAY", alias: "TOD", family: "timeOfDay", bits: 32 },
|
|
44
|
+
{ name: "LTIME_OF_DAY", alias: "LTOD", family: "timeOfDay", bits: 64 },
|
|
45
|
+
{ name: "DATE_AND_TIME", alias: "DT", family: "dateTime", bits: 32 },
|
|
46
|
+
{ name: "LDATE_AND_TIME", alias: "LDT", family: "dateTime", bits: 64 },
|
|
47
|
+
{ name: "CHAR", family: "char", bits: 8 },
|
|
48
|
+
{ name: "WCHAR", family: "char", bits: 16 },
|
|
49
|
+
{ name: "STRING", family: "string" },
|
|
50
|
+
{ name: "WSTRING", family: "string" },
|
|
51
|
+
] as const satisfies readonly ElementaryTypeShape[];
|
|
52
|
+
|
|
53
|
+
export type ElementaryType = (typeof elementaryTypes)[number];
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Every elementary type by each name a program may write for it, its alias included. Keys are
|
|
57
|
+
* upper case: IEC names are case-insensitive, so callers upper-case what they read.
|
|
58
|
+
*/
|
|
59
|
+
export const elementaryTypesByName: ReadonlyMap<string, ElementaryType> =
|
|
60
|
+
new Map(
|
|
61
|
+
elementaryTypes.flatMap((type): [string, ElementaryType][] =>
|
|
62
|
+
"alias" in type
|
|
63
|
+
? [
|
|
64
|
+
[type.name, type],
|
|
65
|
+
[type.alias, type],
|
|
66
|
+
]
|
|
67
|
+
: [[type.name, type]],
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
/** The elementary type a name or alias stands for; a full name always finds its own type. */
|
|
72
|
+
export function elementaryType<Name extends ElementaryType["name"]>(
|
|
73
|
+
name: Name,
|
|
74
|
+
): Extract<ElementaryType, { name: Name }>;
|
|
75
|
+
export function elementaryType(name: string): ElementaryType | undefined;
|
|
76
|
+
export function elementaryType(name: string): ElementaryType | undefined {
|
|
77
|
+
return elementaryTypesByName.get(name);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Integers and bit strings: the types whose values are whole numbers in a fixed range. */
|
|
81
|
+
export type IntegerValuedType = Extract<
|
|
82
|
+
ElementaryType,
|
|
83
|
+
{ family: "integer" | "bits" }
|
|
84
|
+
>;
|
|
85
|
+
|
|
86
|
+
export const isIntegerValued = (
|
|
87
|
+
type: ElementaryType | undefined,
|
|
88
|
+
): type is IntegerValuedType =>
|
|
89
|
+
type?.family === "integer" || type?.family === "bits";
|
|
90
|
+
|
|
91
|
+
// Literal, so a range can be a type as well as a value.
|
|
92
|
+
const integerLimits = {
|
|
93
|
+
8: { signed: [-128n, 127n], unsigned: [0n, 255n] },
|
|
94
|
+
16: { signed: [-32_768n, 32_767n], unsigned: [0n, 65_535n] },
|
|
95
|
+
32: {
|
|
96
|
+
signed: [-2_147_483_648n, 2_147_483_647n],
|
|
97
|
+
unsigned: [0n, 4_294_967_295n],
|
|
98
|
+
},
|
|
99
|
+
64: {
|
|
100
|
+
signed: [-9_223_372_036_854_775_808n, 9_223_372_036_854_775_807n],
|
|
101
|
+
unsigned: [0n, 18_446_744_073_709_551_615n],
|
|
102
|
+
},
|
|
103
|
+
} as const;
|
|
104
|
+
|
|
105
|
+
interface IntegerLayout {
|
|
106
|
+
bits: IntegerBits;
|
|
107
|
+
signed: boolean;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The least and greatest value of an integer layout, as literal types. */
|
|
111
|
+
export type IntegerRange<Layout extends IntegerLayout> =
|
|
112
|
+
(typeof integerLimits)[Layout["bits"]][Layout["signed"] extends true
|
|
113
|
+
? "signed"
|
|
114
|
+
: "unsigned"];
|
|
115
|
+
|
|
116
|
+
/** The least and greatest value of an integer layout; bit strings are unsigned. */
|
|
117
|
+
export const integerRange = (
|
|
118
|
+
layout: IntegerLayout,
|
|
119
|
+
): readonly [bigint, bigint] =>
|
|
120
|
+
integerLimits[layout.bits][layout.signed ? "signed" : "unsigned"];
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ioArray,
|
|
3
|
+
ioScalar,
|
|
4
|
+
ioScalarBits,
|
|
5
|
+
sizePrefix,
|
|
6
|
+
sizePrefixes,
|
|
7
|
+
} from "./direct-addresses.ts";
|
|
8
|
+
export {
|
|
9
|
+
type ElementaryType,
|
|
10
|
+
type IntegerRange,
|
|
11
|
+
type IntegerValuedType,
|
|
12
|
+
elementaryType,
|
|
13
|
+
elementaryTypes,
|
|
14
|
+
elementaryTypesByName,
|
|
15
|
+
integerRange,
|
|
16
|
+
isIntegerValued,
|
|
17
|
+
} from "./elementary-types.ts";
|
package/src/st/index.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
blockCommentEnd,
|
|
3
|
+
directAddressEnd,
|
|
4
|
+
escapeString,
|
|
5
|
+
identifierPattern,
|
|
6
|
+
lineCommentEnd,
|
|
7
|
+
stringEnd,
|
|
8
|
+
} from "./lexical";
|
|
9
|
+
export {
|
|
10
|
+
conditionalDirective,
|
|
11
|
+
globalSections,
|
|
12
|
+
keywords,
|
|
13
|
+
modifiers,
|
|
14
|
+
type ObjectKeyword,
|
|
15
|
+
objectKeywords,
|
|
16
|
+
qualifiers,
|
|
17
|
+
reservedWords,
|
|
18
|
+
variableSections,
|
|
19
|
+
} from "./vocabulary";
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Offset where a `//` comment starting at `start` ends: before the line break. */
|
|
2
|
+
export const lineCommentEnd = (source: string, start: number): number => {
|
|
3
|
+
const length = source.slice(start).search(/[\r\n]/);
|
|
4
|
+
return length < 0 ? source.length : start + length;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/** Offset just past a `(* *)` comment starting at `start`, or `null` when it never closes. Comments nest. */
|
|
8
|
+
export const blockCommentEnd = (
|
|
9
|
+
source: string,
|
|
10
|
+
start: number,
|
|
11
|
+
): number | null => {
|
|
12
|
+
let depth = 1;
|
|
13
|
+
let offset = start + 2;
|
|
14
|
+
while (offset < source.length && depth > 0) {
|
|
15
|
+
if (source.startsWith("(*", offset)) {
|
|
16
|
+
depth++;
|
|
17
|
+
offset += 2;
|
|
18
|
+
} else if (source.startsWith("*)", offset)) {
|
|
19
|
+
depth--;
|
|
20
|
+
offset += 2;
|
|
21
|
+
} else {
|
|
22
|
+
offset++;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return depth > 0 ? null : offset;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Offset just past a `'` or `"` string starting at `start`, or `null` when it never closes. */
|
|
29
|
+
export const stringEnd = (source: string, start: number): number | null => {
|
|
30
|
+
const delimiter = source[start];
|
|
31
|
+
let offset = start + 1;
|
|
32
|
+
while (offset < source.length) {
|
|
33
|
+
// IEC strings escape characters (including quotes and $ itself) with $.
|
|
34
|
+
if (source[offset] === "$") {
|
|
35
|
+
offset += 2;
|
|
36
|
+
} else if (source[offset++] === delimiter) {
|
|
37
|
+
return offset;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `text` as it reads between the delimiters of an ST string literal, `'` for STRING and `"` for
|
|
45
|
+
* WSTRING: `$`, the delimiter and every control character escaped, the last as its code.
|
|
46
|
+
*/
|
|
47
|
+
export const escapeString = (text: string, delimiter: "'" | '"') => {
|
|
48
|
+
let escaped = "";
|
|
49
|
+
for (const character of text) {
|
|
50
|
+
const code = character.charCodeAt(0);
|
|
51
|
+
if (character === "$" || character === delimiter) {
|
|
52
|
+
escaped += `$${character}`;
|
|
53
|
+
} else if (code < 32 || code === 127) {
|
|
54
|
+
escaped += `$${code
|
|
55
|
+
.toString(16)
|
|
56
|
+
.toUpperCase()
|
|
57
|
+
.padStart(delimiter === '"' ? 4 : 2, "0")}`;
|
|
58
|
+
} else {
|
|
59
|
+
escaped += character;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return escaped;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** Offset just past a direct address such as `%IX0.0`, `%QW4` or `%I*` at `start`, or `null` when there is none. */
|
|
66
|
+
export const directAddressEnd = (
|
|
67
|
+
source: string,
|
|
68
|
+
start: number,
|
|
69
|
+
): number | null => {
|
|
70
|
+
const address = /^%[IQM][XBWDL]?(?:\d+(?:\.\d+)*|\*)/i.exec(
|
|
71
|
+
source.slice(start),
|
|
72
|
+
)?.[0];
|
|
73
|
+
return address === undefined ? null : start + address.length;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** A complete IEC identifier: a letter or underscore, then letters, digits and underscores. */
|
|
77
|
+
export const identifierPattern = /^[A-Za-z_][A-Za-z_0-9]*$/;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** Keywords that open a CODESYS object; each closes with `END_<keyword>`. */
|
|
2
|
+
export const objectKeywords = [
|
|
3
|
+
"PROGRAM",
|
|
4
|
+
"FUNCTION_BLOCK",
|
|
5
|
+
"FUNCTION",
|
|
6
|
+
"METHOD",
|
|
7
|
+
"PROPERTY",
|
|
8
|
+
"INTERFACE",
|
|
9
|
+
] as const;
|
|
10
|
+
export type ObjectKeyword = (typeof objectKeywords)[number];
|
|
11
|
+
|
|
12
|
+
/** Sections that make a file a global variable list rather than an object. */
|
|
13
|
+
export const globalSections = new Set(["VAR_GLOBAL", "VAR_CONFIG"]);
|
|
14
|
+
export const variableSections = new Set([
|
|
15
|
+
...globalSections,
|
|
16
|
+
"VAR",
|
|
17
|
+
"VAR_INPUT",
|
|
18
|
+
"VAR_OUTPUT",
|
|
19
|
+
"VAR_IN_OUT",
|
|
20
|
+
"VAR_TEMP",
|
|
21
|
+
"VAR_STAT",
|
|
22
|
+
"VAR_INST",
|
|
23
|
+
"VAR_EXTERNAL",
|
|
24
|
+
"VAR_ACCESS",
|
|
25
|
+
"VAR_GENERIC",
|
|
26
|
+
]);
|
|
27
|
+
/** Access and inheritance modifiers written after an object keyword. */
|
|
28
|
+
export const modifiers = new Set([
|
|
29
|
+
"PUBLIC",
|
|
30
|
+
"PRIVATE",
|
|
31
|
+
"PROTECTED",
|
|
32
|
+
"INTERNAL",
|
|
33
|
+
"ABSTRACT",
|
|
34
|
+
"FINAL",
|
|
35
|
+
"OVERRIDE",
|
|
36
|
+
]);
|
|
37
|
+
/** Qualifiers written after a variable section keyword. */
|
|
38
|
+
export const qualifiers = new Set([
|
|
39
|
+
"CONSTANT",
|
|
40
|
+
"RETAIN",
|
|
41
|
+
"NON_RETAIN",
|
|
42
|
+
"PERSISTENT",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
/** Words the Structured Text grammar gives a meaning. */
|
|
46
|
+
export const keywords = new Set([
|
|
47
|
+
...objectKeywords,
|
|
48
|
+
...objectKeywords.map((keyword) => `END_${keyword}`),
|
|
49
|
+
...variableSections,
|
|
50
|
+
"END_VAR",
|
|
51
|
+
...modifiers,
|
|
52
|
+
...qualifiers,
|
|
53
|
+
"TYPE",
|
|
54
|
+
"END_TYPE",
|
|
55
|
+
"STRUCT",
|
|
56
|
+
"END_STRUCT",
|
|
57
|
+
"UNION",
|
|
58
|
+
"END_UNION",
|
|
59
|
+
"EXTENDS",
|
|
60
|
+
"IMPLEMENTS",
|
|
61
|
+
"IF",
|
|
62
|
+
"THEN",
|
|
63
|
+
"ELSIF",
|
|
64
|
+
"ELSE",
|
|
65
|
+
"END_IF",
|
|
66
|
+
"CASE",
|
|
67
|
+
"OF",
|
|
68
|
+
"END_CASE",
|
|
69
|
+
"FOR",
|
|
70
|
+
"TO",
|
|
71
|
+
"BY",
|
|
72
|
+
"DO",
|
|
73
|
+
"END_FOR",
|
|
74
|
+
"WHILE",
|
|
75
|
+
"END_WHILE",
|
|
76
|
+
"REPEAT",
|
|
77
|
+
"UNTIL",
|
|
78
|
+
"END_REPEAT",
|
|
79
|
+
"EXIT",
|
|
80
|
+
"CONTINUE",
|
|
81
|
+
"RETURN",
|
|
82
|
+
"AND",
|
|
83
|
+
"AND_THEN",
|
|
84
|
+
"OR",
|
|
85
|
+
"OR_ELSE",
|
|
86
|
+
"XOR",
|
|
87
|
+
"NOT",
|
|
88
|
+
"MOD",
|
|
89
|
+
"ARRAY",
|
|
90
|
+
"POINTER",
|
|
91
|
+
"REFERENCE",
|
|
92
|
+
"REF_TO",
|
|
93
|
+
"AT",
|
|
94
|
+
"TRUE",
|
|
95
|
+
"FALSE",
|
|
96
|
+
"THIS",
|
|
97
|
+
"SUPER",
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Every word CODESYS reserves. Beyond the grammar keywords these are the export
|
|
102
|
+
* format's ACTION, configuration and access-path keywords, Instruction List's
|
|
103
|
+
* JMP, and the legacy FUNCTIONBLOCK spelling the importer still reads.
|
|
104
|
+
*/
|
|
105
|
+
export const reservedWords = new Set([
|
|
106
|
+
...keywords,
|
|
107
|
+
"FUNCTIONBLOCK",
|
|
108
|
+
"END_FUNCTIONBLOCK",
|
|
109
|
+
"ACTION",
|
|
110
|
+
"END_ACTION",
|
|
111
|
+
"CONFIGURATION",
|
|
112
|
+
"END_CONFIGURATION",
|
|
113
|
+
"RESOURCE",
|
|
114
|
+
"END_RESOURCE",
|
|
115
|
+
"TASK",
|
|
116
|
+
"WITH",
|
|
117
|
+
"SINGLE",
|
|
118
|
+
"INTERVAL",
|
|
119
|
+
"PRIORITY",
|
|
120
|
+
"READ_ONLY",
|
|
121
|
+
"READ_WRITE",
|
|
122
|
+
"PARAMS",
|
|
123
|
+
"JMP",
|
|
124
|
+
]);
|
|
125
|
+
|
|
126
|
+
/** The directive of a conditional-compilation pragma such as `{IF defined(DEBUG)}`. */
|
|
127
|
+
export const conditionalDirective = (pragma: string) =>
|
|
128
|
+
/^\{\s*(IF|ELSIF|ELSE|END_IF)\b/i.exec(pragma)?.[1]?.toUpperCase();
|