@wgslx/wgslx
Version:
Extended WebGPU shading language tools
155 lines (128 loc) • 3.76 kB
text/typescript
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);
}