wesl-plugin
Version:
[](https://www.npmjs.com/package/wesl-plugin) [](https://wesl-lang.dev/)
511 lines (498 loc) • 16 kB
TypeScript
import "unplugin";
import { Span } from "mini-parse";
//#region ../wesl/src/LiveDeclarations.d.ts
/** decls currently visible in this scope */
/** decls currently visible in this scope */
interface LiveDecls {
/** decls currently visible in this scope */
decls: Map<string, DeclIdent>;
/** live decls in the parent scope. null for the modue root scope */
parent?: LiveDecls | null;
}
//#endregion
//#region ../wesl/src/ParseWESL.d.ts
/** create a LiveDecls */
/** result of a parse for one wesl module (e.g. one .wesl file)
*
* The parser constructs the AST constructed into three sections
* for convenient access by the binding stage.
* - import statements
* - language elements (fn, struct, etc)
* - scopes
*
*/
interface WeslAST {
/** source text for this module */
srcModule: SrcModule;
/** root module element */
moduleElem: ModuleElem;
/** root scope for this module */
rootScope: Scope;
/** imports found in this module */
imports: ImportStatement[];
/** module level const_assert statements */
moduleAsserts?: ConstAssertElem[];
}
//#endregion
//#region ../wesl/src/Scope.d.ts
/** an extended version of the AST */
interface SrcModule {
/** module path "rand_pkg::sub::foo", or "package::main" */
modulePath: string;
/** file path to the module for user error reporting e.g "rand_pkg:sub/foo.wesl", or "./sub/foo.wesl" */
debugFilePath: string;
/** original src for module */
src: string;
}
/** a src declaration or reference to an ident */
type Ident = DeclIdent | RefIdent;
/** LATER change this to a Map, so that `toString` isn't accidentally a condition */
interface IdentBase {
originalName: string;
id?: number;
}
interface RefIdent extends IdentBase {
kind: "ref";
refersTo?: Ident;
std?: true;
ast: WeslAST;
refIdentElem: RefIdentElem;
}
interface DeclIdent extends IdentBase {
kind: "decl";
/** name in the output code */
mangledName?: string;
/** link to AST so that we can traverse scopes and know what elems to emit */
declElem?: DeclarationElem;
/** scope in which this declaration is found */
containingScope: Scope;
/** scope for the references within this declaration
* (only needed for global decls.)
* if this decl is included in the link, dependentScope holds other refIdents that should be included too */
dependentScope?: Scope;
/** true if this is a global declaration (e.g. not a local variable) */
isGlobal: boolean;
/** To figure out which module this declaration is from. */
srcModule: SrcModule;
}
/** tree of ident references, organized by lexical scope and partialScope . */
type Scope = LexicalScope | PartialScope;
/** A wgsl scope */
interface LexicalScope extends ScopeBase {
kind: "scope";
/** @if condition for conditionally translating this scope */
ifAttribute?: IfAttribute;
/**
* Efficient access to declarations in this scope.
* constructed on demand, for module root scopes only */
_scopeDecls?: LiveDecls;
}
/** A synthetic partial scope to contain @if conditioned idents.
* PartialScope idents are considered to be in the wgsl lexical scope of their parent. */
interface PartialScope extends ScopeBase {
kind: "partial";
/** @if condition for conditionally translating this scope */
ifAttribute?: IfAttribute;
}
/** common scope elements */
interface ScopeBase {
/** id for debugging */
id: number;
/** null for root scope in a module */
parent: Scope | null;
contents: (Ident | Scope)[];
/** @if conditions for conditionally translating this scope */
ifAttribute?: IfAttribute;
}
//#endregion
//#region ../wesl/src/AbstractElems.d.ts
/** Combine two scope siblings.
* The first scope is mutated to append the contents of the second. */
/**
* Structures to describe the 'interesting' parts of a WESL source file.
*
* The parts of the source that need to analyze further in the linker
* are pulled out into these structures.
*
* The parts that are uninteresting the the linker are recorded
* as 'TextElem' nodes, which are generally just copied to the output WGSL
* along with their containing element.
*/
type AbstractElem = GrammarElem | SyntheticElem;
type GrammarElem = ContainerElem | TerminalElem;
type ContainerElem = AttributeElem | AliasElem | ConstAssertElem | ConstElem | UnknownExpressionElem | SimpleMemberRef | FnElem | TypedDeclElem | GlobalVarElem | LetElem | ModuleElem | OverrideElem | FnParamElem | StructElem | StructMemberElem | StuffElem | TypeRefElem | VarElem | StatementElem | SwitchClauseElem;
/** Inspired by https://github.com/wgsl-tooling-wg/wesl-rs/blob/3b2434eac1b2ebda9eb8bfb25f43d8600d819872/crates/wgsl-parse/src/syntax.rs#L364 */
type ExpressionElem = Literal | TranslateTimeFeature | RefIdentElem | ParenthesizedExpression | ComponentExpression | ComponentMemberExpression | UnaryExpression | BinaryExpression | FunctionCallExpression;
type TerminalElem = DirectiveElem | DeclIdentElem | NameElem | RefIdentElem | TextElem | ImportElem;
type GlobalDeclarationElem = AliasElem | ConstElem | FnElem | GlobalVarElem | OverrideElem | StructElem;
type DeclarationElem = GlobalDeclarationElem | FnParamElem | VarElem;
interface AbstractElemBase {
kind: AbstractElem["kind"];
start: number;
end: number;
}
interface ElemWithContentsBase extends AbstractElemBase {
contents: AbstractElem[];
}
interface HasAttributes {
attributes?: AttributeElem[];
}
/**
* a raw bit of text in WESL source that's typically copied to the linked WGSL.
* e.g. a keyword like 'var'
* or a phrase we needn't analyze further like '@diagnostic(off,derivative_uniformity)'
*/
interface TextElem extends AbstractElemBase {
kind: "text";
srcModule: SrcModule;
}
/** a name that doesn't need to be an Ident
* e.g.
* - a struct member name
* - a diagnostic rule name
* - an enable-extension name
* - an interpolation sampling name
*/
interface NameElem extends AbstractElemBase {
kind: "name";
name: string;
}
/** an identifier that 'refers to' a declaration (aka a symbol reference) */
interface RefIdentElem extends AbstractElemBase {
kind: RefIdent["kind"];
ident: RefIdent;
srcModule: SrcModule;
}
/** a declaration identifier (aka a symbol declaration) */
interface DeclIdentElem extends AbstractElemBase {
kind: DeclIdent["kind"];
ident: DeclIdent;
srcModule: SrcModule;
}
/** Holds an import statement, and has a span */
interface ImportElem extends AbstractElemBase {
kind: "import";
imports: ImportStatement;
}
/**
* An import statement, which is tree shaped.
* `import foo::bar::{baz, cat as neko};
*/
interface ImportStatement {
kind: "import-statement";
segments: ImportSegment[];
finalSegment: ImportCollection | ImportItem;
}
/**
* A collection of import trees.
* `{baz, cat as neko}`
*/
interface ImportSegment {
kind: "import-segment";
name: string;
}
/**
* A primitive segment in an import statement.
* `foo`
*/
interface ImportCollection {
kind: "import-collection";
subtrees: ImportStatement[];
}
/**
* A renamed item at the end of an import statement.
* `cat as neko`
*/
interface ImportItem {
kind: "import-item";
name: string;
as?: string;
}
/** generated element, produced after parsing and binding */
interface SyntheticElem {
kind: "synthetic";
text: string;
}
/** a declaration identifer with a possible type */
interface TypedDeclElem extends ElemWithContentsBase {
kind: "typeDecl";
decl: DeclIdentElem;
typeRef?: TypeRefElem;
typeScope?: Scope;
}
/** an alias statement */
interface AliasElem extends ElemWithContentsBase, HasAttributes {
kind: "alias";
name: DeclIdentElem;
typeRef: TypeRefElem;
}
/** an attribute like '@compute' or '@binding(0)' */
interface AttributeElem extends ElemWithContentsBase {
kind: "attribute";
attribute: Attribute;
}
type Attribute = StandardAttribute | InterpolateAttribute | BuiltinAttribute | DiagnosticAttribute | IfAttribute;
interface StandardAttribute {
kind: "@attribute";
name: string;
params?: UnknownExpressionElem[];
}
interface InterpolateAttribute {
kind: "@interpolate";
params: NameElem[];
}
interface BuiltinAttribute {
kind: "@builtin";
param: NameElem;
}
interface DiagnosticAttribute {
kind: "@diagnostic";
severity: NameElem;
rule: [NameElem, NameElem | null];
}
interface IfAttribute {
kind: "@if";
param: TranslateTimeExpressionElem;
}
/** a const_assert statement */
interface ConstAssertElem extends ElemWithContentsBase, HasAttributes {
kind: "assert";
}
/** a const declaration */
interface ConstElem extends ElemWithContentsBase, HasAttributes {
kind: "const";
name: TypedDeclElem;
}
/** an expression w/o special handling, used inside attribute parameters */
interface UnknownExpressionElem extends ElemWithContentsBase {
kind: "expression";
}
/** an expression that can be safely evaluated at compile time */
interface TranslateTimeExpressionElem {
kind: "translate-time-expression";
expression: ExpressionElem;
span: Span;
}
/** A literal value in WESL source. A boolean or a number. */
interface Literal {
kind: "literal";
value: string;
span: Span;
}
/** `words`s inside `@if` */
interface TranslateTimeFeature {
kind: "translate-time-feature";
name: string;
span: Span;
}
/** (expr) */
interface ParenthesizedExpression {
kind: "parenthesized-expression";
expression: ExpressionElem;
}
/** `foo[expr]` */
interface ComponentExpression {
kind: "component-expression";
base: ExpressionElem;
access: ExpressionElem;
}
/** `foo.member` */
interface ComponentMemberExpression {
kind: "component-member-expression";
base: ExpressionElem;
access: NameElem;
}
/** `+foo` */
interface UnaryExpression {
kind: "unary-expression";
operator: UnaryOperator;
expression: ExpressionElem;
}
/** `foo + bar` */
interface BinaryExpression {
kind: "binary-expression";
operator: BinaryOperator;
left: ExpressionElem;
right: ExpressionElem;
}
/** `foo(arg, arg)` */
interface FunctionCallExpression {
kind: "call-expression";
function: RefIdentElem;
arguments: ExpressionElem[];
}
interface UnaryOperator {
value: "!" | "&" | "*" | "-" | "~";
span: Span;
}
interface BinaryOperator {
value: ("||" | "&&" | "+" | "-" | "*" | "/" | "%" | "==") | ("!=" | "<" | "<=" | ">" | ">=" | "|" | "&" | "^") | ("<<" | ">>");
span: Span;
}
type DirectiveVariant = DiagnosticDirective | EnableDirective | RequiresDirective;
interface DirectiveElem extends AbstractElemBase, HasAttributes {
kind: "directive";
directive: DirectiveVariant;
}
interface DiagnosticDirective {
kind: "diagnostic";
severity: NameElem;
rule: [NameElem, NameElem | null];
}
interface EnableDirective {
kind: "enable";
extensions: NameElem[];
}
interface RequiresDirective {
kind: "requires";
extensions: NameElem[];
}
/** a function declaration */
interface FnElem extends ElemWithContentsBase, HasAttributes {
kind: "fn";
name: DeclIdentElem;
params: FnParamElem[];
body: StatementElem;
returnAttributes?: AttributeElem[];
returnType?: TypeRefElem;
}
/** a global variable declaration (at the root level) */
interface GlobalVarElem extends ElemWithContentsBase, HasAttributes {
kind: "gvar";
name: TypedDeclElem;
}
/** an entire file */
interface ModuleElem extends ElemWithContentsBase {
kind: "module";
}
/** an override declaration */
interface OverrideElem extends ElemWithContentsBase, HasAttributes {
kind: "override";
name: TypedDeclElem;
}
/** a parameter in a function declaration */
interface FnParamElem extends ElemWithContentsBase, HasAttributes {
kind: "param";
name: TypedDeclElem;
}
/** simple references to structures, like myStruct.bar
* (used for transforming refs to binding structs) */
interface SimpleMemberRef extends ElemWithContentsBase {
kind: "memberRef";
name: RefIdentElem;
member: NameElem;
extraComponents?: StuffElem;
}
/** a struct declaration */
interface StructElem extends ElemWithContentsBase, HasAttributes {
kind: "struct";
name: DeclIdentElem;
members: StructMemberElem[];
bindingStruct?: true;
}
/** generic container of other elements */
interface StuffElem extends ElemWithContentsBase {
kind: "stuff";
}
/** a struct declaration that's been marked as a bindingStruct */
/** a member of a struct declaration */
interface StructMemberElem extends ElemWithContentsBase, HasAttributes {
kind: "member";
name: NameElem;
typeRef: TypeRefElem;
mangledVarName?: string;
}
type TypeTemplateParameter = TypeRefElem | UnknownExpressionElem;
/** a reference to a type, like 'f32', or 'MyStruct', or 'ptr<storage, array<f32>, read_only>' */
interface TypeRefElem extends ElemWithContentsBase {
kind: "type";
name: RefIdent;
templateParams?: TypeTemplateParameter[];
}
/** a variable declaration */
interface VarElem extends ElemWithContentsBase, HasAttributes {
kind: "var";
name: TypedDeclElem;
}
interface LetElem extends ElemWithContentsBase, HasAttributes {
kind: "let";
name: TypedDeclElem;
}
interface StatementElem extends ElemWithContentsBase, HasAttributes {
kind: "statement";
}
interface SwitchClauseElem extends ElemWithContentsBase, HasAttributes {
kind: "switch-clause";
}
//#endregion
//#region ../wesl/src/ParsedRegistry.d.ts
interface ParsedRegistry {
modules: Record<string, WeslAST>;
}
//#endregion
//#region ../wesl/src/Linker.d.ts
type LinkerTransform = (boundAST: TransformedAST) => TransformedAST;
interface WeslJsPlugin {
transform?: LinkerTransform;
}
interface TransformedAST extends Pick<WeslAST, "srcModule" | "moduleElem"> {
globalNames: Set<string>;
notableElems: Record<string, AbstractElem[]>;
}
//#endregion
//#region src/WeslPluginOptions.d.ts
interface WeslPluginOptions {
weslToml?: string;
extensions?: PluginExtension[];
}
//#endregion
//#region src/WeslPlugin.d.ts
/** loaded (or synthesized) info from .toml */
interface WeslToml {
/** glob search strings to find .wesl/.wgsl files. Relative to the toml directory. */
weslFiles: string[];
/** base directory for wesl files. Relative to the toml directory. */
weslRoot: string;
/** names of directly referenced wesl shader packages (e.g. npm package names) */
dependencies?: string[];
}
interface WeslTomlInfo {
/** The path to the toml file, relative to the cwd, undefined if no toml file */
tomlFile: string | undefined;
/** The absolute path to the directory that contains the toml.
* Paths inside the toml are relative to this. */
tomlDir: string;
/** The wesl root, relative to the cwd.
* This lets us correctly do `path.resolve(resolvedWeslRoot, someShaderFile)` */
resolvedWeslRoot: string;
/** The underlying toml file */
toml: WeslToml;
}
//#endregion
//#region src/PluginExtension.d.ts
/** internal cache used by the plugin to avoid reloading files
* The assumption is that the plugin is used for a single wesl.toml and set of shaders
* (a plugin instance supports only one shader project)
*/
/** function type required for for emit extensions */
type ExtensionEmitFn = (/** absolute path to the shader to which the extension is attached */
shaderPath: string, /** support functions available to plugin extensions */
pluginApi: PluginExtensionApi, /** static conditions specified on the js import */conditions?: Record<string, boolean>) => Promise<string>;
/** an extension that runs inside the wesl-js build plugin */
interface PluginExtension extends WeslJsPlugin {
/** javascript imports with this suffix will trigger the plugin */
extensionName: string;
/** generate javascript text for js/ts importers to use.
* e.g. import myPluginJs from "./foo.wesl?myPlugin"; */
emitFn: ExtensionEmitFn;
}
/** api supplied to plugin extensions */
interface PluginExtensionApi {
weslToml: () => Promise<WeslTomlInfo>;
weslSrc: () => Promise<Record<string, string>>;
weslRegistry: () => Promise<ParsedRegistry>;
weslMain: (baseId: string) => Promise<string>;
weslDependencies: () => Promise<string[]>;
}
//#endregion
export { ExtensionEmitFn, PluginExtension, PluginExtensionApi, WeslPluginOptions };