From 837ec683718c2e537752c5237dad02a3e7fa95ca Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 11:49:56 +0100 Subject: [PATCH] Reorganize and document Parsely classes - lib.js only holds Parser so rename it parser.js - Move Symbol and None into terminals.js - Briefly document all parser classes --- packages/phoenix/packages/parsely/exports.js | 41 +++++---------- .../packages/parsely/{lib.js => parser.js} | 6 +++ .../packages/parsely/parsers/combinators.js | 32 ++++++++---- .../packages/parsely/parsers/terminals.js | 51 ++++++++++++++++++- .../puter-shell/coreutils/concept-parser.js | 2 +- 5 files changed, 91 insertions(+), 41 deletions(-) rename packages/phoenix/packages/parsely/{lib.js => parser.js} (75%) diff --git a/packages/phoenix/packages/parsely/exports.js b/packages/phoenix/packages/parsely/exports.js index dfab21853..b5c1985a9 100644 --- a/packages/phoenix/packages/parsely/exports.js +++ b/packages/phoenix/packages/parsely/exports.js @@ -1,31 +1,6 @@ -import { adapt_parser, INVALID, Parser, UNRECOGNIZED, VALUE } from './lib.js'; -import { Discard, FirstMatch, None, Optional, Repeat, Sequence } from './parsers/combinators.js'; -import { Literal, StringOf } from './parsers/terminals.js'; - -class Symbol extends Parser { - _create(symbolName) { - this.symbolName = symbolName; - } - - _parse (stream) { - const parser = this.symbol_registry[this.symbolName]; - if ( ! parser ) { - throw new Error(`No symbol defined named '${this.symbolName}'`); - } - const subStream = stream.fork(); - const result = parser.parse(subStream); - console.log(`Result of parsing symbol('${this.symbolName}'):`, result); - if ( result.status === UNRECOGNIZED ) { - return UNRECOGNIZED; - } - if ( result.status === INVALID ) { - return { status: INVALID, value: result }; - } - stream.join(subStream); - result.$ = this.symbolName; - return result; - } -} +import { adapt_parser, VALUE } from './parser.js'; +import { Discard, FirstMatch, Optional, Repeat, Sequence } from './parsers/combinators.js'; +import { Literal, None, StringOf, Symbol } from './parsers/terminals.js'; class ParserWithAction { #parser; @@ -55,6 +30,12 @@ export class GrammarContext { return new GrammarContext({...this.parsers, ...more_parsers}); } + /** + * Construct a parsing function for the given grammar. + * @param grammar An object of symbol-names to a DSL for parsing that symbol. + * @param actions An object of symbol-names to a function run to process the symbol after it has been parsed. + * @returns {function(*, *, {must_consume_all_input?: boolean}=): *} A function to run the parser. Throws if parsing fails. + */ define_parser (grammar, actions) { const symbol_registry = {}; const api = {}; @@ -81,7 +62,9 @@ export class GrammarContext { if (!entry_parser) { throw new Error(`Entry symbol '${entry_symbol}' not found in grammar.`); } - return entry_parser.parse(stream); + const result = entry_parser.parse(stream); + // TODO: Ensure all the stream has been consumed + return result; }; } } diff --git a/packages/phoenix/packages/parsely/lib.js b/packages/phoenix/packages/parsely/parser.js similarity index 75% rename from packages/phoenix/packages/parsely/lib.js rename to packages/phoenix/packages/parsely/parser.js index 89f185e03..c588ccf12 100644 --- a/packages/phoenix/packages/parsely/lib.js +++ b/packages/phoenix/packages/parsely/parser.js @@ -4,6 +4,12 @@ export const UNRECOGNIZED = Symbol('unrecognized'); export const INVALID = Symbol('invalid'); export const VALUE = Symbol('value'); +/** + * Base class for parsers. + * To implement your own, subclass it and define these methods: + * - _create(): Acts as the constructor + * - _parse(stream): Performs the parsing on the stream, and returns either UNRECOGNIZED, INVALID, or a result object. + */ export class Parser { result (o) { if (o.value && o.value.$discard) { diff --git a/packages/phoenix/packages/parsely/parsers/combinators.js b/packages/phoenix/packages/parsely/parsers/combinators.js index 54b23c058..5078b6a53 100644 --- a/packages/phoenix/packages/parsely/parsers/combinators.js +++ b/packages/phoenix/packages/parsely/parsers/combinators.js @@ -1,5 +1,9 @@ -import { INVALID, UNRECOGNIZED, VALUE, adapt_parser, Parser } from '../lib.js'; +import { adapt_parser, INVALID, Parser, UNRECOGNIZED, VALUE } from '../parser.js'; +/** + * Runs its child parser, and discards its result. + * @param parser Child parser + */ export class Discard extends Parser { _create (parser) { this.parser = adapt_parser(parser); @@ -19,6 +23,10 @@ export class Discard extends Parser { } } +/** + * Runs its child parsers in order, and returns the first successful result. + * @param parsers Child parsers + */ export class FirstMatch extends Parser { _create (...parsers) { this.parsers = parsers.map(adapt_parser); @@ -42,14 +50,10 @@ export class FirstMatch extends Parser { } } -export class None extends Parser { - _create () {} - - _parse (stream) { - return { status: VALUE, $: 'none', $discard: true }; - } -} - +/** + * Runs its child parser, and then returns its result, or nothing. + * @param parser Child parser + */ export class Optional extends Parser { _create (parser) { this.parser = adapt_parser(parser); @@ -66,6 +70,12 @@ export class Optional extends Parser { } } +/** + * Parses a repeated sequence of values with separators between them. + * @param value_parser Parser for the value + * @param separator_parser Parser for the separator + * @param trailing Whether to allow a trailing separator + */ export class Repeat extends Parser { _create (value_parser, separator_parser, { trailing = false } = {}) { this.value_parser = adapt_parser(value_parser); @@ -114,6 +124,10 @@ export class Repeat extends Parser { } } +/** + * Runs a sequence of child parsers, and returns their result as an array if they all succeed. + * @param parsers Child parsers + */ export class Sequence extends Parser { _create (...parsers) { this.parsers = parsers.map(adapt_parser); diff --git a/packages/phoenix/packages/parsely/parsers/terminals.js b/packages/phoenix/packages/parsely/parsers/terminals.js index 4a82cd147..712c688fd 100644 --- a/packages/phoenix/packages/parsely/parsers/terminals.js +++ b/packages/phoenix/packages/parsely/parsers/terminals.js @@ -1,5 +1,9 @@ -import { Parser, UNRECOGNIZED, VALUE } from '../lib.js'; +import { INVALID, Parser, UNRECOGNIZED, VALUE } from '../parser.js'; +/** + * Parses a literal value. + * @param value The value to parse + */ export class Literal extends Parser { _create (value) { this.value = value; @@ -18,6 +22,10 @@ export class Literal extends Parser { } } +/** + * Parses a string composed of the given values. + * @param values An array of strings that will be parsed as the result. + */ export class StringOf extends Parser { _create (values) { this.values = values; @@ -43,4 +51,43 @@ export class StringOf extends Parser { stream.join(subStream); return { status: VALUE, $: 'stringOf', value: text }; } -} \ No newline at end of file +} + +/** + * Parses an object defined by the symbol registry. + * @param symbolName The name of the symbol to parse. + */ +export class Symbol extends Parser { + _create(symbolName) { + this.symbolName = symbolName; + } + + _parse (stream) { + const parser = this.symbol_registry[this.symbolName]; + if ( ! parser ) { + throw new Error(`No symbol defined named '${this.symbolName}'`); + } + const subStream = stream.fork(); + const result = parser.parse(subStream); + if ( result.status === UNRECOGNIZED ) { + return UNRECOGNIZED; + } + if ( result.status === INVALID ) { + return { status: INVALID, value: result }; + } + stream.join(subStream); + result.$ = this.symbolName; + return result; + } +} + +/** + * Does no parsing and returns a discarded result. + */ +export class None extends Parser { + _create () {} + + _parse (stream) { + return { status: VALUE, $: 'none', $discard: true }; + } +} diff --git a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js index b61928f09..7036fab45 100644 --- a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js +++ b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js @@ -1,5 +1,5 @@ import { GrammarContext, standard_parsers } from '../../../packages/parsely/exports.js'; -import { Parser, UNRECOGNIZED, VALUE } from '../../../packages/parsely/lib.js'; +import { Parser, UNRECOGNIZED, VALUE } from '../../../packages/parsely/parser.js'; class NumberParser extends Parser { static data = {