UNPKG

wesl-plugin

Version:

[![NPM Version](https://img.shields.io/npm/v/wesl-plugin)](https://www.npmjs.com/package/wesl-plugin) [![Static Badge](https://img.shields.io/badge/Read%20the%20-Docs-blue)](https://wesl-lang.dev/)

511 lines (498 loc) 16 kB
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 };