From f8525c6f6c3c96b7794c0ba265c6f8a305712a47 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 10:28:22 +0100 Subject: [PATCH 1/7] Rename newparser to Parsely We're going to actually use this so let's give it a more permanent name. --- packages/phoenix/packages/{newparser => parsely}/exports.js | 0 packages/phoenix/packages/{newparser => parsely}/lib.js | 0 .../packages/{newparser => parsely}/parsers/combinators.js | 0 .../packages/{newparser => parsely}/parsers/terminals.js | 0 .../phoenix/src/puter-shell/coreutils/concept-parser.js | 6 +++--- 5 files changed, 3 insertions(+), 3 deletions(-) rename packages/phoenix/packages/{newparser => parsely}/exports.js (100%) rename packages/phoenix/packages/{newparser => parsely}/lib.js (100%) rename packages/phoenix/packages/{newparser => parsely}/parsers/combinators.js (100%) rename packages/phoenix/packages/{newparser => parsely}/parsers/terminals.js (100%) diff --git a/packages/phoenix/packages/newparser/exports.js b/packages/phoenix/packages/parsely/exports.js similarity index 100% rename from packages/phoenix/packages/newparser/exports.js rename to packages/phoenix/packages/parsely/exports.js diff --git a/packages/phoenix/packages/newparser/lib.js b/packages/phoenix/packages/parsely/lib.js similarity index 100% rename from packages/phoenix/packages/newparser/lib.js rename to packages/phoenix/packages/parsely/lib.js diff --git a/packages/phoenix/packages/newparser/parsers/combinators.js b/packages/phoenix/packages/parsely/parsers/combinators.js similarity index 100% rename from packages/phoenix/packages/newparser/parsers/combinators.js rename to packages/phoenix/packages/parsely/parsers/combinators.js diff --git a/packages/phoenix/packages/newparser/parsers/terminals.js b/packages/phoenix/packages/parsely/parsers/terminals.js similarity index 100% rename from packages/phoenix/packages/newparser/parsers/terminals.js rename to packages/phoenix/packages/parsely/parsers/terminals.js diff --git a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js index 31888220..b61928f0 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/newparser/exports.js'; -import { Parser, UNRECOGNIZED, VALUE } from '../../../packages/newparser/lib.js'; +import { GrammarContext, standard_parsers } from '../../../packages/parsely/exports.js'; +import { Parser, UNRECOGNIZED, VALUE } from '../../../packages/parsely/lib.js'; class NumberParser extends Parser { static data = { @@ -317,4 +317,4 @@ export default { await err.write(e.stack + '\n'); } } -} \ No newline at end of file +} From 837ec683718c2e537752c5237dad02a3e7fa95ca Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 11:49:56 +0100 Subject: [PATCH 2/7] 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 dfab2185..b5c1985a 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 89f185e0..c588ccf1 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 54b23c05..5078b6a5 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 4a82cd14..712c688f 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 b61928f0..7036fab4 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 = { From 873dee7e511d4ede74536750e16915da1a8bc18f Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 13:29:26 +0100 Subject: [PATCH 3/7] Implement 'trailing' parameter for repeat() parser --- .../packages/parsely/parsers/combinators.js | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/phoenix/packages/parsely/parsers/combinators.js b/packages/phoenix/packages/parsely/parsers/combinators.js index 5078b6a5..b597e857 100644 --- a/packages/phoenix/packages/parsely/parsers/combinators.js +++ b/packages/phoenix/packages/parsely/parsers/combinators.js @@ -73,7 +73,7 @@ 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 separator_parser Parser for the separator, optional * @param trailing Whether to allow a trailing separator */ export class Repeat extends Parser { @@ -85,40 +85,49 @@ export class Repeat extends Parser { _parse (stream) { const results = []; - for ( ;; ) { - const subStream = stream.fork(); + const subStream = stream.fork(); - // Value - const result = this.value_parser.parse(subStream); - if ( result.status === UNRECOGNIZED ) { - break; - } - if ( result.status === INVALID ) { - return { status: INVALID, value: result }; - } + // Parse first value + const result = this.value_parser.parse(subStream); + if ( result.status === INVALID ) + return { status: INVALID, value: result }; + + if ( result.status === VALUE ) { stream.join(subStream); - if ( ! result.$discard ) results.push(result); + if (!result.$discard) results.push(result); - // Separator - if ( ! this.separator_parser ) { - continue; - } - const separatorResult = this.separator_parser.parse(subStream); - if ( separatorResult.status === UNRECOGNIZED ) { - break; - } - if ( separatorResult.status === INVALID ) { - return { status: INVALID, value: separatorResult }; - } - stream.join(subStream); - if ( ! result.$discard ) results.push(separatorResult); + // Repeatedly parse + for (;;) { + // Separator + if (!this.separator_parser) + continue; - // TODO: Detect trailing separator and reject it if trailing==false + const separatorResult = this.separator_parser.parse(subStream); + if (separatorResult.status === UNRECOGNIZED) + break; + if (separatorResult.status === INVALID) + return { status: INVALID, value: separatorResult }; + stream.join(subStream); + if (!separatorResult.$discard) results.push(separatorResult); + + // Value + const result = this.value_parser.parse(subStream); + if (result.status === UNRECOGNIZED) { + // If we failed to parse a value, we have a trailing separator + if (this.trailing === false) + return { status: INVALID, value: result }; + break; + } + if (result.status === INVALID) + return { status: INVALID, value: result }; + + stream.join(subStream); + if (!result.$discard) results.push(result); + } } - if ( results.length === 0 ) { + if ( results.length === 0 ) return UNRECOGNIZED; - } return { status: VALUE, value: results }; } From 0bc4c3fde2a1e6137b08a9285b7d8f028456b4ae Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 14:41:07 +0100 Subject: [PATCH 4/7] Move StringStream into Parsely --- packages/phoenix/packages/parsely/streams.js | 52 +++++++++++++++++++ .../puter-shell/coreutils/concept-parser.js | 35 +------------ 2 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 packages/phoenix/packages/parsely/streams.js diff --git a/packages/phoenix/packages/parsely/streams.js b/packages/phoenix/packages/parsely/streams.js new file mode 100644 index 00000000..23254b62 --- /dev/null +++ b/packages/phoenix/packages/parsely/streams.js @@ -0,0 +1,52 @@ +/** + * Base class for input streams. + * Defines which methods are expected for any stream implementations. + */ +export class ParserStream { + value_at (index) { throw new Error(`${this.constructor.name}.value_at() not implemented`); } + look () { throw new Error(`${this.constructor.name}.look() not implemented`); } + next () { throw new Error(`${this.constructor.name}.next() not implemented`); } + fork () { throw new Error(`${this.constructor.name}.fork() not implemented`); } + join () { throw new Error(`${this.constructor.name}.join() not implemented`); } + + is_eof () { + return this.look().done; + } +} + +/** + * ParserStream that takes a string, and processes it character by character. + */ +export class StringStream extends ParserStream { + constructor (str, startIndex = 0) { + super(); + this.str = str; + this.i = startIndex; + } + + value_at (index) { + if ( index >= this.str.length ) { + return { done: true, value: undefined }; + } + + return { done: false, value: this.str[index] }; + } + + look () { + return this.value_at(this.i); + } + + next () { + const result = this.value_at(this.i); + this.i++; + return result; + } + + fork () { + return new StringStream(this.str, this.i); + } + + join (forked) { + this.i = forked.i; + } +} diff --git a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js index 7036fab4..a24b611d 100644 --- a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js +++ b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js @@ -1,5 +1,6 @@ import { GrammarContext, standard_parsers } from '../../../packages/parsely/exports.js'; import { Parser, UNRECOGNIZED, VALUE } from '../../../packages/parsely/parser.js'; +import { StringStream } from '../../../packages/parsely/streams.js'; class NumberParser extends Parser { static data = { @@ -163,39 +164,6 @@ class StringParser extends Parser { } } -class StringStream { - constructor (str, startIndex = 0) { - this.str = str; - this.i = startIndex; - } - - value_at (index) { - if ( index >= this.str.length ) { - return { done: true, value: undefined }; - } - - return { done: false, value: this.str[index] }; - } - - look () { - return this.value_at(this.i); - } - - next () { - const result = this.value_at(this.i); - this.i++; - return result; - } - - fork () { - return new StringStream(this.str, this.i); - } - - join (forked) { - this.i = forked.i; - } -} - export default { name: 'concept-parser', args: { @@ -305,7 +273,6 @@ export default { whitespace: _ => {}, }); - // TODO: What do we want our streams to be like? const input = ctx.locals.positionals.shift(); const stream = new StringStream(input); try { From d3dff8c20edec15ed15b4ed0fbb84dbd7375266b Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 14:41:13 +0100 Subject: [PATCH 5/7] Throw errors if parsing fails Not consuming all input is now treated as an error, unless the `must_consume_all_input` flag is set to false when parsing. --- packages/phoenix/packages/parsely/exports.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/phoenix/packages/parsely/exports.js b/packages/phoenix/packages/parsely/exports.js index b5c1985a..b729a589 100644 --- a/packages/phoenix/packages/parsely/exports.js +++ b/packages/phoenix/packages/parsely/exports.js @@ -57,13 +57,22 @@ export class GrammarContext { } } - return (stream, entry_symbol) => { + return (stream, entry_symbol, { must_consume_all_input = true } = {}) => { const entry_parser = symbol_registry[entry_symbol]; if (!entry_parser) { throw new Error(`Entry symbol '${entry_symbol}' not found in grammar.`); } const result = entry_parser.parse(stream); - // TODO: Ensure all the stream has been consumed + + if (result.status !== VALUE) { + throw new Error('Failed to parse input against grammar.'); + } + + // Ensure the entire stream is consumed. + if (must_consume_all_input && !stream.is_eof()) { + throw new Error('Parsing did not consume all input.'); + } + return result; }; } From 7fccf79591de916b63e39b858644f7b91d11b852 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 15:57:59 +0100 Subject: [PATCH 6/7] Make stringOf() take a callback instead of an array of accepted values THe `a.stringOf(' \r\n\t'.split('')),` pattern works fine for small sets of characters, but is horrible for situations like "any alphanumeric". Instead, let's make it take a callback function that is run on each character. --- packages/phoenix/packages/parsely/parsers/terminals.js | 10 +++++----- .../src/puter-shell/coreutils/concept-parser.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/phoenix/packages/parsely/parsers/terminals.js b/packages/phoenix/packages/parsely/parsers/terminals.js index 712c688f..10936540 100644 --- a/packages/phoenix/packages/parsely/parsers/terminals.js +++ b/packages/phoenix/packages/parsely/parsers/terminals.js @@ -23,12 +23,12 @@ 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. + * Parses matching characters as a string. + * @param test Function that takes a character, and returns whether to include it. */ export class StringOf extends Parser { - _create (values) { - this.values = values; + _create (test) { + this.test = test; } _parse (stream) { @@ -38,7 +38,7 @@ export class StringOf extends Parser { while (true) { let { done, value } = subStream.look(); if ( done ) break; - if ( ! this.values.includes(value) ) break; + if ( ! this.test(value) ) break; subStream.next(); text += value; diff --git a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js index a24b611d..64369858 100644 --- a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js +++ b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js @@ -232,7 +232,7 @@ export default { number: a => new NumberParser(), string: a => new StringParser(), whitespace: a => a.optional( - a.stringOf(' \r\n\t'.split('')), + a.stringOf(c => ' \r\n\t'.includes(c)), ), }, { element: it => it[0].value, From 03123faa8acf1e23f1eb1936906b365788a24e63 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 24 May 2024 12:06:10 +0100 Subject: [PATCH 7/7] Finish concept JSON parser This now can parse JSON correctly in various configurations. --- .../puter-shell/coreutils/concept-parser.js | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js index 64369858..968c3928 100644 --- a/packages/phoenix/src/puter-shell/coreutils/concept-parser.js +++ b/packages/phoenix/src/puter-shell/coreutils/concept-parser.js @@ -172,15 +172,13 @@ export default { }, execute: async ctx => { const { in_, out, err } = ctx.externs; - await out.write("STARTING CONCEPT PARSER\n"); const grammar_context = new GrammarContext(standard_parsers()); - await out.write("Constructed a grammar context\n"); const parser = grammar_context.define_parser({ element: a => a.sequence( - a.symbol('whitespace'), + a.optional(a.symbol('whitespace')), a.symbol('value'), - a.symbol('whitespace'), + a.optional(a.symbol('whitespace')), ), value: a => a.firstMatch( a.symbol('object'), @@ -193,37 +191,33 @@ export default { ), array: a => a.sequence( a.literal('['), - a.symbol('whitespace'), - a.optional( + a.firstMatch( a.repeat( a.symbol('element'), a.literal(','), - { trailing: true }, + { trailing: false }, ), + a.optional(a.symbol('whitespace')), ), - a.symbol('whitespace'), a.literal(']'), ), member: a => a.sequence( - a.symbol('whitespace'), + a.optional(a.symbol('whitespace')), a.symbol('string'), - a.symbol('whitespace'), + a.optional(a.symbol('whitespace')), a.literal(':'), - a.symbol('whitespace'), - a.symbol('value'), - a.symbol('whitespace'), + a.symbol('element'), ), object: a => a.sequence( a.literal('{'), - a.symbol('whitespace'), - a.optional( + a.firstMatch( a.repeat( a.symbol('member'), a.literal(','), - { trailing: true }, + { trailing: false }, ), + a.optional(a.symbol('whitespace')), ), - a.symbol('whitespace'), a.literal('}'), ), true: a => a.literal('true'), @@ -231,37 +225,31 @@ export default { null: a => a.literal('null'), number: a => new NumberParser(), string: a => new StringParser(), - whitespace: a => a.optional( - a.stringOf(c => ' \r\n\t'.includes(c)), - ), + whitespace: a => a.stringOf(c => ' \r\n\t'.includes(c)), }, { - element: it => it[0].value, + element: it => it.filter(it => it.$ === 'value')[0].value, value: it => it, array: it => { // A parsed array contains 3 values: `[`, the entries array, and `]`, so we only care about index 1. // If it's less than 3, there were no entries. if (it.length < 3) return []; return (it[1].value || []) - .filter(it => it.$ !== 'literal') + .filter(it => it.$ === 'element') .map(it => it.value); }, member: it => { - // A parsed member contains 3 values: a name, `:`, and a value. - const [ name_part, colon, value_part ] = it; + const [ name_part, value_part ] = it.filter(it => it.$ === 'string' || it.$ === 'element'); return { name: name_part.value, value: value_part.value }; }, object: it => { - console.log('OBJECT!!!!'); - console.log(it[1]); // A parsed object contains 3 values: `{`, the members array, and `}`, so we only care about index 1. // If it's less than 3, there were no members. if (it.length < 3) return {}; const result = {}; - // FIXME: This is all wrong!!! (it[1].value || []) .filter(it => it.$ === 'member') .forEach(it => { - result[it.name] = it.value; + result[it.value.name] = it.value.value; }); return result; },