@wgslx/wgslx
Version:
Extended WebGPU shading language tools
437 lines (436 loc) • 14 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = exports.symbol = exports.SymbolRule = exports.star = exports.StarRule = exports.maybe = exports.MaybeRule = exports.union = exports.UnionRule = exports.sequence = exports.SequenceRule = exports.regex = exports.RegExpRule = exports.literal = exports.LiteralRule = exports.Rule = exports.Context = exports.MatchResult = void 0;
const patterns_1 = require("./patterns");
const sequence_1 = require("./sequence");
const token_1 = require("./token");
class MatchResult {
match;
canaries = [];
clone() {
const result = new MatchResult();
if (this.match) {
result.match = {
token: this.match.token.clone(),
cursor: this.match.cursor,
};
}
result.canaries = this.canaries.map((c) => ({
rules: [...c.rules],
cursor: c.cursor,
}));
return result;
}
static success(cursor, token) {
const result = new MatchResult();
result.match = { token, cursor };
return result;
}
static successFrom(rule, match, canaries) {
const result = new MatchResult();
result.match = match;
result.canaries = this.augmentCanaries(rule, canaries, match.cursor);
return result;
}
static failure(cursor, rule) {
const result = new MatchResult();
result.canaries = [{ rules: [rule], cursor }];
return result;
}
static failureFrom(rule, canaries) {
const result = new MatchResult();
if (!canaries || canaries.length === 0) {
throw new Error('Expected canaries');
}
result.canaries = this.augmentCanaries(rule, canaries);
return result;
}
static augmentCanaries(rule, canaries, cursor) {
if (cursor) {
canaries = canaries.filter((c) => (0, sequence_1.cursorGreaterOrEqualThan)(c.cursor, cursor));
}
if (canaries.length === 0) {
return [];
}
canaries.sort((a, b) => -(0, sequence_1.compareCursor)(a.cursor, b.cursor));
let culledCanaries = [];
let canary = {
rules: [...canaries[0].rules, rule],
cursor: canaries[0].cursor,
};
for (let i = 1; i < canaries.length; i++) {
if ((0, sequence_1.compareCursor)(canaries[i].cursor, canary.cursor) === 0) {
if (canaries[i].rules.length < canary.rules.length) {
canary = {
rules: [...canaries[i].rules, rule],
cursor: canaries[i].cursor,
};
}
}
else {
culledCanaries.push(canary);
if (culledCanaries.length >= 5) {
return culledCanaries;
}
}
}
return [canary];
}
}
exports.MatchResult = MatchResult;
class Context {
sequence;
cache = new Map();
text(cursor, textRule) {
const cursorKey = this.sequence.stringify(cursor);
const textMatch = this.sequence.match(cursor, textRule.matcher);
if (!textMatch) {
return MatchResult.failure(cursor, textRule);
}
const match = MatchResult.success(textMatch.cursor, token_1.Token.text(textMatch.text, cursorKey));
return match;
}
rule(cursor, rule) {
const cursorKey = this.sequence.stringify(cursor);
const cached = this.get(cursorKey, rule);
if (cached !== undefined) {
return cached;
}
const match = rule.match(cursor, this);
this.set(cursorKey, rule, match);
return match;
}
get(cursorKey, rule) {
const cursorMap = this.cache.get(cursorKey);
if (!cursorMap) {
return undefined;
}
const cached = cursorMap.get(rule);
if (!cached) {
return undefined;
}
return cached.clone();
}
set(cursorKey, rule, matchResult) {
let cursorMap = this.cache.get(cursorKey);
if (!cursorMap) {
cursorMap = new Map();
this.cache.set(cursorKey, cursorMap);
}
cursorMap.set(rule, matchResult.clone());
}
constructor(sequence) {
this.sequence = sequence;
}
static from(text, file) {
return new Context(sequence_1.Sequence.from(text, file));
}
matchSource(rootRule) {
const cursor = (0, sequence_1.Cursor)(0);
const matchResult = rootRule.match(cursor, this);
if (matchResult.canaries) {
matchResult.canaries.sort((a, b) => -(0, sequence_1.compareCursor)(a.cursor, b.cursor));
}
if (!matchResult.match) {
return matchResult;
}
if (matchResult.match.cursor.segment < this.sequence.segments.length) {
matchResult.match = undefined;
}
return matchResult;
}
static matchSource(text, file, rootRule) {
const context = Context.from(text, file);
return context.matchSource(rootRule);
}
}
exports.Context = Context;
class Rule {
symbol;
}
exports.Rule = Rule;
function rulifyOne(rule) {
if (typeof rule === 'string') {
return literal(rule);
}
if (rule instanceof RegExp) {
return regex(rule);
}
return rule;
}
function rulifyAll(rules) {
return rules.map((r) => rulifyOne(r));
}
class LiteralRule extends Rule {
matcher;
literals;
match(cursor, context) {
return context.text(cursor, this);
}
constructor(literals) {
super();
this.matcher = (0, patterns_1.createStringTextMatcher)(...literals);
this.literals = literals;
}
}
exports.LiteralRule = LiteralRule;
function literal(...literals) {
return new LiteralRule(literals);
}
exports.literal = literal;
class RegExpRule extends Rule {
matcher;
patterns;
match(cursor, context) {
return context.text(cursor, this);
}
constructor(patterns) {
super();
this.matcher = (0, patterns_1.createRegExpTextMatcher)(...patterns);
this.patterns = patterns;
}
}
exports.RegExpRule = RegExpRule;
function regex(...patterns) {
return new RegExpRule(patterns);
}
exports.regex = regex;
class SequenceRule extends Rule {
rules;
match(cursor, context) {
const tokens = [];
const canaries = [];
for (const rule of this.rules) {
const matchResult = context.rule(cursor, rule);
canaries.push(...matchResult.canaries);
if (!matchResult.match) {
return MatchResult.failureFrom(this, canaries);
}
tokens.push(matchResult.match.token);
cursor = matchResult.match.cursor;
}
const token = token_1.Token.group(tokens, 'S');
return MatchResult.successFrom(this, {
cursor,
token,
}, canaries);
}
constructor(rules) {
super();
this.rules = rules;
}
}
exports.SequenceRule = SequenceRule;
function sequence(first, ...rest) {
const rules = rulifyAll([first, ...rest]);
if (rules.length === 0) {
throw new Error();
}
if (rules.length === 1) {
return rules[0];
}
return new SequenceRule(rules);
}
exports.sequence = sequence;
class UnionRule extends Rule {
rules;
match(cursor, context) {
let longestMatch = undefined;
let combinedCanaries = [];
for (const rule of this.rules) {
const matchResult = context.rule(cursor, rule);
combinedCanaries.push(...matchResult.canaries);
if (!matchResult.match) {
continue;
}
if (!longestMatch) {
longestMatch = matchResult.match;
continue;
}
if ((0, sequence_1.cursorGreaterThan)(matchResult.match.cursor, longestMatch.cursor)) {
longestMatch = matchResult.match;
continue;
}
}
if (!longestMatch) {
return MatchResult.failureFrom(this, combinedCanaries);
}
longestMatch.token = token_1.Token.group([longestMatch.token], 'U');
return MatchResult.successFrom(this, longestMatch, combinedCanaries);
}
constructor(rules) {
super();
this.rules = rules;
if (this.rules.length === 0) {
throw new Error('Expected at least one rule in a union.');
}
}
}
exports.UnionRule = UnionRule;
function union(first, ...rest) {
const rules = rulifyAll([first, ...rest]);
if (rules.length === 1) {
return rules[0];
}
return new UnionRule(rules);
}
exports.union = union;
class MaybeRule extends Rule {
rule;
modifiedSymbol;
match(cursor, context) {
const matchResult = context.rule(cursor, this.rule);
const token = matchResult.match ? [matchResult.match.token] : [];
const match = {
token: token_1.Token.modify(token, '?', this.modifiedSymbol),
cursor: matchResult.match ? matchResult.match.cursor : cursor,
};
return MatchResult.successFrom(this, match, matchResult.canaries);
}
constructor(rule) {
super();
this.rule = rule;
if (rule instanceof SymbolRule) {
this.modifiedSymbol = rule.symbol;
}
}
}
exports.MaybeRule = MaybeRule;
function maybe(first, ...rest) {
const rule = sequence(first, ...rest);
return new MaybeRule(rule);
}
exports.maybe = maybe;
class StarRule extends Rule {
rule;
modifiedSymbol;
match(cursor, context) {
const tokens = [];
let matchResult = context.rule(cursor, this.rule);
const canaries = [];
while (matchResult.match) {
tokens.push(matchResult.match.token);
cursor = matchResult.match.cursor;
matchResult = context.rule(cursor, this.rule);
}
const result = {
token: token_1.Token.modify(tokens, '*', this.modifiedSymbol),
cursor,
};
return MatchResult.successFrom(this, result, matchResult.canaries);
}
constructor(rule) {
super();
this.rule = rule;
if (rule instanceof SymbolRule) {
this.modifiedSymbol = rule.symbol;
}
}
}
exports.StarRule = StarRule;
function star(first, ...rest) {
const rule = sequence(first, ...rest);
return new StarRule(rule);
}
exports.star = star;
class SymbolRule extends Rule {
symbol;
leftRecursiveTail;
rule;
isLeftRecursive() {
return !!this.leftRecursiveTail;
}
get() {
return this.rule ?? null;
}
set(rule) {
if (this.rule) {
throw new Error(`Duplicate initialization of rule ${this.rule}`);
}
if (rule instanceof UnionRule) {
const tail = [];
const body = [];
for (const or of rule.rules) {
if (or instanceof SequenceRule && or.rules[0] === this) {
const [_, second, ...rest] = or.rules;
tail.push(sequence(second, ...rest));
continue;
}
body.push(or);
}
if (tail.length !== 0) {
const [tail1, ...tailRest] = tail;
this.leftRecursiveTail = union(tail1, ...tailRest);
const [body1, ...bodyRest] = body;
rule = union(body1, ...bodyRest);
}
}
this.rule = rule;
}
match(cursor, context) {
const bodyRule = this.rule;
if (!bodyRule) {
throw new Error(`Uninitialized rule ${this.symbol}`);
}
let matchResult = context.rule(cursor, bodyRule);
if (!matchResult.match) {
return MatchResult.failureFrom(this, matchResult.canaries);
}
if (!this.leftRecursiveTail) {
matchResult.match.token = token_1.Token.symbol(matchResult.match.token, this.symbol);
return MatchResult.successFrom(this, matchResult.match, matchResult.canaries);
}
const tailRule = this.leftRecursiveTail;
const tokens = [matchResult.match.token];
const canaries = [...matchResult.canaries];
let tailMatchResult = context.rule(matchResult.match.cursor, tailRule);
while (tailMatchResult.match) {
tokens.push(tailMatchResult.match.token);
canaries.push(...tailMatchResult.canaries);
matchResult = tailMatchResult;
tailMatchResult = context.rule(tailMatchResult.match.cursor, tailRule);
}
canaries.push(...tailMatchResult.canaries);
const token = token_1.Token.group(tokens, 'L', this.symbol);
return MatchResult.successFrom(this, {
token,
cursor: matchResult.match.cursor,
}, canaries);
}
constructor(name) {
super();
this.symbol = name;
}
static symbolize(rule, symbol) {
if (rule instanceof SymbolRule) {
return;
}
rule.symbol = symbol;
if (rule instanceof StarRule || rule instanceof MaybeRule) {
SymbolRule.symbolize(rule.rule, symbol);
}
if (rule instanceof SequenceRule || rule instanceof UnionRule) {
for (const subrule of rule.rules) {
SymbolRule.symbolize(subrule, symbol);
}
}
}
}
exports.SymbolRule = SymbolRule;
const registry = new Map();
function symbol(name) {
const rule = new SymbolRule(name);
if (registry.has(name)) {
throw new Error(`Duplicate named rule ${rule}`);
}
return rule;
}
exports.symbol = symbol;
function rule(name) {
const rule = registry.get(name);
if (!rule) {
throw new Error(`Duplicate named rule ${rule}`);
}
return rule;
}
exports.rule = rule;