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
package/src/Mangler.ts CHANGED
@@ -25,7 +25,7 @@ export type ManglerFn = (
25
25
  /**
26
26
  * Construct a globally unique name based on the declaration
27
27
  * module path separated by underscores.
28
- * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/wgsl-tooling-wg/wesl-spec/blob/main/NameMangling.md)
28
+ * Corresponds to "Underscore-count mangling" from [NameMangling.md](https://github.com/webgpu-tools/wesl-spec/blob/main/NameMangling.md)
29
29
  */
30
30
  export function underscoreMangle(
31
31
  decl: DeclIdent,
@@ -1,5 +1,9 @@
1
1
  import { moduleToRelativePath, normalizeDebugRoot } from "./ModulePathUtil.ts";
2
- import { parseSrcModule, type WeslAST } from "./ParseWESL.ts";
2
+ import {
3
+ parseSrcModule,
4
+ type WeslAST,
5
+ type WeslExtensions,
6
+ } from "./ParseWESL.ts";
3
7
  import { normalize, noSuffix } from "./PathUtil.ts";
4
8
  import type { WeslBundle } from "./WeslBundle.ts";
5
9
 
@@ -20,6 +24,8 @@ export interface RecordResolverOptions {
20
24
  packageName?: string;
21
25
  /** Debug path prefix for error messages */
22
26
  debugWeslRoot?: string;
27
+ /** Enable experimental, not-yet-spec'd syntax extensions while parsing. */
28
+ weslExtensions?: WeslExtensions;
23
29
  }
24
30
 
25
31
  const libRegex = /^lib\.w[eg]sl$/i;
@@ -30,15 +36,17 @@ export class RecordResolver implements BatchModuleResolver {
30
36
  readonly sources: Record<string, string>;
31
37
  readonly packageName: string;
32
38
  readonly debugWeslRoot: string;
39
+ readonly weslExtensions?: WeslExtensions;
33
40
 
34
41
  constructor(
35
42
  sources: Record<string, string>,
36
43
  options: RecordResolverOptions = {},
37
44
  ) {
38
- const { packageName = "package", debugWeslRoot } = options;
45
+ const { packageName = "package", debugWeslRoot, weslExtensions } = options;
39
46
  this.sources = sources;
40
47
  this.packageName = packageName;
41
48
  this.debugWeslRoot = normalizeDebugRoot(debugWeslRoot);
49
+ this.weslExtensions = weslExtensions;
42
50
  }
43
51
 
44
52
  resolveModule(modulePath: string): WeslAST | undefined {
@@ -49,7 +57,10 @@ export class RecordResolver implements BatchModuleResolver {
49
57
  if (source === undefined) return undefined;
50
58
 
51
59
  const debugFilePath = this.modulePathToDebugPath(modulePath);
52
- const ast = parseSrcModule({ modulePath, debugFilePath, src: source });
60
+ const ast = parseSrcModule(
61
+ { modulePath, debugFilePath, src: source },
62
+ { weslExtensions: this.weslExtensions },
63
+ );
53
64
  this.astCache.set(modulePath, ast);
54
65
  return ast;
55
66
  }
@@ -171,7 +182,7 @@ export function freshResolver(inner: ModuleResolver): ModuleResolver {
171
182
  if (cached) return cached;
172
183
  const ast = inner.resolveModule(modulePath);
173
184
  if (!ast) return undefined;
174
- const fresh = parseSrcModule(ast.srcModule);
185
+ const fresh = parseSrcModule(ast.srcModule, ast.parseOptions);
175
186
  cache.set(modulePath, fresh);
176
187
  return fresh;
177
188
  },
package/src/ParseWESL.ts CHANGED
@@ -1,27 +1,18 @@
1
1
  import type {
2
2
  ConstAssertElem,
3
- ContainerElem,
4
- ImportElem,
5
3
  ImportStatement,
6
4
  ModuleElem,
7
5
  } from "./AbstractElems.ts";
8
6
  import { filterValidElements } from "./Conditions.ts";
9
7
  import { type FlatImport, flattenTreeImport } from "./FlattenTreeImport.ts";
8
+ import { declsOfKind } from "./LinkerUtil.ts";
10
9
  import type { ParseError } from "./ParseError.ts";
11
10
  import { parseWesl } from "./parse/ParseWesl.ts";
12
- import type { ParseOptions } from "./parse/ParsingContext.ts";
11
+ import type { ParseOptions, WeslExtensions } from "./parse/ParsingContext.ts";
13
12
  import type { Conditions, Scope, SrcModule } from "./Scope.ts";
14
13
  import type { Span } from "./Span.ts";
15
14
  import { errorHighlight, offsetToLineNumber } from "./Util.ts";
16
15
 
17
- export type { ParseOptions };
18
-
19
- /** Partial element being constructed during parsing. */
20
- export type OpenElem<T extends ContainerElem = ContainerElem> = Pick<
21
- T,
22
- "kind" | "contents"
23
- >;
24
-
25
16
  /**
26
17
  * Result of parsing one WESL module (e.g., one .wesl file).
27
18
  *
@@ -41,6 +32,8 @@ export interface WeslAST {
41
32
  imports: ImportStatement[];
42
33
  /** Module level const_assert statements. */
43
34
  moduleAsserts?: ConstAssertElem[];
35
+ /** Parse options used to produce this AST (so re-parsing preserves them). */
36
+ parseOptions?: ParseOptions;
44
37
  }
45
38
 
46
39
  /** Extended AST with cached flattened imports. */
@@ -61,22 +54,25 @@ export type StableState = WeslAST;
61
54
  /** Unstable values used during parse collection. */
62
55
  export interface WeslParseContext {
63
56
  scope: Scope; // current scope (points somewhere in rootScope)
64
- openElems: OpenElem[]; // elems that are collecting their contents
65
57
  }
66
58
 
59
+ export type { ParseOptions, WeslExtensions };
60
+
67
61
  /** Human-readable error when parsing WESL fails. */
68
62
  export class WeslParseError extends Error {
69
63
  span: Span;
70
64
  src: SrcModule;
71
65
  constructor(opts: { cause: ParseError; src: SrcModule }) {
72
- const source = opts.src.src;
73
- const [lineNum, linePos] = offsetToLineNumber(opts.cause.span[0], source);
74
- let message = `${opts.src.debugFilePath}:${lineNum}:${linePos}`;
75
- message += ` error: ${opts.cause.message}\n`;
76
- message += errorHighlight(source, opts.cause.span).join("\n");
77
- super(message, { cause: opts.cause });
78
- this.span = opts.cause.span;
79
- this.src = opts.src;
66
+ const { cause, src } = opts;
67
+ const source = src.src;
68
+ const [lineNum, linePos] = offsetToLineNumber(cause.span[0], source);
69
+ const highlight = errorHighlight(source, cause.span).join("\n");
70
+ const message =
71
+ `${src.debugFilePath}:${lineNum}:${linePos}` +
72
+ ` error: ${cause.message}\n${highlight}`;
73
+ super(message, { cause });
74
+ this.span = cause.span;
75
+ this.src = src;
80
76
  }
81
77
  }
82
78
 
@@ -96,22 +92,14 @@ export function flatImports(
96
92
  // TODO cache per condition set?
97
93
  if (ast._flatImports && !conditions) return ast._flatImports;
98
94
 
99
- // Get ImportElem elements from moduleElem contents
100
- const importElems = ast.moduleElem.contents.filter(
101
- (elem): elem is ImportElem => elem.kind === "import",
102
- );
103
-
104
- // Filter based on conditions if provided
95
+ const importElems = declsOfKind(ast.moduleElem, "import");
105
96
  const validImportElems = conditions
106
97
  ? filterValidElements(importElems, conditions)
107
98
  : importElems;
108
99
 
109
- // Extract ImportStatement from valid ImportElem elements
110
- const importStatements = validImportElems.map(elem => elem.imports);
111
-
112
- const flat = importStatements.flatMap(flattenTreeImport);
113
- if (!conditions) {
114
- ast._flatImports = flat;
115
- }
100
+ const flat = validImportElems.flatMap(elem =>
101
+ flattenTreeImport(elem.imports),
102
+ );
103
+ if (!conditions) ast._flatImports = flat;
116
104
  return flat;
117
105
  }
package/src/SrcMap.ts CHANGED
@@ -88,14 +88,8 @@ export class SrcMap {
88
88
  const { src, position: srcStart } = this.destToSrc(e.srcStart);
89
89
  const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
90
90
  if (endSrc !== src) throw new Error("NYI, need to split");
91
- const newEntry: SrcMapEntry = {
92
- src,
93
- srcStart,
94
- srcEnd,
95
- destStart: e.destStart,
96
- destEnd: e.destEnd,
97
- };
98
- return newEntry;
91
+ const { destStart, destEnd } = e;
92
+ return { src, srcStart, srcEnd, destStart, destEnd };
99
93
  });
100
94
 
101
95
  const otherSources = other.entries.filter(
@@ -122,11 +116,6 @@ export class SrcMap {
122
116
  }
123
117
  }
124
118
 
125
- /** sort entries in place by src start position */
126
- function sortSrc(entries: SrcMapEntry[]): void {
127
- entries.sort((a, b) => a.srcStart - b.srcStart);
128
- }
129
-
130
119
  /** Incrementally append to a string, tracking source references */
131
120
  export class SrcMapBuilder {
132
121
  #fragments: string[] = [];
@@ -191,12 +180,6 @@ export class SrcMapBuilder {
191
180
  this.add("\n", srcStart, srcEnd);
192
181
  }
193
182
 
194
- /** copy a string fragment from the src to the destination string */
195
- addCopy(srcStart: number, srcEnd: number): void {
196
- const fragment = this.source.text.slice(srcStart, srcEnd);
197
- this.add(fragment, srcStart, srcEnd);
198
- }
199
-
200
183
  /** return a SrcMap */
201
184
  static build(builders: SrcMapBuilder[]): SrcMap {
202
185
  const map = new SrcMap(
@@ -207,3 +190,8 @@ export class SrcMapBuilder {
207
190
  return map;
208
191
  }
209
192
  }
193
+
194
+ /** sort entries in place by src start position */
195
+ function sortSrc(entries: SrcMapEntry[]): void {
196
+ entries.sort((a, b) => a.srcStart - b.srcStart);
197
+ }
@@ -1,5 +1,5 @@
1
1
  // From https://www.w3.org/TR/WGSL/#predeclared
2
- // Use https://github.com/wgsl-tooling-wg/wgsl-spec to regenerate these list in the future
2
+ // Use https://github.com/webgpu-tools/wgsl-spec to regenerate these list in the future
3
3
 
4
4
  export const stdFns = `bitcast all any select arrayLength
5
5
  abs acos acosh asin asinh atan atanh atan2 ceil clamp cos cosh
package/src/WeslDevice.ts CHANGED
@@ -89,6 +89,7 @@ export function makeWeslDevice(device: GPUDevice): WeslDevice {
89
89
  lineNumber: message.lineNum,
90
90
  lineColumn: message.linePos,
91
91
  length: message.length,
92
+ offset: message.offset,
92
93
  error: new Error(message.type + ": " + message.message),
93
94
  });
94
95
  }
@@ -2,15 +2,18 @@ import type {
2
2
  AbstractElem,
3
3
  Attribute,
4
4
  AttributeElem,
5
+ CommentElem,
5
6
  DirectiveElem,
7
+ DoBlockElem,
6
8
  FnElem,
7
- StuffElem,
9
+ FnParamElem,
8
10
  TypedDeclElem,
9
11
  TypeRefElem,
10
12
  TypeTemplateParameter,
11
13
  UnknownExpressionElem,
12
14
  } from "../AbstractElems.ts";
13
15
  import { assertUnreachable } from "../Assertions.ts";
16
+ import { childElems } from "../LinkerUtil.ts";
14
17
  import {
15
18
  diagnosticControlToString,
16
19
  expressionToString,
@@ -25,34 +28,38 @@ export function astToString(elem: AbstractElem, indent = 0): string {
25
28
  const str = new LineWrapper(indent, maxLineLength);
26
29
  str.add(kind);
27
30
  addElemFields(elem, str);
28
- let childStrings: string[] = [];
29
- if ("contents" in elem) {
30
- childStrings = elem.contents.map(e => astToString(e, indent + 2));
31
- }
32
- if (childStrings.length) {
31
+ addCommentFields(elem, str);
32
+ const children = childElems(elem);
33
+ if (children.length) {
33
34
  str.nl();
35
+ const childStrings = children.map(e => astToString(e, indent + 2));
34
36
  str.addBlock(childStrings.join("\n"), false);
35
37
  }
36
38
 
37
39
  return str.result;
38
40
  }
39
41
 
42
+ /** @return string representation of an attribute (for test/debug) */
43
+ export function attributeToString(attr: Attribute): string {
44
+ const str = new LineWrapper(0, maxLineLength);
45
+ addAttributeFields(attr, str);
46
+ return str.result;
47
+ }
48
+
40
49
  // LATER rewrite to be shorter and easier to read
41
50
  function addElemFields(elem: AbstractElem, str: LineWrapper): void {
42
51
  const { kind } = elem;
43
52
  // no-op kinds (handled elsewhere or no additional fields)
44
53
  if (
45
54
  kind === "assert" ||
55
+ kind === "expression" ||
46
56
  kind === "module" ||
47
57
  kind === "param" ||
48
- kind === "stuff" ||
49
58
  kind === "switch-clause"
50
59
  ) {
51
60
  return;
52
61
  }
53
- if (kind === "text") {
54
- str.add(` '${elem.srcModule.src.slice(elem.start, elem.end)}'`);
55
- } else if (
62
+ if (
56
63
  kind === "var" ||
57
64
  kind === "let" ||
58
65
  kind === "gvar" ||
@@ -68,13 +75,10 @@ function addElemFields(elem: AbstractElem, str: LineWrapper): void {
68
75
  str.add(` ${elem.name.name}: ${typeRefElemToString(elem.typeRef)}`);
69
76
  } else if (kind === "name") {
70
77
  str.add(" " + elem.name);
71
- } else if (kind === "memberRef") {
72
- const extra = elem.extraComponents
73
- ? debugContentsToString(elem.extraComponents)
74
- : "";
75
- str.add(` ${elem.name.ident.originalName}.${elem.member.name}${extra}`);
76
78
  } else if (kind === "fn") {
77
79
  addFnFields(elem, str);
80
+ } else if (kind === "do") {
81
+ addDoFields(elem, str);
78
82
  } else if (kind === "alias") {
79
83
  const prefix = elem.name.ident.kind === "decl" ? "%" : "";
80
84
  str.add(
@@ -82,20 +86,8 @@ function addElemFields(elem: AbstractElem, str: LineWrapper): void {
82
86
  );
83
87
  } else if (kind === "attribute") {
84
88
  addAttributeFields(elem.attribute, str);
85
- } else if (kind === "expression") {
86
- const contents = elem.contents
87
- .map(e =>
88
- e.kind === "text"
89
- ? `'${e.srcModule.src.slice(e.start, e.end)}'`
90
- : astToString(e),
91
- )
92
- .join(" ");
93
- str.add(" " + contents);
94
89
  } else if (kind === "type") {
95
- const nameStr =
96
- typeof elem.name === "string" ? elem.name : elem.name.originalName;
97
- const params = elem.templateParams?.map(templateParamToString).join(", ");
98
- str.add(params ? ` ${nameStr}<${params}>` : ` ${nameStr}`);
90
+ str.add(" " + typeRefElemToString(elem));
99
91
  } else if (kind === "synthetic") {
100
92
  str.add(` '${elem.text}'`);
101
93
  } else if (kind === "import") {
@@ -109,7 +101,25 @@ function addElemFields(elem: AbstractElem, str: LineWrapper): void {
109
101
  str.add(" %" + elem.ident.originalName);
110
102
  } else if (kind === "directive") {
111
103
  addDirective(elem, str);
112
- } else if (kind === "statement" || kind === "continuing") {
104
+ } else if (
105
+ // statement kinds: the debug printer shows only their attributes
106
+ kind === "block" ||
107
+ kind === "if" ||
108
+ kind === "for" ||
109
+ kind === "while" ||
110
+ kind === "loop" ||
111
+ kind === "continuing" ||
112
+ kind === "switch" ||
113
+ kind === "return" ||
114
+ kind === "break" ||
115
+ kind === "continue" ||
116
+ kind === "discard" ||
117
+ kind === "assign" ||
118
+ kind === "increment" ||
119
+ kind === "decrement" ||
120
+ kind === "call" ||
121
+ kind === "empty"
122
+ ) {
113
123
  listAttributeElems(elem.attributes, str);
114
124
  } else if (kind === "literal") {
115
125
  str.add(` literal(${elem.value})`);
@@ -130,6 +140,18 @@ function addElemFields(elem: AbstractElem, str: LineWrapper): void {
130
140
  }
131
141
  }
132
142
 
143
+ /** Show attached leading/trailing comments to verify comment attachment. */
144
+ function addCommentFields(elem: AbstractElem, str: LineWrapper): void {
145
+ // SyntheticElem has no source position and so carries no comments
146
+ if (!("start" in elem)) return;
147
+ const { commentsBefore, commentsAfter } = elem;
148
+ if (commentsBefore?.length)
149
+ str.add(commentsToString("before", commentsBefore));
150
+ if (commentsAfter?.length) str.add(commentsToString("after", commentsAfter));
151
+ if ("innerComments" in elem && elem.innerComments?.length)
152
+ str.add(commentsToString("inner", elem.innerComments));
153
+ }
154
+
133
155
  function addAttributeFields(attr: Attribute, str: LineWrapper) {
134
156
  const { kind } = attr;
135
157
  if (kind === "@attribute") {
@@ -155,13 +177,6 @@ function addAttributeFields(attr: Attribute, str: LineWrapper) {
155
177
  }
156
178
  }
157
179
 
158
- /** @return string representation of an attribute (for test/debug) */
159
- export function attributeToString(attr: Attribute): string {
160
- const str = new LineWrapper(0, maxLineLength);
161
- addAttributeFields(attr, str);
162
- return str.result;
163
- }
164
-
165
180
  function addTypedDeclIdent(elem: TypedDeclElem, str: LineWrapper) {
166
181
  const { decl, typeRef } = elem;
167
182
  str.add(" %" + decl.ident.originalName);
@@ -170,17 +185,6 @@ function addTypedDeclIdent(elem: TypedDeclElem, str: LineWrapper) {
170
185
  }
171
186
  }
172
187
 
173
- function addFnFields(elem: FnElem, str: LineWrapper) {
174
- const { name, params, returnType, attributes } = elem;
175
- const paramStrs = params.map(p => {
176
- const { originalName } = p.name.decl.ident;
177
- return `${originalName}: ${typeRefElemToString(p.name.typeRef!)}`;
178
- });
179
- str.add(` ${name.ident.originalName}(${paramStrs.join(", ")})`);
180
- listAttributeElems(attributes, str);
181
- if (returnType) str.add(" -> " + typeRefElemToString(returnType));
182
- }
183
-
184
188
  /** show attribute names in short form to verify collection */
185
189
  function listAttributeElems(
186
190
  attrs: AttributeElem[] | undefined,
@@ -191,8 +195,25 @@ function listAttributeElems(
191
195
  });
192
196
  }
193
197
 
194
- function attributeName(attr: Attribute): string {
195
- return attr.kind === "@attribute" ? "@" + attr.name : attr.kind;
198
+ function typeRefElemToString(elem: TypeRefElem): string {
199
+ if (!elem) return "?type?";
200
+ const nameStr =
201
+ typeof elem.name === "string" ? elem.name : elem.name.originalName;
202
+ const params = elem.templateParams?.map(templateParamToString).join(", ");
203
+ return params ? `${nameStr}<${params}>` : nameStr;
204
+ }
205
+
206
+ function addFnFields(elem: FnElem, str: LineWrapper) {
207
+ const { name, params, returnType, attributes } = elem;
208
+ str.add(` ${name.ident.originalName}(${paramListToString(params)})`);
209
+ listAttributeElems(attributes, str);
210
+ if (returnType) str.add(" -> " + typeRefElemToString(returnType));
211
+ }
212
+
213
+ function addDoFields(elem: DoBlockElem, str: LineWrapper) {
214
+ const { name, params, attributes } = elem;
215
+ str.add(` ${name.name}(${paramListToString(params)})`);
216
+ listAttributeElems(attributes, str);
196
217
  }
197
218
 
198
219
  function addDirective(elem: DirectiveElem, str: LineWrapper) {
@@ -210,39 +231,34 @@ function addDirective(elem: DirectiveElem, str: LineWrapper) {
210
231
  listAttributeElems(attributes, str);
211
232
  }
212
233
 
213
- // LATER Temp hack while I clean up the expression parsing
234
+ function commentsToString(label: string, comments: CommentElem[]): string {
235
+ const texts = comments.map(c => {
236
+ const text = c.srcModule.src.slice(c.start, c.end);
237
+ return c.blankBefore ? `'${text}'(blank)` : `'${text}'`;
238
+ });
239
+ return ` ${label}[${texts.join(", ")}]`;
240
+ }
241
+
214
242
  function unknownExpressionToString(elem: UnknownExpressionElem): string {
215
- if (!("contents" in elem)) return astToString(elem);
216
- return elem.contents
217
- .map(e => {
218
- if (e.kind === "text")
219
- return `'${e.srcModule.src.slice(e.start, e.end)}'`;
220
- return astToString(e);
221
- })
222
- .join(" ");
243
+ return astToString(elem.expression);
244
+ }
245
+
246
+ function attributeName(attr: Attribute): string {
247
+ return attr.kind === "@attribute" ? "@" + attr.name : attr.kind;
223
248
  }
224
249
 
225
250
  function templateParamToString(p: TypeTemplateParameter): string {
226
251
  if (typeof p === "string") return p;
227
252
  if (p.kind === "type") return typeRefElemToString(p);
228
- // ExpressionElem - use astToString for expression elements
229
- return astToString(p);
253
+ // template params render inline inside <...>, so keep expressions on one line
254
+ return expressionToString(p);
230
255
  }
231
256
 
232
- function typeRefElemToString(elem: TypeRefElem): string {
233
- if (!elem) return "?type?";
234
- const nameStr =
235
- typeof elem.name === "string" ? elem.name : elem.name.originalName;
236
- const params = elem.templateParams?.map(templateParamToString).join(", ");
237
- return params ? `${nameStr}<${params}>` : nameStr;
238
- }
239
-
240
- export function debugContentsToString(elem: StuffElem): string {
241
- return elem.contents
242
- .map(c => {
243
- if (c.kind === "text") return c.srcModule.src.slice(c.start, c.end);
244
- if (c.kind === "ref") return c.ident.originalName; // not using mapped decl name for debug
245
- return `?${c.kind}?`;
246
- })
247
- .join(" ");
257
+ /** @return "name: type, ..." for a fn/do parameter list. */
258
+ function paramListToString(params: FnParamElem[]): string {
259
+ const paramStrs = params.map(p => {
260
+ const { originalName } = p.name.decl.ident;
261
+ return `${originalName}: ${typeRefElemToString(p.name.typeRef!)}`;
262
+ });
263
+ return paramStrs.join(", ");
248
264
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from "./discovery/FindUnboundIdents.ts";
7
7
  export * from "./discovery/PackageNameUtils.ts";
8
8
  export * from "./LinkedWesl.ts";
9
9
  export * from "./Linker.ts";
10
+ export * from "./LinkerUtil.ts";
10
11
  export * from "./LiveDeclarations.ts";
11
12
  export { debug, log, srcLog, validation, withLoggerAsync } from "./Logging.ts";
12
13
  export * from "./Mangler.ts";
@@ -20,7 +21,6 @@ export * from "./Scope.ts";
20
21
  export * from "./Span.ts";
21
22
  export * from "./SrcMap.ts";
22
23
  export * from "./StandardTypes.ts";
23
- export * from "./TransformBindingStructs.ts";
24
24
  export * from "./Util.ts";
25
25
  export * from "./WeslBundle.ts";
26
26
  export * from "./WeslDevice.ts";