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
This commit is contained in:
Sam Atkins
2024-05-24 11:49:56 +01:00
parent f8525c6f6c
commit 837ec68371
5 changed files with 91 additions and 41 deletions

View File

@@ -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;
};
}
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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 };
}
}
}
/**
* 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 };
}
}

View File

@@ -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 = {