|
8 | 8 | zeroOrMore, |
9 | 9 | type Parser, |
10 | 10 | } from '@matt.kantor/parsing' |
| 11 | +import type { Writable } from '../../utility-types.js' |
11 | 12 | import { keyPathToMolecule } from '../semantics.js' |
12 | 13 | import { |
13 | 14 | atomParser, |
@@ -57,13 +58,12 @@ const namedProperty = map( |
57 | 58 | ([key, _colon, _trivia, value]) => [key, value] as const, |
58 | 59 | ) |
59 | 60 |
|
60 | | -const numberedProperty = (index: Indexer) => |
61 | | - map(propertyValue, value => [index(), value] as const) |
62 | | - |
63 | | -const property = (index: Indexer) => |
64 | | - optionallySurroundedByParentheses( |
65 | | - oneOf([namedProperty, numberedProperty(index)]), |
66 | | - ) |
| 61 | +const propertyWithOptionalKey = optionallySurroundedByParentheses( |
| 62 | + oneOf([ |
| 63 | + namedProperty, |
| 64 | + map(propertyValue, value => [undefined, value] as const), |
| 65 | + ]), |
| 66 | +) |
67 | 67 |
|
68 | 68 | const propertyDelimiter = oneOf([ |
69 | 69 | sequence([optionalTrivia, comma, optionalTrivia]), |
@@ -92,43 +92,48 @@ const dottedKeyPathComponent = map( |
92 | 92 | ([_trivia1, _dot, _trivia2, key]) => key, |
93 | 93 | ) |
94 | 94 |
|
95 | | -const moleculeAsEntries = ( |
96 | | - index: Indexer, |
97 | | -): Parser<readonly (readonly [string, string | Molecule])[]> => |
| 95 | +const sugarFreeMolecule: Parser<Molecule> = optionallySurroundedByParentheses( |
98 | 96 | map( |
99 | 97 | sequence([ |
100 | 98 | openingBrace, |
101 | | - optional(trivia), |
102 | | - // Allow initial property not preceded by a delimiter (e.g. `{a b}`). |
103 | | - optional(property(index)), |
104 | | - zeroOrMore( |
105 | | - map( |
106 | | - sequence([propertyDelimiter, property(index)]), |
107 | | - ([_delimiter, property]) => property, |
| 99 | + optionalTrivia, |
| 100 | + sequence([ |
| 101 | + // Allow initial property not preceded by a delimiter (e.g. `{a, b}`). |
| 102 | + optional(propertyWithOptionalKey), |
| 103 | + zeroOrMore( |
| 104 | + map( |
| 105 | + sequence([propertyDelimiter, propertyWithOptionalKey]), |
| 106 | + ([_delimiter, property]) => property, |
| 107 | + ), |
108 | 108 | ), |
109 | | - ), |
| 109 | + ]), |
110 | 110 | optional(propertyDelimiter), |
111 | | - optional(trivia), |
| 111 | + optionalTrivia, |
112 | 112 | closingBrace, |
113 | 113 | ]), |
114 | 114 | ([ |
115 | 115 | _openingBrace, |
116 | | - _optionalLeadingTrivia, |
117 | | - optionalInitialProperty, |
118 | | - remainingProperties, |
119 | | - _optionalDelimiter, |
120 | | - _optionalTrailingTrivia, |
| 116 | + _trivia1, |
| 117 | + [optionalInitialProperty, remainingProperties], |
| 118 | + _trailingDelimiter, |
| 119 | + _trivia2, |
121 | 120 | _closingBrace, |
122 | | - ]) => |
123 | | - optionalInitialProperty === undefined |
124 | | - ? remainingProperties |
125 | | - : [optionalInitialProperty, ...remainingProperties], |
126 | | - ) |
127 | | - |
128 | | -const sugarFreeMolecule: Parser<Molecule> = optionallySurroundedByParentheses( |
129 | | - map( |
130 | | - lazy(() => moleculeAsEntries(makeIncrementingIndexer())), |
131 | | - Object.fromEntries, |
| 121 | + ]) => { |
| 122 | + const properties = |
| 123 | + optionalInitialProperty === undefined |
| 124 | + ? remainingProperties |
| 125 | + : [optionalInitialProperty, ...remainingProperties] |
| 126 | + const enumerate = makeIncrementingIndexer() |
| 127 | + return properties.reduce((molecule: Writable<Molecule>, [key, value]) => { |
| 128 | + if (key === undefined) { |
| 129 | + // Note that `enumerate()` increments its internal counter as a side effect. |
| 130 | + molecule[enumerate()] = value |
| 131 | + } else { |
| 132 | + molecule[key] = value |
| 133 | + } |
| 134 | + return molecule |
| 135 | + }, {}) |
| 136 | + }, |
132 | 137 | ), |
133 | 138 | ) |
134 | 139 |
|
|
0 commit comments