wesl 0.7.26 → 0.7.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +283 -147
  3. package/dist/index.js +1765 -1143
  4. package/package.json +2 -2
  5. package/src/AbstractElems.ts +264 -82
  6. package/src/ClickableError.ts +8 -1
  7. package/src/Linker.ts +63 -67
  8. package/src/LinkerUtil.ts +141 -7
  9. package/src/LowerAndEmit.ts +660 -304
  10. package/src/Mangler.ts +1 -1
  11. package/src/ModuleResolver.ts +15 -4
  12. package/src/ParseWESL.ts +21 -33
  13. package/src/SrcMap.ts +7 -19
  14. package/src/StandardTypes.ts +1 -1
  15. package/src/WeslDevice.ts +1 -0
  16. package/src/debug/ASTtoString.ts +92 -76
  17. package/src/index.ts +1 -1
  18. package/src/parse/AttachComments.ts +289 -0
  19. package/src/parse/ExpressionUtil.ts +3 -3
  20. package/src/parse/Keywords.ts +1 -1
  21. package/src/parse/ParseAttribute.ts +49 -71
  22. package/src/parse/ParseCall.ts +3 -4
  23. package/src/parse/ParseControlFlow.ts +100 -56
  24. package/src/parse/ParseDirective.ts +9 -8
  25. package/src/parse/ParseDoBlock.ts +63 -0
  26. package/src/parse/ParseExpression.ts +11 -10
  27. package/src/parse/ParseFn.ts +11 -33
  28. package/src/parse/ParseGlobalVar.ts +36 -27
  29. package/src/parse/ParseIdent.ts +4 -5
  30. package/src/parse/ParseLocalVar.ts +16 -12
  31. package/src/parse/ParseLoop.ts +76 -39
  32. package/src/parse/ParseModule.ts +65 -19
  33. package/src/parse/ParseSimpleStatement.ts +112 -66
  34. package/src/parse/ParseStatement.ts +40 -67
  35. package/src/parse/ParseStruct.ts +8 -22
  36. package/src/parse/ParseType.ts +10 -13
  37. package/src/parse/ParseUtil.ts +26 -12
  38. package/src/parse/ParseValueDeclaration.ts +18 -31
  39. package/src/parse/ParseWesl.ts +11 -14
  40. package/src/parse/ParsingContext.ts +11 -9
  41. package/src/parse/WeslStream.ts +138 -121
  42. package/src/parse/stream/RegexMatchers.ts +45 -0
  43. package/src/parse/stream/WeslLexer.ts +249 -0
  44. package/src/test/BevyLink.test.ts +18 -11
  45. package/src/test/ConditionalElif.test.ts +4 -2
  46. package/src/test/DeclCommentEmit.test.ts +122 -0
  47. package/src/test/DoBlock.test.ts +164 -0
  48. package/src/test/FilterValidElements.test.ts +4 -6
  49. package/src/test/Linker.test.ts +23 -7
  50. package/src/test/Mangling.test.ts +8 -4
  51. package/src/test/ParseComments.test.ts +234 -6
  52. package/src/test/ParseConditionsV2.test.ts +47 -175
  53. package/src/test/ParseElifV2.test.ts +12 -33
  54. package/src/test/ParseErrorV2.test.ts +0 -0
  55. package/src/test/ParseWeslV2.test.ts +247 -626
  56. package/src/test/StatementEmit.test.ts +143 -0
  57. package/src/test/StripWesl.ts +24 -3
  58. package/src/test/TestLink.ts +19 -3
  59. package/src/test/TestUtil.ts +18 -8
  60. package/src/test/Tokenizer.test.ts +96 -0
  61. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +10 -42
  62. package/src/RawEmit.ts +0 -103
  63. package/src/Reflection.ts +0 -336
  64. package/src/TransformBindingStructs.ts +0 -320
  65. package/src/parse/ContentsHelpers.ts +0 -70
  66. package/src/parse/stream/CachingStream.ts +0 -48
  67. package/src/parse/stream/MatchersStream.ts +0 -85
@@ -0,0 +1,143 @@
1
+ import { expect, test } from "vitest";
2
+ import { linkTest } from "./TestUtil.ts";
3
+ import { expectTrimmedMatch } from "./TrimmedMatch.ts";
4
+
5
+ // Statements are emitted structurally from typed AST fields (not by copying
6
+ // source text), so these tests lock the canonical layout and confirm that
7
+ // comments attached to statements survive linking.
8
+
9
+ test("linked WGSL keeps leading and trailing body comments", async () => {
10
+ const src = `
11
+ fn main() {
12
+ // leading comment
13
+ let x = 1; // trailing comment
14
+ let y = x;
15
+ }
16
+ `;
17
+ const result = await linkTest(src);
18
+ expect(result).toContain("// leading comment");
19
+ expect(result).toContain("// trailing comment");
20
+ // leading comment on its own line above the statement, trailing kept inline
21
+ expectTrimmedMatch(result, src);
22
+ });
23
+
24
+ test("linked WGSL keeps a comment in an otherwise empty block", async () => {
25
+ const src = `
26
+ fn foo() {
27
+ // fooImpl
28
+ }
29
+ `;
30
+ const result = await linkTest(src);
31
+ expect(result).toContain("// fooImpl");
32
+ expectTrimmedMatch(result, src);
33
+ });
34
+
35
+ test("keeps attributes on compound statement bodies", async () => {
36
+ const src = `
37
+ fn main() {
38
+ for (var i = 0; i < 4; i++) @diagnostic(off, derivative_uniformity) {
39
+ let x = i;
40
+ }
41
+ switch 0 @diagnostic(off, derivative_uniformity) {
42
+ default: {
43
+ let y = 1;
44
+ }
45
+ }
46
+ }
47
+ `;
48
+ const result = await linkTest(src);
49
+ expectTrimmedMatch(result, src);
50
+ });
51
+
52
+ test("structural emit of control flow", async () => {
53
+ const src = `
54
+ fn main() {
55
+ var i = 0;
56
+ if i < 10 {
57
+ i = i + 1;
58
+ } else if i < 20 {
59
+ i += 2;
60
+ } else {
61
+ i = 0;
62
+ }
63
+ for (var j = 0; j < 4; j++) {
64
+ i = i + j;
65
+ }
66
+ while i > 0 {
67
+ i = i - 1;
68
+ }
69
+ }
70
+ `;
71
+ const result = await linkTest(src);
72
+ expectTrimmedMatch(result, src);
73
+ });
74
+
75
+ test("structural emit of loop, continuing, and switch", async () => {
76
+ const src = `
77
+ fn main() {
78
+ var i = 0;
79
+ loop {
80
+ i = i + 1;
81
+ continuing {
82
+ break if i > 100;
83
+ }
84
+ }
85
+ switch i {
86
+ case 0, 1: {
87
+ i = 5;
88
+ }
89
+ default: {
90
+ i = 9;
91
+ }
92
+ }
93
+ }
94
+ `;
95
+ const result = await linkTest(src);
96
+ expectTrimmedMatch(result, src);
97
+ });
98
+
99
+ test("linked WGSL keeps an inline comment before a call argument", async () => {
100
+ const src = `
101
+ fn main() {
102
+ let y = max(1, /* big */ 2);
103
+ }
104
+ `;
105
+ const result = await linkTest(src);
106
+ expect(result).toContain("/* big */");
107
+ expectTrimmedMatch(result, src);
108
+ });
109
+
110
+ test("linked WGSL keeps an inline comment after a call argument", async () => {
111
+ const src = `
112
+ fn main() {
113
+ let y = max(1 /* big */, 2);
114
+ }
115
+ `;
116
+ const result = await linkTest(src);
117
+ expectTrimmedMatch(result, src);
118
+ });
119
+
120
+ test("linked WGSL keeps an inline comment between binary operands", async () => {
121
+ const src = `
122
+ fn main() {
123
+ let y = 1 + /* mid */ 2;
124
+ }
125
+ `;
126
+ const result = await linkTest(src);
127
+ expectTrimmedMatch(result, src);
128
+ });
129
+
130
+ test("a line comment inside an expression stays on its own line", async () => {
131
+ const src = `
132
+ fn main() {
133
+ let y = max(1, // big
134
+ 2);
135
+ }
136
+ `;
137
+ const result = await linkTest(src);
138
+ expect(result).toContain("// big");
139
+ // the line comment must be followed by a newline, or it would swallow the
140
+ // rest of the expression (`2);`) into the comment
141
+ expect(result).not.toContain("// big 2");
142
+ expect(result).toContain("2)");
143
+ });
@@ -6,29 +6,50 @@ import { WeslStream } from "../parse/WeslStream.ts";
6
6
  * . extra whitespace,
7
7
  * . comments,
8
8
  * . trailing commas in brackets, paren, and array containers
9
+ * . redundant module-scope `;` (an empty global declaration, which structural
10
+ * emit canonicalizes away)
9
11
  */
10
12
  export function stripWesl(text: string): string {
11
13
  const stream = new WeslStream(text);
12
14
  const firstToken = stream.nextToken();
13
15
  if (firstToken === null) return "";
14
16
 
17
+ let depth = firstToken.text === "{" ? 1 : 0;
18
+ let prev = firstToken.text;
15
19
  let result = firstToken.text;
16
20
  while (true) {
17
21
  const token = stream.nextToken();
18
22
  if (token === null) return result;
19
23
 
24
+ // A `;` at module scope following a `}` (or another such `;`) is an empty
25
+ // global declaration; the structural emitter drops it, so drop it here too.
26
+ if (token.text === ";" && depth === 0 && (prev === "}" || prev === ";")) {
27
+ continue;
28
+ }
29
+
20
30
  if (token.text === ",") {
21
31
  const nextToken = stream.nextToken();
22
32
  const nextText = nextToken?.text;
23
33
  if (nextText === "}" || nextText === "]" || nextText === ")") {
24
34
  // Ignore trailing comma
25
- result += " ";
26
- result += nextText;
35
+ result += " " + nextText;
27
36
  } else {
28
- result += ", " + (nextToken?.text ?? "");
37
+ result += ", " + (nextText ?? "");
38
+ }
39
+ if (nextText !== undefined) {
40
+ depth += braceDelta(nextText);
41
+ prev = nextText;
29
42
  }
30
43
  } else {
31
44
  result += " " + token.text;
45
+ depth += braceDelta(token.text);
46
+ prev = token.text;
32
47
  }
33
48
  }
34
49
  }
50
+
51
+ function braceDelta(text: string): number {
52
+ if (text === "{") return 1;
53
+ if (text === "}") return -1;
54
+ return 0;
55
+ }
@@ -3,9 +3,15 @@ import type { WgslTestSrc } from "wesl-testsuite";
3
3
  import { link } from "../Linker.ts";
4
4
  import { type ManglerFn, underscoreMangle } from "../Mangler.ts";
5
5
  import { mapValues } from "../Util.ts";
6
- import { expectTrimmedMatch, trimSrc } from "./TrimmedMatch.ts";
6
+ import { expectTokenMatch } from "./TestUtil.ts";
7
+ import { trimSrc } from "./TrimmedMatch.ts";
7
8
 
8
- /** Link wesl sources and compare linked wgsl vs expectations (ignores whitespace). */
9
+ /**
10
+ * Link wesl sources and compare the linked wgsl against expectations.
11
+ * These cases verify linking (imports, mangling, conditional compilation,
12
+ * tree-shaking), not layout, so the comparison is token-based: whitespace and
13
+ * comments are ignored, leaving line layout to the emit-specific tests.
14
+ */
9
15
  export async function testLink(
10
16
  weslSrc: Record<string, string>,
11
17
  rootModuleName: string,
@@ -13,7 +19,7 @@ export async function testLink(
13
19
  mangler?: ManglerFn,
14
20
  ): Promise<void> {
15
21
  const resultMap = await link({ weslSrc, rootModuleName, mangler });
16
- expectTrimmedMatch(resultMap.dest, expectedWgsl);
22
+ expectTokenMatch(resultMap.dest, expectedWgsl);
17
23
  }
18
24
 
19
25
  type CaseMap = Map<string, WgslTestSrc>;
@@ -43,6 +49,16 @@ const knownFormattingDifferences: Record<string, string> = {
43
49
  "@if on break statement": `
44
50
  fn foo() { while true { break; } }
45
51
  fn bar() { while true { } }`,
52
+ "@if on switch statement": `
53
+ fn func() {
54
+ switch 0 { default: { let foo = 10; } }
55
+ }`,
56
+ "@if on switch clause": `
57
+ fn func() {
58
+ switch 0 {
59
+ default: { let foo = 10; }
60
+ }
61
+ }`,
46
62
  };
47
63
 
48
64
  /** Test linking a single case from a shared test suite (ImportCases, etc.) */
@@ -2,13 +2,23 @@ import { expect } from "vitest";
2
2
  import { type BoundAndTransformed, RecordResolver, type SrcModule } from "wesl";
3
3
  import { bindAndTransform, type LinkParams, link } from "../Linker.ts";
4
4
  import { withLoggerAsync } from "../Logging.ts";
5
- import { parseSrcModule, type WeslAST } from "../ParseWESL.ts";
5
+ import {
6
+ type ParseOptions,
7
+ parseSrcModule,
8
+ type WeslAST,
9
+ } from "../ParseWESL.ts";
6
10
  import { expectNoLog, logCatch } from "./LogCatcher.ts";
7
11
  import { stripWesl } from "./StripWesl.ts";
8
12
 
9
13
  export type LinkTestOpts = Pick<
10
14
  LinkParams,
11
- "conditions" | "libs" | "config" | "virtualLibs" | "constants" | "mangler"
15
+ | "conditions"
16
+ | "libs"
17
+ | "config"
18
+ | "virtualLibs"
19
+ | "constants"
20
+ | "mangler"
21
+ | "weslExtensions"
12
22
  >;
13
23
 
14
24
  interface BindTestResult {
@@ -22,13 +32,13 @@ export function expectTokenMatch(actual: string, expected: string): void {
22
32
  }
23
33
 
24
34
  /** Parse a single wesl file. */
25
- export function parseWESL(src: string): WeslAST {
35
+ export function parseWESL(src: string, options?: ParseOptions): WeslAST {
26
36
  const srcModule: SrcModule = {
27
37
  modulePath: "package::test",
28
38
  debugFilePath: "./test.wesl",
29
39
  src,
30
40
  };
31
- return parseSrcModule(srcModule);
41
+ return parseSrcModule(srcModule, options);
32
42
  }
33
43
 
34
44
  /** Link wesl for tests. First module is ./test.wesl, rest are ./file1.wesl, etc. */
@@ -74,13 +84,13 @@ async function linkWithLogInternal(
74
84
  }
75
85
 
76
86
  /** Parse wesl for testing, ensuring no logged warnings. */
77
- export function parseTest(src: string): WeslAST {
78
- return expectNoLog(() => parseWESL(src));
87
+ export function parseTest(src: string, options?: ParseOptions): WeslAST {
88
+ return expectNoLog(() => parseWESL(src, options));
79
89
  }
80
90
 
81
91
  /** Parse wesl without log collection (for debugging). */
82
- export function parseTestRaw(src: string): WeslAST {
83
- return parseWESL(src);
92
+ export function parseTestRaw(src: string, options?: ParseOptions): WeslAST {
93
+ return parseWESL(src, options);
84
94
  }
85
95
 
86
96
  /** Parse and bind wesl source for testing. Returns bound result and resolver. */
@@ -107,6 +107,24 @@ test("parse >> as template", () => {
107
107
  expect(tokenizer.nextToken()).toBe(null);
108
108
  });
109
109
 
110
+ test("template discovery ignores > inside comments", () => {
111
+ const src = "a < b /* > */ ;";
112
+ const tokenizer = new WeslStream(src);
113
+ expect(tokenizer.nextToken()?.text).toBe("a");
114
+ expect(tokenizer.nextTemplateStartToken()).toBe(null);
115
+ });
116
+
117
+ test("template discovery sees comments inside template lists", () => {
118
+ const src = "array< /* len */ f32, 4 >";
119
+ const tokenizer = new WeslStream(src);
120
+ expect(tokenizer.nextToken()?.text).toBe("array");
121
+ expect(tokenizer.nextTemplateStartToken()).toEqual({
122
+ kind: "symbol",
123
+ text: "<",
124
+ span: [5, 6],
125
+ } as WeslToken);
126
+ });
127
+
110
128
  test("parse skip block comment", () => {
111
129
  const src = "/* /* // */ */vec3<f32>";
112
130
  const tokenizer = new WeslStream(src);
@@ -133,3 +151,81 @@ test("parse skip line without newline", () => {
133
151
  expect(tokenizer.nextToken()).toBe(null);
134
152
  expect(tokenizer.checkpoint()).toBe(src.length);
135
153
  });
154
+
155
+ test("unicode mid-word falls back with correct span", () => {
156
+ const src = "réflexion x";
157
+ const tokenizer = new WeslStream(src);
158
+ expect(tokenizer.nextToken()).toEqual({
159
+ kind: "word",
160
+ text: "réflexion",
161
+ span: [0, 9],
162
+ } as WeslToken);
163
+ expect(tokenizer.nextToken()?.span).toEqual([10, 11]);
164
+ });
165
+
166
+ test("surrogate-pair ident spans count UTF-16 units", () => {
167
+ const src = "𐰓𐰏𐰇 x";
168
+ const tokenizer = new WeslStream(src);
169
+ expect(tokenizer.nextToken()).toEqual({
170
+ kind: "word",
171
+ text: "𐰓𐰏𐰇",
172
+ span: [0, 6],
173
+ } as WeslToken);
174
+ expect(tokenizer.nextToken()?.span).toEqual([7, 8]);
175
+ });
176
+
177
+ test("underscore boundary: _é word, _x word, bare _ symbol", () => {
178
+ const under = new WeslStream("_é _x _ x");
179
+ expect(under.nextToken()).toEqual({
180
+ kind: "word",
181
+ text: "_é",
182
+ span: [0, 2],
183
+ } as WeslToken);
184
+ expect(under.nextToken()).toEqual({
185
+ kind: "word",
186
+ text: "_x",
187
+ span: [3, 5],
188
+ } as WeslToken);
189
+ expect(under.nextToken()).toEqual({
190
+ kind: "symbol",
191
+ text: "_",
192
+ span: [6, 7],
193
+ } as WeslToken);
194
+ });
195
+
196
+ test("unicode blankspace separates tokens", () => {
197
+ const src = "a\u{2028}b\u{0085}c";
198
+ const tokenizer = new WeslStream(src);
199
+ expect(tokenizer.nextToken()?.span).toEqual([0, 1]);
200
+ expect(tokenizer.nextToken()?.span).toEqual([2, 3]);
201
+ expect(tokenizer.nextToken()?.span).toEqual([4, 5]);
202
+ expect(tokenizer.nextToken()).toBe(null);
203
+ });
204
+
205
+ test("leading-dot float and 3-char symbols", () => {
206
+ const src = "x >>= .5 <<= 0x1p4";
207
+ const tokenizer = new WeslStream(src);
208
+ tokenizer.nextToken(); // x
209
+ expect(tokenizer.nextToken()).toEqual({
210
+ kind: "symbol",
211
+ text: ">>=",
212
+ span: [2, 5],
213
+ } as WeslToken);
214
+ expect(tokenizer.nextToken()).toEqual({
215
+ kind: "number",
216
+ text: ".5",
217
+ span: [6, 8],
218
+ } as WeslToken);
219
+ expect(tokenizer.nextToken()?.text).toBe("<<=");
220
+ expect(tokenizer.nextToken()).toEqual({
221
+ kind: "number",
222
+ text: "0x1p4",
223
+ span: [13, 18],
224
+ } as WeslToken);
225
+ });
226
+
227
+ test("invalid character throws", () => {
228
+ const tokenizer = new WeslStream("a # b");
229
+ tokenizer.nextToken();
230
+ expect(() => tokenizer.nextToken()).toThrow(/Invalid token #/);
231
+ });
@@ -2,66 +2,34 @@
2
2
 
3
3
  exports[`parse unicode ident 1`] = `
4
4
  "module
5
- text '
6
- '
7
5
  fn Δέλτα()
8
6
  decl %Δέλτα
9
- statement
10
- text '{}'
11
- text '
12
- '
7
+ block
13
8
  fn réflexion()
14
9
  decl %réflexion
15
- statement
16
- text '{}'
17
- text '
18
- '
10
+ block
19
11
  fn Кызыл()
20
12
  decl %Кызыл
21
- statement
22
- text '{}'
23
- text '
24
- '
13
+ block
25
14
  fn 𐰓𐰏𐰇()
26
15
  decl %𐰓𐰏𐰇
27
- statement
28
- text '{}'
29
- text '
30
- '
16
+ block
31
17
  fn 朝焼け()
32
18
  decl %朝焼け
33
- statement
34
- text '{}'
35
- text '
36
- '
19
+ block
37
20
  fn سلام()
38
21
  decl %سلام
39
- statement
40
- text '{}'
41
- text '
42
- '
22
+ block
43
23
  fn 검정()
44
24
  decl %검정
45
- statement
46
- text '{}'
47
- text '
48
- '
25
+ block
49
26
  fn שָׁלוֹם()
50
27
  decl %שָׁלוֹם
51
- statement
52
- text '{}'
53
- text '
54
- '
28
+ block
55
29
  fn गुलाबी()
56
30
  decl %गुलाबी
57
- statement
58
- text '{}'
59
- text '
60
- '
31
+ block
61
32
  fn փիրուզ()
62
33
  decl %փիրուզ
63
- statement
64
- text '{}'
65
- text '
66
- '"
34
+ block"
67
35
  `;
package/src/RawEmit.ts DELETED
@@ -1,103 +0,0 @@
1
- import type {
2
- AttributeElem,
3
- NameElem,
4
- StuffElem,
5
- TranslateTimeExpressionElem,
6
- TypeRefElem,
7
- TypeTemplateParameter,
8
- UnknownExpressionElem,
9
- } from "./AbstractElems.ts";
10
- import { assertUnreachable } from "./Assertions.ts";
11
- import {
12
- diagnosticControlToString,
13
- expressionToString,
14
- findDecl,
15
- } from "./LowerAndEmit.ts";
16
- import type { RefIdent } from "./Scope.ts";
17
-
18
- // LATER DRY emitting elements like this with LowerAndEmit?
19
-
20
- export function attributeToString(e: AttributeElem): string {
21
- const { kind } = e.attribute;
22
- // LATER emit more precise source map info by making use of all the spans
23
- // Like the first case does
24
- if (kind === "@attribute") {
25
- const { params } = e.attribute;
26
- if (params === undefined || params.length === 0) {
27
- return "@" + e.attribute.name;
28
- } else {
29
- return `@${e.attribute.name}(${params
30
- .map(param => contentsToString(param))
31
- .join(", ")})`;
32
- }
33
- } else if (kind === "@builtin") {
34
- return "@builtin(" + e.attribute.param.name + ")";
35
- } else if (kind === "@diagnostic") {
36
- return (
37
- "@diagnostic" +
38
- diagnosticControlToString(e.attribute.severity, e.attribute.rule)
39
- );
40
- } else if (kind === "@if") {
41
- return `@if(${expressionToString(e.attribute.param.expression)})`;
42
- } else if (kind === "@elif") {
43
- return `@elif(${expressionToString(e.attribute.param.expression)})`;
44
- } else if (kind === "@else") {
45
- return "@else";
46
- } else if (kind === "@interpolate") {
47
- return `@interpolate(${e.attribute.params.map(v => v.name).join(", ")})`;
48
- } else {
49
- assertUnreachable(kind);
50
- }
51
- }
52
-
53
- export function typeListToString(params: TypeTemplateParameter[]): string {
54
- return `<${params.map(typeParamToString).join(", ")}>`;
55
- }
56
-
57
- export function typeParamToString(param?: TypeTemplateParameter): string {
58
- if (param === undefined) return "?";
59
- if (param.kind === "type") return typeRefToString(param);
60
- return expressionToString(param);
61
- }
62
-
63
- export function typeRefToString(t?: TypeRefElem): string {
64
- if (!t) return "?";
65
- const { name, templateParams } = t;
66
- const params = templateParams ? typeListToString(templateParams) : "";
67
- return `${refToString(name)}${params}`;
68
- }
69
-
70
- function refToString(ref: RefIdent | string): string {
71
- if (typeof ref === "string") return ref;
72
- if (ref.std) return ref.originalName;
73
- const decl = findDecl(ref);
74
- return decl.mangledName || decl.originalName;
75
- }
76
-
77
- export function contentsToString(
78
- elem:
79
- | TranslateTimeExpressionElem
80
- | UnknownExpressionElem
81
- | NameElem
82
- | StuffElem,
83
- ): string {
84
- if (elem.kind === "translate-time-expression") {
85
- throw new Error("Not supported");
86
- } else if (elem.kind === "expression" || elem.kind === "stuff") {
87
- const parts = elem.contents.map(c => {
88
- const { kind } = c;
89
- if (kind === "text") {
90
- return c.srcModule.src.slice(c.start, c.end);
91
- } else if (kind === "ref") {
92
- return refToString(c.ident);
93
- } else {
94
- return `?${c.kind}?`;
95
- }
96
- });
97
- return parts.join(" ");
98
- } else if (elem.kind === "name") {
99
- return elem.name;
100
- } else {
101
- assertUnreachable(elem);
102
- }
103
- }