Simplify parser patterns
This commit is contained in:
parent
08b4f7fcea
commit
d6eacc886b
2 changed files with 63 additions and 77 deletions
|
@ -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]];
|
||||
|
|
116
src/peg.ts
116
src/peg.ts
|
@ -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>");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue