Skip to content

Commit e327345

Browse files
authored
Merge pull request #31 from mkantor/simplify-molecule-parsers
Simplify molecule parsers
2 parents 70fdf62 + c3a7e63 commit e327345

File tree

1 file changed

+59
-80
lines changed

1 file changed

+59
-80
lines changed

src/language/parsing/molecule.ts

Lines changed: 59 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
as,
32
lazy,
43
literal,
54
map,
@@ -29,21 +28,9 @@ export const moleculeParser: Parser<Molecule> = oneOf([
2928
lazy(() => sugaredFunction),
3029
])
3130

32-
// During parsing molecules and properties are represented as nested arrays (of key/value pairs).
33-
// The following utilities make it easier to work with such a structure.
34-
35-
const flat = <Output>(theParser: Parser<readonly Output[]>) =>
36-
map(theParser, output => output.flat())
37-
38-
const omit = (theParser: Parser<unknown>) => as(theParser, [])
39-
4031
const optional = <Output>(
41-
theParser: Parser<readonly Output[]>,
42-
): Parser<readonly Output[]> => oneOf([theParser, omit(nothing)])
43-
44-
const withoutOmittedOutputs = <Output>(
45-
theParser: Parser<readonly (readonly Output[])[]>,
46-
) => map(theParser, output => output.filter(output => output.length > 0))
32+
parser: Parser<NonNullable<Output>>,
33+
): Parser<Output | undefined> => oneOf([parser, nothing])
4734

4835
// Keyless properties are automatically assigned numeric indexes, which uses some mutable state.
4936
type Indexer = () => string
@@ -57,59 +44,50 @@ const makeIncrementingIndexer = (): Indexer => {
5744
}
5845
}
5946

60-
// Language-specific parsers follow.
61-
6247
const propertyDelimiter = oneOf([
63-
sequence([optional(omit(trivia)), literal(','), optional(omit(trivia))]),
48+
sequence([optional(trivia), literal(','), optional(trivia)]),
6449
trivia,
6550
])
6651

67-
const sugaredLookup: Parser<PartialMolecule> =
68-
optionallySurroundedByParentheses(
69-
map(
70-
sequence([literal(':'), oneOf([atomParser, moleculeParser])]),
71-
([_colon, query]) => ({ 0: '@lookup', query }),
72-
),
73-
)
52+
const sugaredLookup: Parser<Molecule> = optionallySurroundedByParentheses(
53+
map(
54+
sequence([literal(':'), oneOf([atomParser, moleculeParser])]),
55+
([_colon, query]) => ({ 0: '@lookup', query }),
56+
),
57+
)
7458

75-
const sugaredFunction: Parser<PartialMolecule> =
76-
optionallySurroundedByParentheses(
77-
map(
78-
flat(
79-
sequence([
80-
map(atomParser, output => [output]),
81-
omit(trivia),
82-
omit(literal('=>')),
83-
omit(trivia),
84-
map(
85-
lazy(() => propertyValue),
86-
output => [output],
87-
),
88-
]),
89-
),
90-
([parameter, body]) => ({
91-
0: '@function',
92-
parameter,
93-
body,
94-
}),
95-
),
96-
)
59+
const sugaredFunction: Parser<Molecule> = optionallySurroundedByParentheses(
60+
map(
61+
sequence([
62+
atomParser,
63+
trivia,
64+
literal('=>'),
65+
trivia,
66+
lazy(() => propertyValue),
67+
]),
68+
([parameter, _trivia1, _arrow, _trivia2, body]) => ({
69+
0: '@function',
70+
parameter,
71+
body,
72+
}),
73+
),
74+
)
9775

98-
const sugaredApply: Parser<PartialMolecule> = map(
76+
const sugaredApply: Parser<Molecule> = map(
9977
sequence([
10078
oneOf([sugaredLookup, lazy(() => sugaredFunction)]),
10179
oneOrMore(
10280
sequence([
10381
literal('('),
104-
optional(omit(trivia)),
82+
optional(trivia),
10583
lazy(() => propertyValue),
106-
optional(omit(trivia)),
84+
optional(trivia),
10785
literal(')'),
10886
]),
10987
),
11088
]),
11189
([f, multipleArguments]) =>
112-
multipleArguments.reduce<PartialMolecule>(
90+
multipleArguments.reduce<Molecule>(
11391
(expression, [_1, _2, argument, _3, _4]) => ({
11492
0: '@apply',
11593
function: expression,
@@ -127,43 +105,44 @@ const propertyValue = oneOf([
127105
sugaredLookup,
128106
])
129107

130-
const namedProperty = flat(
131-
sequence([
132-
propertyKey,
133-
omit(literal(':')),
134-
optional(omit(trivia)),
135-
propertyValue,
136-
]),
108+
const namedProperty = map(
109+
sequence([propertyKey, literal(':'), optional(trivia), propertyValue]),
110+
([key, _colon, _trivia, value]) => [key, value] as const,
137111
)
138112

139113
const numberedProperty = (index: Indexer) =>
140-
map(propertyValue, value => [index(), value])
114+
map(propertyValue, value => [index(), value] as const)
141115

142116
const property = (index: Indexer) =>
143117
optionallySurroundedByParentheses(
144118
oneOf([namedProperty, numberedProperty(index)]),
145119
)
146120

147-
const moleculeAsEntries = (index: Indexer) =>
148-
withoutOmittedOutputs(
149-
flat(
150-
sequence([
151-
omit(literal('{')),
152-
// Allow initial property not preceded by a delimiter (e.g. `{a b}`).
153-
map(optional(property(index)), property => [property]),
154-
zeroOrMore(
155-
flat(
156-
sequence([omit(propertyDelimiter), lazy(() => property(index))]),
157-
),
121+
const moleculeAsEntries = (
122+
index: Indexer,
123+
): Parser<readonly (readonly [string, string | Molecule])[]> =>
124+
map(
125+
sequence([
126+
literal('{'),
127+
// Allow initial property not preceded by a delimiter (e.g. `{a b}`).
128+
optional(property(index)),
129+
zeroOrMore(
130+
map(
131+
sequence([propertyDelimiter, property(index)]),
132+
([_delimiter, property]) => property,
158133
),
159-
optional(omit(propertyDelimiter)),
160-
omit(literal('}')),
161-
]),
162-
),
134+
),
135+
optional(propertyDelimiter),
136+
literal('}'),
137+
]),
138+
([
139+
_openingBrace,
140+
optionalInitialProperty,
141+
remainingProperties,
142+
_delimiter,
143+
_closingBrace,
144+
]) =>
145+
optionalInitialProperty === undefined
146+
? remainingProperties
147+
: [optionalInitialProperty, ...remainingProperties],
163148
)
164-
165-
// This is a lazy workaround for `sequence` returning an array rather than a tuple with
166-
// definitely-present elements.
167-
type PartialMolecule = {
168-
readonly [key: Atom]: PartialMolecule | Atom | undefined
169-
}

0 commit comments

Comments
 (0)