@duet3d/monacotokens 3.7.0-alpha.13 → 3.7.0-alpha.14
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 +5 -55
- package/dist/gcodes/gcodes.json +1331 -1974
- package/dist/gcodes/index.d.ts +2 -24
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/providers.js +23 -156
- package/package.json +1 -1
- package/dist/objectmodel/axes.d.ts +0 -12
- package/dist/objectmodel/axes.js +0 -18
package/dist/gcodes/index.d.ts
CHANGED
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* One enumerated value of a parameter (e.g. "1" for G1 H meaning "Stop on endstop")
|
|
3
3
|
*/
|
|
4
4
|
export interface GcodeParameterValue {
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
* the same thing, e.g. M669 K accepts both the kinematics name and its legacy number ("coreXY" and "1").
|
|
8
|
-
*/
|
|
9
|
-
value: string | string[];
|
|
5
|
+
/** Literal value as it appears in source (e.g. "0", "1", "-1") */
|
|
6
|
+
value: string;
|
|
10
7
|
/** Human-readable meaning */
|
|
11
8
|
description: string;
|
|
12
9
|
}
|
|
@@ -34,23 +31,6 @@ export interface GcodeUnprecedentedParameter {
|
|
|
34
31
|
/** Description shown in the tooltip when this position is active */
|
|
35
32
|
description: string;
|
|
36
33
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Description of a parameter that exists once per machine axis (e.g. the per-axis speed limits of M203). Instead of
|
|
39
|
-
* listing X/Y/Z/U/... explicitly, a code carries a single `axisParameter` that the completion provider expands into
|
|
40
|
-
* one `GcodeParameter` per axis at runtime, so the suggestion list matches the machine that is actually connected.
|
|
41
|
-
*/
|
|
42
|
-
export interface GcodeAxisParameter {
|
|
43
|
-
/** Description shown for each generated axis parameter. The placeholder `{axis}` is replaced with the axis letter. */
|
|
44
|
-
description: string;
|
|
45
|
-
/** Optional enumeration of valid values, shared by every generated axis parameter */
|
|
46
|
-
values?: GcodeParameterValue[];
|
|
47
|
-
/**
|
|
48
|
-
* Whether the parameter list follows the live machine configuration. Defaults to `true`: expand to the axes the
|
|
49
|
-
* connected machine reports (falling back to the standard letters when no machine is connected). Set to `false`
|
|
50
|
-
* for axis-defining codes (e.g. M584, M669) that must offer every standard axis letter regardless of configuration.
|
|
51
|
-
*/
|
|
52
|
-
dynamic?: boolean;
|
|
53
|
-
}
|
|
54
34
|
/**
|
|
55
35
|
* Description of one G/M/T-code (RRF dialect)
|
|
56
36
|
*/
|
|
@@ -65,8 +45,6 @@ export interface GcodeInfo {
|
|
|
65
45
|
unprecedentedParameter?: GcodeUnprecedentedParameter;
|
|
66
46
|
/** Parameter letters this code understands */
|
|
67
47
|
parameters: GcodeParameter[];
|
|
68
|
-
/** Per-axis parameter expanded into one entry per configured axis at completion time (e.g. M203 speed limits) */
|
|
69
|
-
axisParameter?: GcodeAxisParameter;
|
|
70
48
|
/** If set, this code is deprecated; the string is shown as the reason */
|
|
71
49
|
deprecated?: string;
|
|
72
50
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,6 @@ export * from "./monaco-menu";
|
|
|
4
4
|
export * from "./gcodes";
|
|
5
5
|
export * from "./expressions";
|
|
6
6
|
export * from "./objectmodel/machine-context";
|
|
7
|
-
export * from "./objectmodel/axes";
|
|
8
7
|
export * from "./gcodes/local-variables";
|
|
9
8
|
export * from "./objectmodel/deprecations";
|
|
10
9
|
export * from "./objectmodel/enums";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,6 @@ export * from "./monaco-menu";
|
|
|
4
4
|
export * from "./gcodes";
|
|
5
5
|
export * from "./expressions";
|
|
6
6
|
export * from "./objectmodel/machine-context";
|
|
7
|
-
export * from "./objectmodel/axes";
|
|
8
7
|
export * from "./gcodes/local-variables";
|
|
9
8
|
export * from "./objectmodel/deprecations";
|
|
10
9
|
export * from "./objectmodel/enums";
|
package/dist/providers.js
CHANGED
|
@@ -1,133 +1,12 @@
|
|
|
1
|
-
import { gcodeData, findGcode
|
|
1
|
+
import { gcodeData, findGcode } from "./gcodes";
|
|
2
2
|
import { expressionData } from "./expressions";
|
|
3
3
|
import { getMachineContext } from "./objectmodel/machine-context";
|
|
4
|
-
import { ALL_AXIS_LETTERS, DEFAULT_AXIS_LETTERS } from "./objectmodel/axes";
|
|
5
4
|
import { getLocalVariables } from "./gcodes/local-variables";
|
|
6
5
|
import { getMemberDeprecation, getPathDeprecation } from "./objectmodel/deprecations";
|
|
7
6
|
import { getEnumValuesForPath } from "./objectmodel/enums";
|
|
8
7
|
// Re-export the runtime-context helpers so consumers (Vue DWC, React DuetWebUI, ...) can install a context
|
|
9
8
|
// without adding a separate import path
|
|
10
9
|
export { getMachineContext, onMachineContextChange } from "./objectmodel/machine-context";
|
|
11
|
-
/**
|
|
12
|
-
* Expand a code's `axisParameter` into one concrete `GcodeParameter` per axis letter. Dynamic parameters (the
|
|
13
|
-
* default) follow the connected machine's configured, visible axes and fall back to the standard letters when no
|
|
14
|
-
* machine is connected; non-dynamic parameters always offer the standard letters, for axis-defining codes such as
|
|
15
|
-
* M584/M669 that may reference axes which do not exist yet.
|
|
16
|
-
*/
|
|
17
|
-
function expandAxisParameter(axisParameter) {
|
|
18
|
-
let letters = DEFAULT_AXIS_LETTERS;
|
|
19
|
-
if (axisParameter.dynamic !== false) {
|
|
20
|
-
const axes = getMachineContext()?.model?.move?.axes;
|
|
21
|
-
const configured = axes
|
|
22
|
-
? axes.filter(a => !!a && a.visible !== false && typeof a.letter === "string" && a.letter.length === 1).map(a => a.letter)
|
|
23
|
-
: [];
|
|
24
|
-
if (configured.length > 0) {
|
|
25
|
-
letters = configured;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return letters.map(letter => ({
|
|
29
|
-
letter,
|
|
30
|
-
description: axisParameter.description.replace(/\{axis\}/g, letter),
|
|
31
|
-
values: axisParameter.values
|
|
32
|
-
}));
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Resolve a static code entry against the live machine context, expanding any `axisParameter` into concrete
|
|
36
|
-
* per-axis parameters placed ahead of the fixed parameters. Returns the entry unchanged when it has no axis
|
|
37
|
-
* parameter, so every caller can treat the result like a plain `GcodeInfo`.
|
|
38
|
-
*/
|
|
39
|
-
function findGcode(code) {
|
|
40
|
-
const info = findGcodeStatic(code);
|
|
41
|
-
if (!info || !info.axisParameter) {
|
|
42
|
-
return info;
|
|
43
|
-
}
|
|
44
|
-
return { ...info, parameters: [...expandAxisParameter(info.axisParameter), ...info.parameters] };
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Canonicalise a parameter/axis token to the letter RepRapFirmware actually uses. RRF folds bare command
|
|
48
|
-
* letters to upper case, so `a` addresses axis `A`; a leading apostrophe escapes a lower-case axis, so `'a`
|
|
49
|
-
* addresses axis `a`. The token passed in may carry that leading apostrophe.
|
|
50
|
-
*/
|
|
51
|
-
function canonicalAxisLetter(token) {
|
|
52
|
-
return token.startsWith("'") ? token.slice(1).toLowerCase() : token.toUpperCase();
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Render a canonical parameter letter the way it must be written in G-code: a lower-case axis needs the leading
|
|
56
|
-
* apostrophe (otherwise RRF would fold it to upper case), every other letter is written as-is.
|
|
57
|
-
*/
|
|
58
|
-
function gcodeAxisToken(letter) {
|
|
59
|
-
return /[a-z]/.test(letter) ? "'" + letter : letter;
|
|
60
|
-
}
|
|
61
|
-
/** Build the per-axis parameter for one axis letter from a code's axisParameter, or undefined when the letter is not a supported axis or the code has no axis parameter. */
|
|
62
|
-
function makeAxisParameter(info, letter) {
|
|
63
|
-
if (!info.axisParameter || !ALL_AXIS_LETTERS.includes(letter)) {
|
|
64
|
-
return undefined;
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
letter,
|
|
68
|
-
description: info.axisParameter.description.replace(/\{axis\}/g, letter),
|
|
69
|
-
values: info.axisParameter.values
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Resolve a single parameter for hover and signature use from a canonical letter (see {@link canonicalAxisLetter}).
|
|
74
|
-
* Matches a fixed parameter or an axis already expanded for the connected machine, and otherwise builds the
|
|
75
|
-
* parameter on the fly for any supported axis letter - so hovering or typing an axis the machine is not configured
|
|
76
|
-
* with still surfaces its documentation. This is wider than the completion suggestion list and the parameter
|
|
77
|
-
* summary, which only offer the configured axes.
|
|
78
|
-
*/
|
|
79
|
-
function lookupParameter(info, letter) {
|
|
80
|
-
const found = info.parameters.find(p => p.letter === letter);
|
|
81
|
-
return found ?? makeAxisParameter(info, letter);
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Collect the canonical parameter letters present at the top level of a code's argument segment, in order. Letters
|
|
85
|
-
* inside "..." strings or {...} expressions are skipped because they belong to a value, not to a parameter - e.g.
|
|
86
|
-
* the `g` in `M98 P"check-filament.g"` and any argument passed on to a macro must not be mistaken for parameters.
|
|
87
|
-
* A leading apostrophe is honoured so a lower-case axis (`'a`) stays distinct from the upper-case letter.
|
|
88
|
-
*/
|
|
89
|
-
function scanParameterLetters(segment) {
|
|
90
|
-
const letters = [];
|
|
91
|
-
let inString = false;
|
|
92
|
-
let braceDepth = 0;
|
|
93
|
-
for (let i = 0; i < segment.length; i++) {
|
|
94
|
-
const ch = segment[i];
|
|
95
|
-
if (inString) {
|
|
96
|
-
if (ch === "\"") {
|
|
97
|
-
inString = false;
|
|
98
|
-
}
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (ch === "\"") {
|
|
102
|
-
inString = true;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (ch === "{") {
|
|
106
|
-
braceDepth++;
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (ch === "}") {
|
|
110
|
-
if (braceDepth > 0) {
|
|
111
|
-
braceDepth--;
|
|
112
|
-
}
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
if (braceDepth > 0) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (ch === ";") {
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
if (/[A-Za-z]/.test(ch)) {
|
|
122
|
-
const prev = i > 0 ? segment[i - 1] : "";
|
|
123
|
-
const next = i + 1 < segment.length ? segment[i + 1] : "";
|
|
124
|
-
if (!/[A-Za-z]/.test(prev) && !/[A-Za-z]/.test(next)) {
|
|
125
|
-
letters.push(canonicalAxisLetter(prev === "'" ? prev + ch : ch));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return letters;
|
|
130
|
-
}
|
|
131
10
|
/**
|
|
132
11
|
* Find the enclosing function call (if any) for the cursor position. Walks back from the end of `beforeCursor`
|
|
133
12
|
* keeping track of paren depth so that `max(a, min(b,|` correctly reports `min` with argIndex 1, not `max`.
|
|
@@ -602,8 +481,7 @@ function findParameterAtCursor(line, codeEndColZero, cursorColOne) {
|
|
|
602
481
|
if (!/[A-Za-z]/.test(prev) && !/[A-Za-z]/.test(next)) {
|
|
603
482
|
const valRange = findParameterValueRange(line, i);
|
|
604
483
|
if (cursorColOne >= valRange.startCol && cursorColOne < valRange.endCol) {
|
|
605
|
-
|
|
606
|
-
return { letterColZero: i, letter: canonicalAxisLetter(prev === "'" ? prev + ch : ch) };
|
|
484
|
+
return { letterColZero: i, letter: ch };
|
|
607
485
|
}
|
|
608
486
|
i = valRange.endCol - 1;
|
|
609
487
|
continue;
|
|
@@ -724,8 +602,7 @@ function buildParameterDoc(info, p) {
|
|
|
724
602
|
if (p.values && p.values.length > 0) {
|
|
725
603
|
md += "\n\n**Values:**";
|
|
726
604
|
for (const v of p.values) {
|
|
727
|
-
|
|
728
|
-
md += `\n- ${literals} - ${v.description}`;
|
|
605
|
+
md += `\n- \`${v.value}\` - ${v.description}`;
|
|
729
606
|
}
|
|
730
607
|
}
|
|
731
608
|
const deprecationNote = p.deprecated ?? info.deprecated;
|
|
@@ -985,16 +862,21 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
985
862
|
// just typed it, Monaco should show nothing (so the widget auto-closes) rather than list
|
|
986
863
|
// the very letter that was just typed as the only match
|
|
987
864
|
const fullTail = lineContent.substring(code.startColumn - 1 + code.code.length);
|
|
988
|
-
const used = new Set(
|
|
865
|
+
const used = new Set();
|
|
866
|
+
const re = /(?<![A-Za-z])[A-Za-z](?![A-Za-z])/g;
|
|
867
|
+
let m;
|
|
868
|
+
while ((m = re.exec(fullTail)) !== null) {
|
|
869
|
+
used.add(m[0].toUpperCase());
|
|
870
|
+
}
|
|
989
871
|
const triggerHints = { id: "editor.action.triggerParameterHints", title: "Trigger Parameter Hints" };
|
|
990
872
|
const suggestions = info.parameters
|
|
991
|
-
.filter(p => !used.has(p.letter))
|
|
873
|
+
.filter(p => !used.has(p.letter.toUpperCase()))
|
|
992
874
|
.map(p => ({
|
|
993
|
-
label: { label:
|
|
875
|
+
label: { label: p.letter, description: p.description },
|
|
994
876
|
kind: monacoInstance.languages.CompletionItemKind.Property,
|
|
995
877
|
detail: p.description,
|
|
996
878
|
documentation: md(buildParameterDoc(info, p)),
|
|
997
|
-
insertText:
|
|
879
|
+
insertText: p.letter,
|
|
998
880
|
range,
|
|
999
881
|
// Re-open the signature help tooltip after accepting a parameter letter (Monaco otherwise
|
|
1000
882
|
// closes parameter hints when any completion item is accepted without a command)
|
|
@@ -1121,7 +1003,7 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
1121
1003
|
}
|
|
1122
1004
|
for (const p of info.parameters) {
|
|
1123
1005
|
const start = label.length + 1;
|
|
1124
|
-
label += " " +
|
|
1006
|
+
label += " " + p.letter;
|
|
1125
1007
|
parameters.push({
|
|
1126
1008
|
label: [start, label.length],
|
|
1127
1009
|
documentation: md(buildParameterDoc(info, p))
|
|
@@ -1131,31 +1013,18 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
1131
1013
|
// for codes with a unprecedentedParameter, sit on slot 0 while the user is typing that value (no parameter letter typed yet)
|
|
1132
1014
|
const tail = tailForDismissal;
|
|
1133
1015
|
let activeParameter = -1;
|
|
1134
|
-
const seen =
|
|
1016
|
+
const seen = tail.match(/(?<![A-Za-z])[A-Za-z](?![A-Za-z])/g);
|
|
1135
1017
|
const unprecedentedOffset = info.unprecedentedParameter ? 1 : 0;
|
|
1136
|
-
if (seen.length > 0) {
|
|
1137
|
-
const last = seen[seen.length - 1];
|
|
1138
|
-
const idx = info.parameters.findIndex(p => p.letter === last);
|
|
1018
|
+
if (seen && seen.length > 0) {
|
|
1019
|
+
const last = seen[seen.length - 1].toUpperCase();
|
|
1020
|
+
const idx = info.parameters.findIndex(p => p.letter.toUpperCase() === last);
|
|
1139
1021
|
if (idx >= 0) {
|
|
1140
1022
|
activeParameter = idx + unprecedentedOffset;
|
|
1141
1023
|
}
|
|
1142
1024
|
else {
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1146
|
-
const axisParam = makeAxisParameter(info, last);
|
|
1147
|
-
if (axisParam) {
|
|
1148
|
-
const start = label.length + 1;
|
|
1149
|
-
label += " " + gcodeAxisToken(axisParam.letter);
|
|
1150
|
-
parameters.push({
|
|
1151
|
-
label: [start, label.length],
|
|
1152
|
-
documentation: md(buildParameterDoc(info, axisParam))
|
|
1153
|
-
});
|
|
1154
|
-
activeParameter = parameters.length - 1;
|
|
1155
|
-
}
|
|
1156
|
-
else {
|
|
1157
|
-
return null;
|
|
1158
|
-
}
|
|
1025
|
+
// User typed a letter that isn't a documented parameter for this code - hide the popup
|
|
1026
|
+
// rather than falling back to the generic summary view, which would be misleading
|
|
1027
|
+
return null;
|
|
1159
1028
|
}
|
|
1160
1029
|
}
|
|
1161
1030
|
else if (info.unprecedentedParameter) {
|
|
@@ -1227,7 +1096,7 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
1227
1096
|
const info = findGcode(enclosingCode.code);
|
|
1228
1097
|
const paramAtCursor = info ? findParameterAtCursor(lineContent, enclosingCode.startColumn + enclosingCode.code.length - 1, position.column) : null;
|
|
1229
1098
|
if (info && paramAtCursor) {
|
|
1230
|
-
const param =
|
|
1099
|
+
const param = info.parameters.find(p => p.letter.toUpperCase() === paramAtCursor.letter.toUpperCase());
|
|
1231
1100
|
if (param) {
|
|
1232
1101
|
const valueRange = findParameterValueRange(lineContent, paramAtCursor.letterColZero);
|
|
1233
1102
|
return {
|
|
@@ -1278,7 +1147,7 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
1278
1147
|
// inside `1:2:3` of `E1:2:3`). This covers hovers that don't land on the letter itself
|
|
1279
1148
|
const paramAtCursor = findParameterAtCursor(lineContent, enclosing.startColumn + enclosing.code.length - 1, position.column);
|
|
1280
1149
|
if (info && paramAtCursor) {
|
|
1281
|
-
const param =
|
|
1150
|
+
const param = info.parameters.find(p => p.letter.toUpperCase() === paramAtCursor.letter.toUpperCase());
|
|
1282
1151
|
if (param) {
|
|
1283
1152
|
const valueRange = findParameterValueRange(lineContent, paramAtCursor.letterColZero);
|
|
1284
1153
|
return {
|
|
@@ -1290,9 +1159,7 @@ export function registerProvidersFor(monacoInstance, languageId) {
|
|
|
1290
1159
|
// Fallback: hovered word starts with a parameter letter (e.g. bare `X` in `M84 X Y Z`, where
|
|
1291
1160
|
// the letter has no value after it so findParameterAtCursor may stop before reaching it)
|
|
1292
1161
|
if (info && /^[A-Za-z]/.test(word.word)) {
|
|
1293
|
-
|
|
1294
|
-
const escaped = word.startColumn >= 2 && lineContent[word.startColumn - 2] === "'";
|
|
1295
|
-
const param = lookupParameter(info, canonicalAxisLetter(escaped ? "'" + firstLetter : firstLetter));
|
|
1162
|
+
const param = info.parameters.find(p => p.letter.toUpperCase() === firstLetter.toUpperCase());
|
|
1296
1163
|
if (param) {
|
|
1297
1164
|
const valueRange = findParameterValueRange(lineContent, word.startColumn - 1);
|
|
1298
1165
|
return {
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Supported axis letters, mirroring `Axis.Letters` in DuetAPI (DuetSoftwareFramework). This is the canonical set
|
|
3
|
-
* of identifiers RepRapFirmware accepts for an axis. Kept in sync by hand - update it if DuetAPI gains new letters.
|
|
4
|
-
*/
|
|
5
|
-
export declare const ALL_AXIS_LETTERS: readonly string[];
|
|
6
|
-
/**
|
|
7
|
-
* Axis letters offered by completion when the live machine configuration is unavailable (no machine connected) or
|
|
8
|
-
* when an axis parameter is non-dynamic (axis-defining codes such as M584 that may reference axes which do not
|
|
9
|
-
* exist yet). Restricted to the three axes every standard machine has; additional axes (U, V, W, ...) are surfaced
|
|
10
|
-
* dynamically from the connected machine's object model, and any axis can still be typed manually.
|
|
11
|
-
*/
|
|
12
|
-
export declare const DEFAULT_AXIS_LETTERS: readonly string[];
|
package/dist/objectmodel/axes.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Supported axis letters, mirroring `Axis.Letters` in DuetAPI (DuetSoftwareFramework). This is the canonical set
|
|
3
|
-
* of identifiers RepRapFirmware accepts for an axis. Kept in sync by hand - update it if DuetAPI gains new letters.
|
|
4
|
-
*/
|
|
5
|
-
export const ALL_AXIS_LETTERS = [
|
|
6
|
-
"X", "Y", "Z",
|
|
7
|
-
"U", "V", "W",
|
|
8
|
-
"A", "B", "C", "D",
|
|
9
|
-
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
|
10
|
-
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
|
|
11
|
-
];
|
|
12
|
-
/**
|
|
13
|
-
* Axis letters offered by completion when the live machine configuration is unavailable (no machine connected) or
|
|
14
|
-
* when an axis parameter is non-dynamic (axis-defining codes such as M584 that may reference axes which do not
|
|
15
|
-
* exist yet). Restricted to the three axes every standard machine has; additional axes (U, V, W, ...) are surfaced
|
|
16
|
-
* dynamically from the connected machine's object model, and any axis can still be typed manually.
|
|
17
|
-
*/
|
|
18
|
-
export const DEFAULT_AXIS_LETTERS = ["X", "Y", "Z"];
|