Skip to content

Commit

Permalink
add match expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Joseph Masom committed Jun 7, 2024
1 parent 27485b0 commit 85d5f3b
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 17 deletions.
29 changes: 29 additions & 0 deletions src/logic/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Rule as ConfigRule} from "../models/ui.tsx";
import {
Expr, Grammar,
makeAtom, makeGrammar,
makeMatch,
makeRange,
makeRef, makeRule,
makeSeq,
Expand Down Expand Up @@ -83,6 +84,32 @@ export class Parser {
return makeRef(refName);
}

private parseMatch(): Expr {
let indexString = ''

if (!this.consume('#')) {
throw new Error("Expected number sign to start match");
}

while (this.hasMoreInput()) {
const char = this.peek()!;

if (char === '#') {
break
} else {
// Part of the index; append the character and advance
indexString += char;
this.advancePosition(false);
}
}

if (!this.consume('#')) {
throw new Error("Expected number sign to end match");
}

return makeMatch(parseInt(indexString));
}

private isRefCharacter(char: string | null, lead: boolean | null = null): boolean {
if (!char) {
return false
Expand Down Expand Up @@ -170,6 +197,8 @@ export class Parser {
return this.parseOption();
} else if (char === "[") {
return this.parseGroup();
} else if (char === "#") {
return this.parseMatch();
} else if (this.isRefCharacter(char)) {
return this.parseRef();
} else {
Expand Down
47 changes: 33 additions & 14 deletions src/logic/wordgen.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,49 @@
import findDirectedCycle from "find-cycle/directed"
import {Expr, Grammar, Rule} from "../models/grammar.ts";

const matchSeq = (g: Grammar, items: Expr[], s: string): number | null => {
if (items.length === 0) return 0;
const matchSeq = (g: Grammar, items: Expr[], s: string): number[] | null => {
if (items.length === 0) return [0];

for (let i = 1; i <= s.length; i++) {
const sub = s.substring(0, i);
const exprMatch = matchExpr(g, items[0], sub);
if (exprMatch !== null) {
const restMatch = matchSeq(g, items.slice(1), s.substring(exprMatch));
if (restMatch !== null) return exprMatch + restMatch;
const restMatch = matchSeq(g, items.slice(1), s.substring(exprMatch[0]));
if (restMatch !== null) return [...exprMatch, ...restMatch];
}
}

return null;
};

const matchChoice = (g: Grammar, choices: Expr[], s: string): number | null => {
const matchChoice = (g: Grammar, choices: Expr[], s: string): number[] | null => {
for (const e of choices) {
const match = matchExpr(g, e, s);
if (match !== null) return match;
}
return null;
};

const matchQuantifier = (g: Grammar, expr: Expr, min: number, max: number, s: string): number | null => {
const matchQuantifier = (g: Grammar, expr: Expr, min: number, max: number, s: string): number[] | null => {
for (let i = min; i <= max; ++i) {
const match = matchSeq(g, Array(i).fill(expr), s)
if (match !== null) return match;
}
return null
};

const matchExpr = (g: Grammar, e: Expr, s: string): number | null => {
const matchExpr = (g: Grammar, e: Expr, s: string): number[] | null => {
switch (e.tag) {
case 'Atom':
return s === e.value ? e.value.length : null
return s === e.value ? [e.value.length] : null
case 'Ref': {
const rule = g.rules.find(r => r.name === e.rule);
if (!rule) throw new Error(`Rule ${e.rule} not found`)
return matchExpr(g, rule.expr, s)
}
case 'Match': {
throw new Error(`Encountered ambiguous match expression #${e.index}#`)
}
case "Seq":
return matchSeq(g, e.items, s)
case "Choice":
Expand Down Expand Up @@ -97,7 +100,7 @@ const weightedPick = <T>(array: [T, number][]): T | null => {
return array[array.length - 1][0];
};

const generateExpr = (g: Grammar, e: Expr): string => {
const generateExpr = (g: Grammar, e: Expr, matches?: string[]): string => {
switch (e.tag) {
case 'Atom':
return e.value;
Expand All @@ -106,8 +109,13 @@ const generateExpr = (g: Grammar, e: Expr): string => {
if (!rule) throw new Error(`Rule ${e.rule} not found`);
return generateRule(g, rule);
}
case 'Match': {
if (matches === undefined)
throw new Error(`Encountered ambiguous match expression #${e.index}#`);
return matches[e.index];
}
case "Seq":
return e.items.map(t => generateExpr(g, t)).join('')
return e.items.map(t => generateExpr(g, t, matches)).join('')
case "Choice": {
const choice = g.useWeights ?
weightedPick(e.items.map(c => [c.expr, c.weight])) :
Expand All @@ -117,13 +125,13 @@ const generateExpr = (g: Grammar, e: Expr): string => {
return ""
}

return generateExpr(g, choice)
return generateExpr(g, choice, matches)
}
case "Quantifier": {
const n = Math.floor(Math.random() * (1 + e.max - e.min) + e.min)
let s = ""
for (let i = 0; i < n; i++) {
s += generateExpr(g, e.expr)
s += generateExpr(g, e.expr, matches)
}
return s
}
Expand Down Expand Up @@ -155,9 +163,16 @@ const applyRuleRewrites = (g: Grammar, rule: Rule, gen: string): string => {
const s = result.slice(i, j);
const exprMatch = matchExpr(g, match, s);
if (exprMatch !== null) {
let matchEnd = 0;
const matches = exprMatch.map(l => {
const newMatchEnd = matchEnd + l;
const match = s.slice(matchEnd, newMatchEnd);
matchEnd = newMatchEnd;
return match;
});
const head = result.slice(0, i);
const tail = result.slice(i + exprMatch);
const gen = generateExpr(g, replace);
const tail = result.slice(i + matchEnd);
const gen = generateExpr(g, replace, matches);

result = head + gen + tail;
}
Expand Down Expand Up @@ -188,6 +203,8 @@ const countCombinationsExpr = (g: Grammar, e: Expr): number => {
return 1;
case "Ref":
return countCombinationsRule(g, g.rules.find(r => r.name === e.rule)!);
case "Match":
throw new Error('Match syntax used outside replacement expression');
case "Seq":
return e.items.reduce((acc, it) => acc * countCombinationsExpr(g, it), 1);
case "Choice":
Expand Down Expand Up @@ -215,6 +232,8 @@ const getExprEdges = (e: Expr): string[] => {
return [];
case "Ref":
return [e.rule];
case "Match":
return [];
case "Seq":
return e.items.flatMap(getExprEdges);
case "Choice":
Expand Down
8 changes: 5 additions & 3 deletions src/models/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type Rule = {
export type Expr =
| { tag: 'Atom', value: string }
| { tag: 'Ref', rule: string }
| { tag: 'Match', index: number }
| { tag: 'Seq', items: Expr[] }
| { tag: 'Choice', items: WeightedExpr[] }
| { tag: 'Quantifier', expr: Expr, min: number, max: number }
Expand All @@ -25,9 +26,10 @@ export type WeightedExpr = {

// Utility functions

export const makeAtom = (atom: string): Expr => ({tag: "Atom", value: atom});
export const makeRef = (rule: string): Expr => ({tag: "Ref", rule: rule});
export const makeSeq = (items: Expr[]): Expr => ({tag: "Seq", items: items});
export const makeAtom = (value: string): Expr => ({tag: "Atom", value});
export const makeRef = (rule: string): Expr => ({tag: "Ref", rule});
export const makeMatch = (index: number): Expr => ({tag: "Match", index});
export const makeSeq = (items: Expr[]): Expr => ({tag: "Seq", items});

export const makeWeighted = (expr: Expr, weight: number | null): WeightedExpr => ({expr: expr, weight: weight ?? 1.0})
export const makeChoice = (items: Expr[]): Expr => ({tag: "Choice", items: items.map(makeWeighted)});
Expand Down

0 comments on commit 85d5f3b

Please sign in to comment.