From 06edd289386b3edc2d27cd2b6064025baf3f7ca0 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Wed, 5 Feb 2025 11:02:54 -0500 Subject: [PATCH 1/3] Simplify molecule parsers: ditch `PartialMolecule` Since I wrote this the `sequence` combinator gained a fancier type signature, making it unnecessary. --- src/language/parsing/molecule.ts | 61 +++++++++++++------------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/language/parsing/molecule.ts b/src/language/parsing/molecule.ts index b361180..4aa8b23 100644 --- a/src/language/parsing/molecule.ts +++ b/src/language/parsing/molecule.ts @@ -64,38 +64,31 @@ const propertyDelimiter = oneOf([ trivia, ]) -const sugaredLookup: Parser = - optionallySurroundedByParentheses( - map( - sequence([literal(':'), oneOf([atomParser, moleculeParser])]), - ([_colon, query]) => ({ 0: '@lookup', query }), - ), - ) +const sugaredLookup: Parser = optionallySurroundedByParentheses( + map( + sequence([literal(':'), oneOf([atomParser, moleculeParser])]), + ([_colon, query]) => ({ 0: '@lookup', query }), + ), +) -const sugaredFunction: Parser = - optionallySurroundedByParentheses( - map( - flat( - sequence([ - map(atomParser, output => [output]), - omit(trivia), - omit(literal('=>')), - omit(trivia), - map( - lazy(() => propertyValue), - output => [output], - ), - ]), - ), - ([parameter, body]) => ({ - 0: '@function', - parameter, - body, - }), - ), - ) +const sugaredFunction: Parser = optionallySurroundedByParentheses( + map( + sequence([ + atomParser, + omit(trivia), + omit(literal('=>')), + omit(trivia), + lazy(() => propertyValue), + ]), + ([parameter, _trivia1, _arrow, _trivia2, body]) => ({ + 0: '@function', + parameter, + body, + }), + ), +) -const sugaredApply: Parser = map( +const sugaredApply: Parser = map( sequence([ oneOf([sugaredLookup, lazy(() => sugaredFunction)]), oneOrMore( @@ -109,7 +102,7 @@ const sugaredApply: Parser = map( ), ]), ([f, multipleArguments]) => - multipleArguments.reduce( + multipleArguments.reduce( (expression, [_1, _2, argument, _3, _4]) => ({ 0: '@apply', function: expression, @@ -161,9 +154,3 @@ const moleculeAsEntries = (index: Indexer) => ]), ), ) - -// This is a lazy workaround for `sequence` returning an array rather than a tuple with -// definitely-present elements. -type PartialMolecule = { - readonly [key: Atom]: PartialMolecule | Atom | undefined -} From aa1a9cf366e71d2d6ae2a1bd5388008947f1c684 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Wed, 5 Feb 2025 11:21:01 -0500 Subject: [PATCH 2/3] Simplify molecule parsers more --- src/language/parsing/molecule.ts | 45 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/language/parsing/molecule.ts b/src/language/parsing/molecule.ts index 4aa8b23..20b9218 100644 --- a/src/language/parsing/molecule.ts +++ b/src/language/parsing/molecule.ts @@ -32,9 +32,6 @@ export const moleculeParser: Parser = oneOf([ // During parsing molecules and properties are represented as nested arrays (of key/value pairs). // The following utilities make it easier to work with such a structure. -const flat = (theParser: Parser) => - map(theParser, output => output.flat()) - const omit = (theParser: Parser) => as(theParser, []) const optional = ( @@ -60,7 +57,7 @@ const makeIncrementingIndexer = (): Indexer => { // Language-specific parsers follow. const propertyDelimiter = oneOf([ - sequence([optional(omit(trivia)), literal(','), optional(omit(trivia))]), + sequence([optional(trivia), literal(','), optional(trivia)]), trivia, ]) @@ -75,9 +72,9 @@ const sugaredFunction: Parser = optionallySurroundedByParentheses( map( sequence([ atomParser, - omit(trivia), - omit(literal('=>')), - omit(trivia), + trivia, + literal('=>'), + trivia, lazy(() => propertyValue), ]), ([parameter, _trivia1, _arrow, _trivia2, body]) => ({ @@ -94,9 +91,9 @@ const sugaredApply: Parser = map( oneOrMore( sequence([ literal('('), - optional(omit(trivia)), + optional(trivia), lazy(() => propertyValue), - optional(omit(trivia)), + optional(trivia), literal(')'), ]), ), @@ -120,17 +117,13 @@ const propertyValue = oneOf([ sugaredLookup, ]) -const namedProperty = flat( - sequence([ - propertyKey, - omit(literal(':')), - optional(omit(trivia)), - propertyValue, - ]), +const namedProperty = map( + sequence([propertyKey, literal(':'), optional(trivia), propertyValue]), + ([key, _colon, _trivia, value]) => [key, value] as const, ) const numberedProperty = (index: Indexer) => - map(propertyValue, value => [index(), value]) + map(propertyValue, value => [index(), value] as const) const property = (index: Indexer) => optionallySurroundedByParentheses( @@ -139,18 +132,26 @@ const property = (index: Indexer) => const moleculeAsEntries = (index: Indexer) => withoutOmittedOutputs( - flat( + map( sequence([ - omit(literal('{')), + literal('{'), // Allow initial property not preceded by a delimiter (e.g. `{a b}`). map(optional(property(index)), property => [property]), zeroOrMore( - flat( - sequence([omit(propertyDelimiter), lazy(() => property(index))]), + map( + sequence([propertyDelimiter, property(index)]), + ([_delimiter, property]) => property, ), ), optional(omit(propertyDelimiter)), - omit(literal('}')), + literal('}'), ]), + ([ + _openingBrace, + optionalInitialProperty, + remainingProperties, + _delimiter, + _closingBrace, + ]) => [...optionalInitialProperty, ...remainingProperties], ), ) From c3a7e63d223fdb7146132382ab4d157200fe8c31 Mon Sep 17 00:00:00 2001 From: Matt Kantor Date: Wed, 5 Feb 2025 11:57:06 -0500 Subject: [PATCH 3/3] Simplify molecule parsers more --- src/language/parsing/molecule.ts | 65 ++++++++++++++------------------ 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/language/parsing/molecule.ts b/src/language/parsing/molecule.ts index 20b9218..c8863fc 100644 --- a/src/language/parsing/molecule.ts +++ b/src/language/parsing/molecule.ts @@ -1,5 +1,4 @@ import { - as, lazy, literal, map, @@ -29,18 +28,9 @@ export const moleculeParser: Parser = oneOf([ lazy(() => sugaredFunction), ]) -// During parsing molecules and properties are represented as nested arrays (of key/value pairs). -// The following utilities make it easier to work with such a structure. - -const omit = (theParser: Parser) => as(theParser, []) - const optional = ( - theParser: Parser, -): Parser => oneOf([theParser, omit(nothing)]) - -const withoutOmittedOutputs = ( - theParser: Parser, -) => map(theParser, output => output.filter(output => output.length > 0)) + parser: Parser>, +): Parser => oneOf([parser, nothing]) // Keyless properties are automatically assigned numeric indexes, which uses some mutable state. type Indexer = () => string @@ -54,8 +44,6 @@ const makeIncrementingIndexer = (): Indexer => { } } -// Language-specific parsers follow. - const propertyDelimiter = oneOf([ sequence([optional(trivia), literal(','), optional(trivia)]), trivia, @@ -130,28 +118,31 @@ const property = (index: Indexer) => oneOf([namedProperty, numberedProperty(index)]), ) -const moleculeAsEntries = (index: Indexer) => - withoutOmittedOutputs( - map( - sequence([ - literal('{'), - // Allow initial property not preceded by a delimiter (e.g. `{a b}`). - map(optional(property(index)), property => [property]), - zeroOrMore( - map( - sequence([propertyDelimiter, property(index)]), - ([_delimiter, property]) => property, - ), +const moleculeAsEntries = ( + index: Indexer, +): Parser => + map( + sequence([ + literal('{'), + // Allow initial property not preceded by a delimiter (e.g. `{a b}`). + optional(property(index)), + zeroOrMore( + map( + sequence([propertyDelimiter, property(index)]), + ([_delimiter, property]) => property, ), - optional(omit(propertyDelimiter)), - literal('}'), - ]), - ([ - _openingBrace, - optionalInitialProperty, - remainingProperties, - _delimiter, - _closingBrace, - ]) => [...optionalInitialProperty, ...remainingProperties], - ), + ), + optional(propertyDelimiter), + literal('}'), + ]), + ([ + _openingBrace, + optionalInitialProperty, + remainingProperties, + _delimiter, + _closingBrace, + ]) => + optionalInitialProperty === undefined + ? remainingProperties + : [optionalInitialProperty, ...remainingProperties], )