Simplify parser patterns

This commit is contained in:
Tangent Wantwight 2023-09-08 00:47:47 -04:00
parent 08b4f7fcea
commit d6eacc886b
2 changed files with 63 additions and 77 deletions

View file

@ -1,22 +1,8 @@
import { escapeHtml } from "./helpers";
import { escapeHtml } from './helpers';
import { AtLeast, Choose, End, Pattern, Peek, Regex, Sequence, Use } from './peg';
import {
AtLeast,
Choose,
End,
Pattern,
Peek,
Regex,
Sequence,
Use,
} from "./peg";
import {
InterpolatedPiece,
Script,
ScriptPiece,
SimplifyWord,
TextWord,
Word as WordType,
} from "./words";
InterpolatedPiece, Script, ScriptPiece, SimplifyWord, TextWord, Word as WordType
} from './words';
const Comment = Regex(/#[^\n]*/y)
.expects("#")
@ -151,7 +137,7 @@ export function parse(code: string): [true, Script] | [false, string] {
code = code.replace(/(?<!\\)((\\\\)*)\\\n[ \t]*/g, "$1 ");
/* Parse */
const [commands, errorPos, expected] = Script(code, 0);
const [commands, errorPos, expected] = Script.match(code, 0);
if (commands) {
return [true, commands[0]];

View file

@ -1,22 +1,31 @@
/**
* A Pattern is a function that matches against a string starting at a given index.
*
* A Pattern matches against a string starting at a given index, looking for a value with a particular format.
*/
export type Pattern<T> = PatternFunc<T> & {
export class Pattern<out T> {
constructor(
public match: PatternFunc<T>,
/** A human-readable annotation describing the pattern for error messages */
public expectLabel: string = match.name
) {}
/**
* Creates a pattern that wraps another pattern, transforming the returned value on a match.
*
* @param map - Mapping function
*/
map<U>(map: (value: T) => U): Pattern<U>;
/** A human-readable annotation describing the pattern for error messages */
expectLabel: string;
public map<U>(map: (value: T) => U): Pattern<U> {
return new Pattern((source, index) => {
const [value, furthest, expected] = this.match(source, index);
return [value ? [map(value[0]), value[1]] : null, furthest, expected];
}, this.expectLabel);
}
/** Adds a human-readable annotation describing the pattern */
expects(label: string): Pattern<T>;
};
type PatternFunc<T> = {
public expects(label: string): Pattern<T> {
return new Pattern(this.match, label);
}
}
type PatternFunc<out T> = {
/**
* If the pattern matches successfully, it returns some captured value, and the index following the match.
*
@ -30,33 +39,13 @@ type PatternFunc<T> = {
* @param index - the index in the string to begin matching from
* @returns - [successValue, furthest symbol attempted, expected pattern]
*/
(source: string, index: number): [[T, number] | null, number, string];
(this: Pattern<T>, source: string, index: number): [
[T, number] | null,
number,
string
];
};
/**
* Makes a pattern from a function, adding helper methods.
*
* @param {(source: string, index: number) => ([[T, number] | null, number, string])} matchFunc
* @returns {Peg.Pattern<T>}
*/
function WrapPattern<T>(matchFunc: PatternFunc<T>) {
const pattern = matchFunc as Pattern<T>;
pattern.map = (map) => {
return WrapPattern((source, index) => {
const [value, furthest, expected] = pattern(source, index);
return [value ? [map(value[0]), value[1]] : null, furthest, expected];
}).expects(pattern.expectLabel);
};
pattern.expectLabel = pattern.name;
pattern.expects = (label) => {
pattern.expectLabel = label;
return pattern;
};
return pattern;
}
/**
* Proxies to a pattern retrieved from an accessor function.
*
@ -65,7 +54,8 @@ function WrapPattern<T>(matchFunc: PatternFunc<T>) {
* @param getPattern
*/
export function Use<T>(getPattern: () => Pattern<T>): Pattern<T> {
return WrapPattern((source, index) => getPattern()(source, index)).expects(
return new Pattern(
(source, index) => getPattern().match(source, index),
String(getPattern)
);
}
@ -74,14 +64,13 @@ export function Use<T>(getPattern: () => Pattern<T>): Pattern<T> {
* Creates a pattern matching a regex & returning any captures. The regex needs to be sticky (using the //y modifier)
*/
export function Regex(regex: RegExp): Pattern<RegExpExecArray> {
const pattern: Pattern<RegExpExecArray> = WrapPattern((source, index) => {
return new Pattern<RegExpExecArray>(function (source, index) {
regex.lastIndex = index;
const matches = regex.exec(source);
return matches
? [[matches, regex.lastIndex], -1, pattern.expectLabel]
: [null, index, pattern.expectLabel];
}).expects(regex.source);
return pattern;
? [[matches, regex.lastIndex], -1, this.expectLabel]
: [null, index, this.expectLabel];
}, regex.source);
}
/**
@ -93,11 +82,15 @@ export function Choose<T>(...patterns: Pattern<T>[]): Pattern<T> {
const genericExpected = patterns
.map((pattern) => pattern.expectLabel)
.join(" | ");
return WrapPattern((source, index) => {
return new Pattern(function (source, index) {
let furthestFound = index;
let furthestExpected = genericExpected;
for (const pattern of patterns) {
const [value, furthest, expected] = pattern(source, index);
const [value, furthest, expected] = pattern.match.call(
this,
source,
index
);
if (value) {
return [value, furthest, expected];
} else if (furthest > furthestFound) {
@ -108,7 +101,7 @@ export function Choose<T>(...patterns: Pattern<T>[]): Pattern<T> {
}
}
return [null, furthestFound, furthestExpected];
}).expects(genericExpected);
}, genericExpected);
}
/**
@ -121,12 +114,16 @@ export function Sequence<T extends unknown[]>(
...patterns: { [K in keyof T]: Pattern<T[K]> }
): Pattern<T> {
const genericExpected = patterns[0]?.expectLabel ?? "(nothing)";
return WrapPattern((source, index) => {
return new Pattern(function (source, index) {
const values: unknown[] = [];
let furthestFound = index;
let furthestExpected = genericExpected;
for (const pattern of patterns) {
const [value, furthest, expected] = pattern(source, index);
const [value, furthest, expected] = pattern.match.call(
this,
source,
index
);
if (furthest > furthestFound) {
furthestFound = furthest;
furthestExpected = expected;
@ -140,7 +137,7 @@ export function Sequence<T extends unknown[]>(
index = value[1];
}
return [[values as T, index], furthestFound, furthestExpected];
}).expects(genericExpected);
}, genericExpected);
}
/**
@ -155,12 +152,16 @@ export function Sequence<T extends unknown[]>(
*/
export function AtLeast<T>(min: number, pattern: Pattern<T>): Pattern<T[]> {
return WrapPattern(function (source, index) {
return new Pattern(function (source, index) {
const values: T[] = [];
let furthestFound = index;
let furthestExpected = pattern.expectLabel;
let furthestExpected = this.expectLabel;
do {
const [value, furthest, expected] = pattern(source, index);
const [value, furthest, expected] = pattern.match.call(
this as Pattern<T>,
source,
index
);
if (furthest > furthestFound) {
furthestFound = furthest;
furthestExpected = expected;
@ -179,29 +180,28 @@ export function AtLeast<T>(min: number, pattern: Pattern<T>): Pattern<T[]> {
} else {
return [null, furthestFound, furthestExpected];
}
}).expects(pattern.expectLabel);
}, pattern.expectLabel);
}
/**
* Creates a pattern that matches the given pattern, but consumes no input.
*/
export function Peek<T>(pattern: Pattern<T>): Pattern<T> {
return WrapPattern(function (source, index) {
const [value, furthest, expected] = pattern(source, index);
return new Pattern(function (source, index) {
const [value, furthest, expected] = pattern.match.call(this, source, index);
return [value ? [value[0], index] : null, furthest, expected];
}).expects(pattern.expectLabel);
}, pattern.expectLabel);
}
/**
* Creates a pattern that matches the end of input
*/
export function End(): Pattern<true> {
const end = WrapPattern(function End(source, index) {
return new Pattern(function End(source, index) {
return [
source.length == index ? [true, index] : null,
index,
end.expectLabel,
this.expectLabel,
];
}).expects("<eof>") as Pattern<true>;
return end;
}, "<eof>");
}