diff --git a/src/parser.ts b/src/parser.ts index 8b2bb2d..baabed9 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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(/(? = PatternFunc & { +export class Pattern { + constructor( + public match: PatternFunc, + /** 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(map: (value: T) => U): Pattern; - - /** A human-readable annotation describing the pattern for error messages */ - expectLabel: string; + public map(map: (value: T) => U): Pattern { + 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; -}; -type PatternFunc = { + public expects(label: string): Pattern { + return new Pattern(this.match, label); + } +} +type PatternFunc = { /** * If the pattern matches successfully, it returns some captured value, and the index following the match. * @@ -30,33 +39,13 @@ type PatternFunc = { * @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, 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} - */ -function WrapPattern(matchFunc: PatternFunc) { - const pattern = matchFunc as Pattern; - 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(matchFunc: PatternFunc) { * @param getPattern */ export function Use(getPattern: () => Pattern): Pattern { - 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(getPattern: () => Pattern): Pattern { * 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 { - const pattern: Pattern = WrapPattern((source, index) => { + return new Pattern(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(...patterns: Pattern[]): Pattern { 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(...patterns: Pattern[]): Pattern { } } return [null, furthestFound, furthestExpected]; - }).expects(genericExpected); + }, genericExpected); } /** @@ -121,12 +114,16 @@ export function Sequence( ...patterns: { [K in keyof T]: Pattern } ): Pattern { 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( index = value[1]; } return [[values as T, index], furthestFound, furthestExpected]; - }).expects(genericExpected); + }, genericExpected); } /** @@ -155,12 +152,16 @@ export function Sequence( */ export function AtLeast(min: number, pattern: Pattern): Pattern { - 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, + source, + index + ); if (furthest > furthestFound) { furthestFound = furthest; furthestExpected = expected; @@ -179,29 +180,28 @@ export function AtLeast(min: number, pattern: Pattern): Pattern { } 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(pattern: Pattern): Pattern { - 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 { - 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("") as Pattern; - return end; + }, ""); }