wesl 0.6.48 → 0.7.0

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 (95) hide show
  1. package/dist/index.d.ts +295 -214
  2. package/dist/index.js +2947 -1550
  3. package/package.json +6 -8
  4. package/src/AbstractElems.ts +81 -81
  5. package/src/Assertions.ts +5 -5
  6. package/src/BindIdents.ts +193 -319
  7. package/src/ClickableError.ts +3 -2
  8. package/src/Conditions.ts +2 -2
  9. package/src/LinkedWesl.ts +1 -1
  10. package/src/Linker.ts +4 -3
  11. package/src/LinkerUtil.ts +1 -1
  12. package/src/Logging.ts +165 -0
  13. package/src/LowerAndEmit.ts +278 -110
  14. package/src/ModulePathUtil.ts +59 -0
  15. package/src/ModuleResolver.ts +26 -62
  16. package/src/ParseError.ts +9 -0
  17. package/src/ParseWESL.ts +30 -94
  18. package/src/RawEmit.ts +1 -4
  19. package/src/Reflection.ts +1 -1
  20. package/src/Scope.ts +3 -0
  21. package/src/Span.ts +2 -0
  22. package/src/SrcMap.ts +208 -0
  23. package/src/Stream.ts +30 -0
  24. package/src/TransformBindingStructs.ts +2 -2
  25. package/src/Util.ts +1 -1
  26. package/src/debug/ASTtoString.ts +84 -135
  27. package/src/discovery/FindUnboundIdents.ts +14 -5
  28. package/src/index.ts +5 -0
  29. package/src/parse/ContentsHelpers.ts +70 -0
  30. package/src/parse/ExpressionUtil.ts +121 -0
  31. package/src/parse/Keywords.ts +12 -12
  32. package/src/parse/OperatorBinding.ts +146 -0
  33. package/src/parse/ParseAttribute.ts +272 -0
  34. package/src/parse/ParseCall.ts +77 -0
  35. package/src/parse/ParseControlFlow.ts +129 -0
  36. package/src/parse/ParseDirective.ts +105 -0
  37. package/src/parse/ParseExpression.ts +288 -0
  38. package/src/parse/ParseFn.ts +151 -0
  39. package/src/parse/ParseGlobalVar.ts +131 -0
  40. package/src/parse/ParseIdent.ts +77 -0
  41. package/src/parse/ParseImport.ts +160 -0
  42. package/src/parse/ParseLocalVar.ts +69 -0
  43. package/src/parse/ParseLoop.ts +112 -0
  44. package/src/parse/ParseModule.ts +116 -0
  45. package/src/parse/ParseSimpleStatement.ts +162 -0
  46. package/src/parse/ParseStatement.ts +215 -0
  47. package/src/parse/ParseStruct.ts +89 -0
  48. package/src/parse/ParseType.ts +71 -0
  49. package/src/parse/ParseUtil.ts +174 -0
  50. package/src/parse/ParseValueDeclaration.ts +130 -0
  51. package/src/parse/ParseWesl.ts +51 -0
  52. package/src/parse/ParsingContext.ts +93 -0
  53. package/src/parse/WeslStream.ts +63 -20
  54. package/src/parse/stream/CachingStream.ts +48 -0
  55. package/src/parse/stream/MatchersStream.ts +85 -0
  56. package/src/parse/stream/RegexHelpers.ts +38 -0
  57. package/src/test/BevyLink.test.ts +100 -0
  58. package/src/test/BindStdTypes.test.ts +110 -0
  59. package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
  60. package/src/test/BulkTests.test.ts +11 -12
  61. package/src/test/ConditionLinking.test.ts +107 -0
  62. package/src/test/ConditionalElif.test.ts +1 -13
  63. package/src/test/ConditionalTranslationCases.test.ts +5 -0
  64. package/src/test/ErrorLogging.test.ts +2 -2
  65. package/src/test/ImportCasesV2.test.ts +63 -0
  66. package/src/test/LinkFails.test.ts +69 -0
  67. package/src/test/LinkPackage.test.ts +1 -1
  68. package/src/test/Linker.test.ts +75 -2
  69. package/src/test/LogCatcher.ts +53 -0
  70. package/src/test/Mangling.test.ts +1 -1
  71. package/src/test/ParseComments.test.ts +1 -2
  72. package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
  73. package/src/test/ParseErrorV2.test.ts +73 -0
  74. package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
  75. package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
  76. package/src/test/TestLink.ts +51 -51
  77. package/src/test/TestSetup.ts +9 -3
  78. package/src/test/TestUtil.ts +47 -77
  79. package/src/test/TrimmedMatch.ts +40 -0
  80. package/src/test/VirtualModules.test.ts +33 -2
  81. package/src/test/WeslDevice.test.ts +9 -2
  82. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
  83. package/src/test-util.ts +7 -0
  84. package/src/WESLCollect.ts +0 -656
  85. package/src/parse/AttributeGrammar.ts +0 -232
  86. package/src/parse/ImportGrammar.ts +0 -195
  87. package/src/parse/WeslBaseGrammar.ts +0 -11
  88. package/src/parse/WeslExpression.ts +0 -231
  89. package/src/parse/WeslGrammar.ts +0 -739
  90. package/src/test/Expression.test.ts +0 -22
  91. package/src/test/ImportSyntaxCases.test.ts +0 -24
  92. package/src/test/ParseError.test.ts +0 -45
  93. package/src/test/Reflection.test.ts +0 -176
  94. package/src/test/TransformBindingStructs.test.ts +0 -238
  95. /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
@@ -1,30 +1,17 @@
1
+ import { moduleToRelativePath, normalizeDebugRoot } from "./ModulePathUtil.ts";
1
2
  import { parseSrcModule, type WeslAST } from "./ParseWESL.ts";
2
3
  import { normalize, noSuffix } from "./PathUtil.ts";
3
4
  import type { WeslBundle } from "./WeslBundle.ts";
4
5
 
5
- const libRegex = /^lib\.w[eg]sl$/i;
6
-
7
- /**
8
- * Resolves module paths to parsed ASTs.
9
- *
10
- * Implementations may cache ASTs or load them lazily.
11
- */
6
+ /** Resolves module paths to parsed ASTs. Implementations may cache or load lazily. */
12
7
  export interface ModuleResolver {
13
- /**
14
- * Resolve a module path to its parsed AST.
15
- *
16
- * @param modulePath - Module path in :: format (e.g., "package::foo::bar")
17
- * @returns Parsed AST or undefined if module not found
18
- */
8
+ /** Resolve module path (e.g., "package::foo::bar") to AST, or undefined if not found. */
19
9
  resolveModule(modulePath: string): WeslAST | undefined;
20
10
  }
21
11
 
22
12
  /** Module resolver that supports batch enumeration of all modules. */
23
13
  export interface BatchModuleResolver extends ModuleResolver {
24
- /**
25
- * Return all modules, parsing them on-demand if needed.
26
- * Implementations should ensure all sources are parsed before returning.
27
- */
14
+ /** Return all modules, parsing them on-demand if needed. */
28
15
  allModules(): Iterable<[string, WeslAST]>;
29
16
  }
30
17
 
@@ -35,6 +22,8 @@ export interface RecordResolverOptions {
35
22
  debugWeslRoot?: string;
36
23
  }
37
24
 
25
+ const libRegex = /^lib\.w[eg]sl$/i;
26
+
38
27
  /** Module resolver for in-memory source records. Lazy by default. */
39
28
  export class RecordResolver implements BatchModuleResolver {
40
29
  readonly astCache = new Map<string, WeslAST>();
@@ -57,48 +46,40 @@ export class RecordResolver implements BatchModuleResolver {
57
46
  if (cached) return cached;
58
47
 
59
48
  const source = this.findSource(modulePath);
60
- if (!source) return undefined;
49
+ if (source === undefined) return undefined;
61
50
 
62
51
  const debugFilePath = this.modulePathToDebugPath(modulePath);
63
-
64
- const ast = parseSrcModule({
65
- modulePath,
66
- debugFilePath,
67
- src: source,
68
- });
52
+ const ast = parseSrcModule({ modulePath, debugFilePath, src: source });
69
53
  this.astCache.set(modulePath, ast);
70
54
  return ast;
71
55
  }
72
56
 
73
57
  private findSource(modulePath: string): string | undefined {
74
- if (this.sources[modulePath]) return this.sources[modulePath];
58
+ if (this.sources[modulePath] !== undefined) return this.sources[modulePath];
75
59
 
76
60
  const filePath = this.moduleToFilePath(modulePath);
61
+ if (filePath === undefined) return undefined;
77
62
  return findInVariants(this.sources, filePath);
78
63
  }
79
64
 
80
- private moduleToFilePath(modulePath: string): string {
81
- const parts = modulePath.split("::");
82
- if (parts[0] !== this.packageName && parts[0] !== "package") {
83
- return modulePath;
84
- }
85
-
86
- const pathParts = parts.slice(1);
87
- return pathParts.join("/");
65
+ /** Convert module path to file path, or undefined if not local. */
66
+ private moduleToFilePath(modulePath: string): string | undefined {
67
+ return moduleToRelativePath(modulePath, this.packageName);
88
68
  }
89
69
 
90
70
  private modulePathToDebugPath(modulePath: string): string {
91
- const filePath = this.moduleToFilePath(modulePath);
71
+ const filePath = this.moduleToFilePath(modulePath) ?? modulePath;
92
72
  return this.debugWeslRoot + filePath + ".wesl";
93
73
  }
94
74
 
95
- /** Return all modules, parsing them on-demand if needed. */
75
+ /** Parse all modules and return entries. */
96
76
  allModules(): Iterable<[string, WeslAST]> {
97
77
  for (const filePath of Object.keys(this.sources)) {
78
+ const treatLibAsRoot = this.packageName !== "package";
98
79
  const modulePath = fileToModulePath(
99
80
  filePath,
100
81
  this.packageName,
101
- this.packageName !== "package",
82
+ treatLibAsRoot,
102
83
  );
103
84
  this.resolveModule(modulePath);
104
85
  }
@@ -137,10 +118,8 @@ export class BundleResolver implements ModuleResolver {
137
118
  }
138
119
 
139
120
  resolveModule(modulePath: string): WeslAST | undefined {
140
- if (
141
- modulePath !== this.packageName &&
142
- !modulePath.startsWith(this.packageName + "::")
143
- ) {
121
+ const pkgPrefix = this.packageName + "::";
122
+ if (modulePath !== this.packageName && !modulePath.startsWith(pkgPrefix)) {
144
123
  return undefined;
145
124
  }
146
125
 
@@ -151,11 +130,7 @@ export class BundleResolver implements ModuleResolver {
151
130
  if (!source) return undefined;
152
131
 
153
132
  const debugFilePath = this.modulePathToDebugPath(modulePath);
154
- const ast = parseSrcModule({
155
- modulePath,
156
- debugFilePath,
157
- src: source,
158
- });
133
+ const ast = parseSrcModule({ modulePath, debugFilePath, src: source });
159
134
  this.astCache.set(modulePath, ast);
160
135
  return ast;
161
136
  }
@@ -173,11 +148,7 @@ export class BundleResolver implements ModuleResolver {
173
148
  }
174
149
 
175
150
  private moduleToFilePath(modulePath: string): string {
176
- const parts = modulePath.split("::");
177
- if (parts[0] !== this.packageName) {
178
- return modulePath;
179
- }
180
- return parts.slice(1).join("/");
151
+ return moduleToRelativePath(modulePath, this.packageName) ?? modulePath;
181
152
  }
182
153
 
183
154
  private modulePathToDebugPath(modulePath: string): string {
@@ -186,8 +157,8 @@ export class BundleResolver implements ModuleResolver {
186
157
  }
187
158
  }
188
159
 
189
- /** Convert file path to module path (e.g., "foo/bar.wesl" -> "package::foo::bar"). */
190
- function fileToModulePath(
160
+ /** Convert file path to module path (e.g., "foo/bar.wesl" to "package::foo::bar"). */
161
+ export function fileToModulePath(
191
162
  filePath: string,
192
163
  packageName: string,
193
164
  treatLibAsRoot: boolean,
@@ -204,14 +175,7 @@ function fileToModulePath(
204
175
  return packageName + "::" + moduleSuffix;
205
176
  }
206
177
 
207
- /** Normalize debug root to end with / or be empty. */
208
- function normalizeDebugRoot(debugWeslRoot?: string): string {
209
- if (debugWeslRoot === undefined) return "./";
210
- if (debugWeslRoot === "") return "";
211
- return debugWeslRoot.endsWith("/") ? debugWeslRoot : debugWeslRoot + "/";
212
- }
213
-
214
- /** Try path variants with and without ./ prefix and extension suffixes. */
178
+ /** Try path variants with/without ./ prefix and extension suffixes. */
215
179
  function findInVariants(
216
180
  sources: Record<string, string>,
217
181
  basePath: string,
@@ -221,11 +185,11 @@ function findInVariants(
221
185
 
222
186
  for (const prefix of prefixes) {
223
187
  const path = prefix + basePath;
224
- if (sources[path]) return sources[path];
188
+ if (sources[path] !== undefined) return sources[path];
225
189
 
226
190
  for (const ext of extensions) {
227
191
  const withExt = `${path}.${ext}`;
228
- if (sources[withExt]) return sources[withExt];
192
+ if (sources[withExt] !== undefined) return sources[withExt];
229
193
  }
230
194
  }
231
195
 
@@ -0,0 +1,9 @@
1
+ import type { Span } from "./Span.ts";
2
+
3
+ export class ParseError extends Error {
4
+ span: Span;
5
+ constructor(msg: string, span: Span) {
6
+ super(msg);
7
+ this.span = span;
8
+ }
9
+ }
package/src/ParseWESL.ts CHANGED
@@ -1,80 +1,67 @@
1
- import {
2
- type AppState,
3
- ParseError,
4
- type ParserInit,
5
- type Span,
6
- } from "mini-parse";
7
1
  import type {
8
2
  ConstAssertElem,
3
+ ContainerElem,
9
4
  ImportElem,
10
5
  ImportStatement,
11
6
  ModuleElem,
12
7
  } from "./AbstractElems.ts";
13
- import { throwClickableError } from "./ClickableError.ts";
14
8
  import { filterValidElements } from "./Conditions.ts";
15
9
  import { type FlatImport, flattenTreeImport } from "./FlattenTreeImport.ts";
16
- import { weslRoot } from "./parse/WeslGrammar.ts";
17
- import { WeslStream } from "./parse/WeslStream.ts";
18
- import {
19
- type Conditions,
20
- emptyScope,
21
- type Scope,
22
- type SrcModule,
23
- } from "./Scope.ts";
10
+ import type { ParseError } from "./ParseError.ts";
11
+ import { parseWesl } from "./parse/ParseWesl.ts";
12
+ import type { Conditions, Scope, SrcModule } from "./Scope.ts";
13
+ import type { Span } from "./Span.ts";
24
14
  import { errorHighlight, offsetToLineNumber } from "./Util.ts";
25
- import type { OpenElem } from "./WESLCollect.ts";
26
15
 
27
- /** result of a parse for one wesl module (e.g. one .wesl file)
16
+ /** Partial element being constructed during parsing. */
17
+ export type OpenElem<T extends ContainerElem = ContainerElem> = Pick<
18
+ T,
19
+ "kind" | "contents"
20
+ >;
21
+
22
+ /**
23
+ * Result of parsing one WESL module (e.g., one .wesl file).
28
24
  *
29
- * The parser constructs the AST constructed into three sections
30
- * for convenient access by the binding stage.
25
+ * The AST is constructed into three sections for the binding stage:
31
26
  * - import statements
32
27
  * - language elements (fn, struct, etc)
33
28
  * - scopes
34
- *
35
29
  */
36
30
  export interface WeslAST {
37
- /** source text for this module */
31
+ /** Source text for this module. */
38
32
  srcModule: SrcModule;
39
-
40
- /** root module element */
33
+ /** Root module element. */
41
34
  moduleElem: ModuleElem;
42
-
43
- /** root scope for this module */
35
+ /** Root scope for this module. */
44
36
  rootScope: Scope;
45
-
46
- /** imports found in this module */
37
+ /** Imports found in this module. */
47
38
  imports: ImportStatement[];
48
-
49
- /** module level const_assert statements */
39
+ /** Module level const_assert statements. */
50
40
  moduleAsserts?: ConstAssertElem[];
51
41
  }
52
42
 
53
- /** an extended version of the AST */
43
+ /** Extended AST with cached flattened imports. */
54
44
  export interface BindingAST extends WeslAST {
55
- /* a flattened version of the import statements constructed on demand from import trees, and cached here */
45
+ /** Flattened import statements (cached on demand). */
56
46
  _flatImports?: FlatImport[];
57
47
  }
58
48
 
59
- /** stable and unstable state used during parsing */
60
- export interface WeslParseState
61
- extends AppState<WeslParseContext, StableState> {
49
+ /** Stable and unstable state used during parsing. */
50
+ export interface WeslParseState {
62
51
  context: WeslParseContext;
63
52
  stable: StableState;
64
53
  }
65
54
 
66
- /** stable values used or accumulated during parsing */
55
+ /** Stable values used or accumulated during parsing. */
67
56
  export type StableState = WeslAST;
68
57
 
69
- /** unstable values used during parse collection */
58
+ /** Unstable values used during parse collection. */
70
59
  export interface WeslParseContext {
71
60
  scope: Scope; // current scope (points somewhere in rootScope)
72
61
  openElems: OpenElem[]; // elems that are collecting their contents
73
62
  }
74
63
 
75
- /**
76
- * An error when parsing WESL fails. Designed to be human-readable.
77
- */
64
+ /** Human-readable error when parsing WESL fails. */
78
65
  export class WeslParseError extends Error {
79
66
  span: Span;
80
67
  src: SrcModule;
@@ -84,69 +71,18 @@ export class WeslParseError extends Error {
84
71
  let message = `${opts.src.debugFilePath}:${lineNum}:${linePos}`;
85
72
  message += ` error: ${opts.cause.message}\n`;
86
73
  message += errorHighlight(source, opts.cause.span).join("\n");
87
- super(message, {
88
- cause: opts.cause,
89
- });
74
+ super(message, { cause: opts.cause });
90
75
  this.span = opts.cause.span;
91
76
  this.src = opts.src;
92
77
  }
93
78
  }
94
79
 
95
- /** Parse a WESL file. Throws on error. */
80
+ /** Parse a WESL file. */
96
81
  export function parseSrcModule(srcModule: SrcModule): WeslAST {
97
- const stream = new WeslStream(srcModule.src);
98
-
99
- const appState = blankWeslParseState(srcModule);
100
-
101
- const init: ParserInit = { stream, appState };
102
- try {
103
- const parseResult = weslRoot.parse(init);
104
- if (parseResult === null) {
105
- throw new Error("parseWESL failed");
106
- }
107
- } catch (e) {
108
- if (e instanceof ParseError) {
109
- const [lineNumber, lineColumn] = offsetToLineNumber(
110
- e.span[0],
111
- srcModule.src,
112
- );
113
- const error = new WeslParseError({ cause: e, src: srcModule });
114
- throwClickableError({
115
- url: srcModule.debugFilePath,
116
- text: srcModule.src,
117
- error,
118
- lineNumber,
119
- lineColumn,
120
- length: e.span[1] - e.span[0],
121
- });
122
- } else {
123
- throw e;
124
- }
125
- }
126
-
127
- return appState.stable as WeslAST;
128
- }
129
-
130
- export function blankWeslParseState(srcModule: SrcModule): WeslParseState {
131
- const rootScope = emptyScope(null);
132
- const moduleElem = null as any; // we'll fill this in later
133
- return {
134
- context: { scope: rootScope, openElems: [] },
135
- stable: { srcModule, imports: [], rootScope, moduleElem },
136
- };
137
- }
138
-
139
- export function syntheticWeslParseState(): WeslParseState {
140
- const srcModule: SrcModule = {
141
- modulePath: "package::test",
142
- debugFilePath: "./test.wesl",
143
- src: "",
144
- };
145
-
146
- return blankWeslParseState(srcModule);
82
+ return parseWesl(srcModule);
147
83
  }
148
84
 
149
- /** @return a flattened form of the import tree for convenience in binding idents. */
85
+ /** @return flattened form of import tree for binding idents. */
150
86
  export function flatImports(
151
87
  ast: BindingAST,
152
88
  conditions?: Conditions,
package/src/RawEmit.ts CHANGED
@@ -56,11 +56,8 @@ export function typeListToString(params: TypeTemplateParameter[]): string {
56
56
 
57
57
  export function typeParamToString(param?: TypeTemplateParameter): string {
58
58
  if (param === undefined) return "?";
59
- if (typeof param === "string") return param;
60
-
61
- if (param.kind === "expression") return contentsToString(param);
62
59
  if (param.kind === "type") return typeRefToString(param);
63
- assertUnreachable(param);
60
+ return expressionToString(param);
64
61
  }
65
62
 
66
63
  export function typeRefToString(t?: TypeRefElem): string {
package/src/Reflection.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { matchOneOf } from "mini-parse";
2
1
  import type {
3
2
  BindingStructElem,
4
3
  NameElem,
@@ -11,6 +10,7 @@ import type {
11
10
  import { assertThat } from "./Assertions.ts";
12
11
  import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
13
12
  import { identElemLog } from "./LinkerUtil.ts";
13
+ import { matchOneOf } from "./parse/stream/RegexHelpers.ts";
14
14
  import type { RefIdent } from "./Scope.ts";
15
15
  import {
16
16
  multisampledTextureTypes,
package/src/Scope.ts CHANGED
@@ -38,6 +38,9 @@ export interface RefIdent extends IdentBase {
38
38
  refersTo?: Ident; // import or decl ident in scope to which this ident refers. undefined before binding
39
39
  std?: true; // true if this is a standard wgsl identifier (like sin, or u32)
40
40
 
41
+ /** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
42
+ conditionRef?: true;
43
+
41
44
  // LATER consider tracking the current ast in BindIdents so that this field is unnecessary
42
45
  ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
43
46
 
package/src/Span.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** A range, from start (inclusive) to end (exclusive). */
2
+ export type Span = readonly [number, number];
package/src/SrcMap.ts ADDED
@@ -0,0 +1,208 @@
1
+ /** A source map file, and a path for debug purposes. */
2
+ export interface SrcWithPath {
3
+ /** User friendly path */
4
+ path?: string;
5
+ text: string;
6
+ }
7
+
8
+ export interface SrcMapEntry {
9
+ src: SrcWithPath;
10
+ srcStart: number;
11
+ srcEnd: number;
12
+ destStart: number;
13
+ destEnd: number;
14
+ }
15
+
16
+ export interface SrcPosition {
17
+ src: SrcWithPath;
18
+ position: number;
19
+ }
20
+
21
+ /** map text ranges in multiple src texts to a single dest text */
22
+ export class SrcMap {
23
+ entries: SrcMapEntry[];
24
+ dest: SrcWithPath;
25
+
26
+ constructor(dest: SrcWithPath, entries: SrcMapEntry[] = []) {
27
+ this.dest = dest;
28
+ this.entries = entries;
29
+ }
30
+
31
+ /** add a new mapping from src to dest ranges (must be non-overlapping in destination) */
32
+ addEntries(newEntries: SrcMapEntry[]): void {
33
+ this.entries.push(...newEntries);
34
+ }
35
+
36
+ /** given positions in the dest string, return corresponding positions in the src strings */
37
+ mapPositions(...positions: number[]): SrcPosition[] {
38
+ return positions.map(p => this.destToSrc(p));
39
+ }
40
+
41
+ /** internally compress adjacent entries where possible */
42
+ compact(): void {
43
+ if (!this.entries.length) return;
44
+ let prev = this.entries[0];
45
+ const newEntries: SrcMapEntry[] = [prev];
46
+
47
+ for (let i = 1; i < this.entries.length; i++) {
48
+ const e = this.entries[i];
49
+ if (
50
+ e.src.path === prev.src.path &&
51
+ e.src.text === prev.src.text &&
52
+ prev.destEnd === e.destStart &&
53
+ prev.srcEnd === e.srcStart
54
+ ) {
55
+ // combine adjacent range entries into one
56
+ prev.destEnd = e.destEnd;
57
+ prev.srcEnd = e.srcEnd;
58
+ } else {
59
+ newEntries.push(e);
60
+ prev = e;
61
+ }
62
+ }
63
+ this.entries = newEntries;
64
+ }
65
+
66
+ /** sort in destination order */
67
+ sort(): void {
68
+ this.entries.sort((a, b) => a.destStart - b.destStart);
69
+ }
70
+
71
+ /**
72
+ * This SrcMap's destination is a src for the other srcmap,
73
+ * so combine the two and return the result.
74
+ */
75
+ merge(other: SrcMap): SrcMap {
76
+ if (other === this) return this;
77
+
78
+ const mappedEntries = other.entries.filter(
79
+ e => e.src.path === this.dest.path && e.src.text === this.dest.text,
80
+ );
81
+ if (mappedEntries.length === 0) {
82
+ console.log("other source map does not link to this one");
83
+ return other;
84
+ }
85
+ sortSrc(mappedEntries);
86
+ const newEntries = mappedEntries.map(e => {
87
+ const { src, position: srcStart } = this.destToSrc(e.srcStart);
88
+ const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
89
+ if (endSrc !== src) throw new Error("NYI, need to split");
90
+ const newEntry: SrcMapEntry = {
91
+ src,
92
+ srcStart,
93
+ srcEnd,
94
+ destStart: e.destStart,
95
+ destEnd: e.destEnd,
96
+ };
97
+ return newEntry;
98
+ });
99
+
100
+ const otherSources = other.entries.filter(
101
+ e => e.src.path !== this.dest.path || e.src.text !== this.dest.text,
102
+ );
103
+
104
+ const newMap = new SrcMap(other.dest, [...otherSources, ...newEntries]);
105
+ newMap.sort();
106
+ return newMap;
107
+ }
108
+
109
+ /** @return the source position corresponding to a provided destination position */
110
+ destToSrc(destPos: number): SrcPosition {
111
+ const entry = this.entries.find(
112
+ e => e.destStart <= destPos && e.destEnd >= destPos,
113
+ );
114
+ if (!entry) {
115
+ return { src: this.dest, position: destPos };
116
+ }
117
+ return {
118
+ src: entry.src,
119
+ position: entry.srcStart + destPos - entry.destStart,
120
+ };
121
+ }
122
+ }
123
+
124
+ /** sort entries in place by src start position */
125
+ function sortSrc(entries: SrcMapEntry[]): void {
126
+ entries.sort((a, b) => a.srcStart - b.srcStart);
127
+ }
128
+
129
+ /** Incrementally append to a string, tracking source references */
130
+ export class SrcMapBuilder {
131
+ #fragments: string[] = [];
132
+ #destLength = 0;
133
+ #entries: SrcMapEntry[] = [];
134
+ source: SrcWithPath;
135
+
136
+ constructor(source: SrcWithPath) {
137
+ this.source = source;
138
+ }
139
+
140
+ /** append a string fragment to the destination string */
141
+ add(fragment: string, srcStart: number, srcEnd: number): void {
142
+ const destStart = this.#destLength;
143
+ this.#destLength += fragment.length;
144
+ const destEnd = this.#destLength;
145
+
146
+ this.#fragments.push(fragment);
147
+ this.#entries.push({
148
+ src: this.source,
149
+ srcStart,
150
+ srcEnd,
151
+ destStart,
152
+ destEnd,
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Append a fragment to the destination string,
158
+ * mapping source to the previous,
159
+ * and guessing that the source fragment is just as long as the dest fragment.
160
+ */
161
+ appendNext(fragment: string): void {
162
+ const lastEnd = this.#entries.at(-1)?.destEnd ?? 0;
163
+ this.add(fragment, lastEnd, lastEnd + fragment.length);
164
+ }
165
+
166
+ addSynthetic(
167
+ fragment: string,
168
+ syntheticSource: string,
169
+ srcStart: number,
170
+ srcEnd: number,
171
+ ): void {
172
+ const destStart = this.#destLength;
173
+ this.#destLength += fragment.length;
174
+ const destEnd = this.#destLength;
175
+
176
+ this.#fragments.push(fragment);
177
+ this.#entries.push({
178
+ src: { text: syntheticSource },
179
+ srcStart,
180
+ srcEnd,
181
+ destStart,
182
+ destEnd,
183
+ });
184
+ }
185
+
186
+ /** append a synthetic newline, mapped to previous source location */
187
+ addNl(): void {
188
+ const lastEntry = this.#entries.at(-1) ?? { srcStart: 0, srcEnd: 0 };
189
+ const { srcStart, srcEnd } = lastEntry;
190
+ this.add("\n", srcStart, srcEnd);
191
+ }
192
+
193
+ /** copy a string fragment from the src to the destination string */
194
+ addCopy(srcStart: number, srcEnd: number): void {
195
+ const fragment = this.source.text.slice(srcStart, srcEnd);
196
+ this.add(fragment, srcStart, srcEnd);
197
+ }
198
+
199
+ /** return a SrcMap */
200
+ static build(builders: SrcMapBuilder[]): SrcMap {
201
+ const map = new SrcMap(
202
+ { text: builders.map(b => b.#fragments.join("")).join("") },
203
+ builders.flatMap(b => b.#entries),
204
+ );
205
+ map.compact();
206
+ return map;
207
+ }
208
+ }
package/src/Stream.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { Span } from "./Span.ts";
2
+
3
+ /**
4
+ * Interface for a tokenizer. Returns a "next token", and can be reset to
5
+ * previously saved positions (checkpoints).
6
+ */
7
+ export interface Stream<T extends Token> {
8
+ /** Returns the current position */
9
+ checkpoint(): number;
10
+ /** Restores a position */
11
+ reset(position: number): void;
12
+ /**
13
+ * Returns the next token, or `null` if the end of the stream has been reached.
14
+ * Always leaves `checkpoint` right after the token.
15
+ */
16
+ nextToken(): T | null;
17
+ /** src text */
18
+ src: string;
19
+ }
20
+
21
+ /** A text token */
22
+ export interface Token {
23
+ kind: string;
24
+ text: string;
25
+ span: Span;
26
+ }
27
+
28
+ export interface TypedToken<Kind extends string> extends Token {
29
+ kind: Kind;
30
+ }
@@ -1,4 +1,3 @@
1
- import { tracing } from "mini-parse";
2
1
  import type {
3
2
  AbstractElem,
4
3
  AttributeElem,
@@ -13,6 +12,7 @@ import type {
13
12
  } from "./AbstractElems.ts";
14
13
  import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
15
14
  import { visitAst } from "./LinkerUtil.ts";
15
+ import { debug } from "./Logging.ts";
16
16
  import { findDecl } from "./LowerAndEmit.ts";
17
17
  import { minimallyMangledName } from "./Mangler.ts";
18
18
  import {
@@ -307,7 +307,7 @@ export function transformBindingReference(
307
307
  const refName = memberRef.member.name;
308
308
  const structMember = struct.members.find(m => m.name.name === refName)!;
309
309
  if (!structMember || !structMember.mangledVarName) {
310
- if (tracing) console.log(`missing mangledVarName for ${refName}`);
310
+ if (debug) console.log(`missing mangledVarName for ${refName}`);
311
311
  return { kind: "synthetic", text: refName };
312
312
  }
313
313
  const { extraComponents } = memberRef;
package/src/Util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Span } from "mini-parse";
1
+ import type { Span } from "./Span.ts";
2
2
 
3
3
  export function multiKeySet<A, B, V>(
4
4
  m: Map<A, Map<B, V>>,