UNPKG

@wgslx/wgslx

Version:

Extended WebGPU shading language tools

155 lines (128 loc) 3.76 kB
import {SymbolRule} from './rules'; import {Token} from './token'; type FlexSymbol = string | SymbolRule | Token; function symbolName(symbol: FlexSymbol): string { if (typeof symbol === 'string') { return symbol; } if (symbol instanceof Token) { return symbol.symbol ?? ''; } return symbol.symbol; } function symbolNames(symbols: FlexSymbol[]): string[] { return symbols.map((s) => symbolName(s)); } export function isType(token: Token, symbol: FlexSymbol): Token | null { const name = symbolName(symbol); while (token.symbol === undefined && token.children?.length === 1) { token = token.children[0]; } if (token.symbol === name) { return token; } return null; } export function assertType(token: Token, symbol: FlexSymbol) { if (token.symbol !== symbolName(symbol)) { throw new Error('Type'); } } export function ofType(...symbols: FlexSymbol[]) { const names = symbolNames(symbols); return (token: Token): boolean => { if (token.symbol) { const symbols = token.symbol; return names.some((n) => symbols.includes(n)); } return false; }; } export function childrenOfType(token: Token, symbols: FlexSymbol[]): Token[] { if (!token.children || symbols.length === 0) { return []; } const predicate = ofType(...symbols); return token.children.flatMap((child) => { if (child.symbol) { return predicate(child) ? [child] : []; } else { return childrenOfType(child, symbols); } }); } interface PreorderResponse { /** Whether to continue */ continue?: boolean; /** Override set of children to traverse. */ children?: null | Token | Token[]; /** * Cleanup callback to call after children have been traversed, but before * the afterCallback is called. */ cleanupCallback?: PostorderCallback; } type PreorderCallback = ( token: Token, ancestors: Token[], index: number ) => PreorderResponse | undefined | void; type PostorderCallback = ( token: Token, ancestors: Token[], index: number ) => void; interface TraversalOptions { predicate?: (t: Token) => boolean; preorderCallback?: PreorderCallback; postorderCallback?: PostorderCallback; } function _traverse( tokens: Token[], ancestors: Token[], options: TraversalOptions ) { for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; const match = options.predicate?.(token) ?? true; let preorderResponse: PreorderResponse | undefined = undefined; if (match && options.preorderCallback) { const result = options.preorderCallback(token, ancestors, i); if (result) { if (result.continue === false) { result.cleanupCallback?.(token, ancestors, i); continue; } preorderResponse = result; } } if (preorderResponse?.children !== undefined) { if (preorderResponse.children !== null) { _traverse( Array.isArray(preorderResponse.children) ? preorderResponse.children : [preorderResponse.children], [...ancestors, token], options ); } } else if (token.children) { _traverse(token.children, [...ancestors, token], options); } if (preorderResponse?.cleanupCallback) { preorderResponse.cleanupCallback(token, ancestors, i); } if (match && options.postorderCallback) { options.postorderCallback(token, ancestors, i); } } } export function traverse(token: Token | Token[], options: TraversalOptions) { if (!options.preorderCallback && !options.postorderCallback) { throw new Error('At leat one callback must be provided.'); } if (!Array.isArray(token)) { token = [token]; } _traverse(token, [], options); }