@@ -11,7 +11,7 @@ import {
1111import type { Writable } from '../../utility-types.js'
1212import { keyPathToMolecule , type KeyPath } from '../semantics.js'
1313import {
14- atomParser ,
14+ atom ,
1515 atomWithAdditionalQuotationRequirements ,
1616 type Atom ,
1717} from './atom.js'
@@ -49,7 +49,7 @@ const optional = <Output>(
4949) : Parser < Output | undefined > => oneOf ( [ parser , nothing ] )
5050
5151const trailingIndexesAndArgumentsToExpression = (
52- root : Molecule ,
52+ root : Atom | Molecule ,
5353 trailingIndexesAndArguments : readonly TrailingIndexOrArgument [ ] ,
5454) =>
5555 trailingIndexesAndArguments . reduce ( ( expression , indexOrArgument ) => {
@@ -69,73 +69,93 @@ const trailingIndexesAndArgumentsToExpression = (
6969 }
7070 } , root )
7171
72- const infixOperationToExpression = (
73- initialExpression : Atom | Molecule ,
74- tokens : readonly [ InfixToken , ...InfixToken [ ] ] ,
75- ) : Molecule => {
76- const [
77- [
78- [ firstOperatorLookupKey , firstOperatorTrailingIndexesAndArguments ] ,
79- firstArgument ,
80- ] ,
81- ...additionalTokens
82- ] = tokens
83-
84- const firstFunction = trailingIndexesAndArgumentsToExpression (
85- { 0 : '@lookup' , key : firstOperatorLookupKey } ,
86- firstOperatorTrailingIndexesAndArguments ,
87- )
72+ type InfixOperator = readonly [ Atom , readonly TrailingIndexOrArgument [ ] ]
73+ type InfixOperand = Atom | Molecule
74+ type InfixToken = InfixOperator | InfixOperand
8875
89- const initialApplication : Molecule = {
90- 0 : '@apply' ,
91- function : {
92- 0 : '@apply' ,
93- function : firstFunction ,
94- argument : firstArgument ,
95- } ,
96- argument : initialExpression ,
97- }
76+ /**
77+ * Infix operations should be of the following form:
78+ * ```
79+ * [InfixOperand, InfixOperator, InfixOperand, InfixOperator, …, InfixOperand]
80+ * ```
81+ * However this can't be directly modeled in TypeScript.
82+ */
83+ type InfixOperation = readonly [ InfixToken , ...InfixToken [ ] ]
9884
99- return additionalTokens . reduce (
100- (
101- expression ,
102- [ [ operatorLookupKey , operatorTrailingIndexesAndArguments ] , nextArgument ] ,
103- ) => {
104- const nextFunction = trailingIndexesAndArgumentsToExpression (
105- { 0 : '@lookup' , key : operatorLookupKey } ,
106- operatorTrailingIndexesAndArguments ,
85+ const isOperand = ( value : InfixToken | undefined ) : value is InfixOperand =>
86+ ! Array . isArray ( value )
87+ const isOperator = ( value : InfixToken | undefined ) : value is InfixOperator =>
88+ Array . isArray ( value )
89+
90+ const appendToArray = < A > ( b : A , init : readonly A [ ] ) : readonly [ A , ...A [ ] ] =>
91+ // Unfortunately TypeScript can't reason through this on its own:
92+ [ ...init , b ] as readonly A [ ] as readonly [ A , ...A [ ] ]
93+
94+ const infixTokensToExpression = (
95+ operation : InfixOperation ,
96+ ) : Molecule | Atom => {
97+ const firstToken = operation [ 0 ]
98+ if ( operation . length === 1 && isOperand ( firstToken ) ) {
99+ return firstToken
100+ } else {
101+ const rightmostOperationRHS = operation [ operation . length - 1 ]
102+ if ( rightmostOperationRHS === undefined ) {
103+ throw new Error ( 'Infix operation was empty. This is a bug!' )
104+ }
105+ if ( ! isOperand ( rightmostOperationRHS ) ) {
106+ throw new Error (
107+ 'Rightmost token in infix operation was not an operand. This is a bug!' ,
107108 )
108- return {
109+ }
110+ const rightmostOperator = operation [ operation . length - 2 ]
111+ if ( ! isOperator ( rightmostOperator ) ) {
112+ throw new Error (
113+ 'Could not find rightmost operator in infix operation. This is a bug!' ,
114+ )
115+ }
116+
117+ const rightmostOperationLHS = operation [ operation . length - 3 ]
118+ if ( ! isOperand ( rightmostOperationLHS ) ) {
119+ throw new Error (
120+ 'Missing left-hand side of infix operation. This is a bug!' ,
121+ )
122+ }
123+
124+ const rightmostFunction = trailingIndexesAndArgumentsToExpression (
125+ { 0 : '@lookup' , key : rightmostOperator [ 0 ] } ,
126+ rightmostOperator [ 1 ] ,
127+ )
128+
129+ const reducedRightmostOperation : Molecule = {
130+ 0 : '@apply' ,
131+ function : {
109132 0 : '@apply' ,
110- function : {
111- 0 : '@apply' ,
112- function : nextFunction ,
113- argument : nextArgument ,
114- } ,
115- argument : expression ,
116- }
117- } ,
118- initialApplication ,
119- )
133+ function : rightmostFunction ,
134+ argument : rightmostOperationRHS ,
135+ } ,
136+ argument : rightmostOperationLHS ,
137+ }
138+
139+ return infixTokensToExpression (
140+ appendToArray ( reducedRightmostOperation , operation . slice ( 0 , - 3 ) ) ,
141+ )
142+ }
120143}
121144
122145const atomRequiringDotQuotation = atomWithAdditionalQuotationRequirements ( dot )
123146
124- const propertyKey : Parser < Atom > = atomParser
125- const propertyValue : Parser < Molecule | Atom > = oneOf ( [
126- lazy ( ( ) => moleculeParser ) ,
127- optionallySurroundedByParentheses ( atomParser ) ,
128- ] )
129-
130147const namedProperty = map (
131- sequence ( [ propertyKey , colon , optionalTrivia , propertyValue ] ) ,
148+ sequence ( [ atom , colon , optionalTrivia , lazy ( ( ) => expression ) ] ) ,
132149 ( [ key , _colon , _trivia , value ] ) => [ key , value ] as const ,
133150)
134151
135152const propertyWithOptionalKey = optionallySurroundedByParentheses (
136153 oneOf ( [
137154 namedProperty ,
138- map ( propertyValue , value => [ undefined , value ] as const ) ,
155+ map (
156+ lazy ( ( ) => expression ) ,
157+ value => [ undefined , value ] as const ,
158+ ) ,
139159 ] ) ,
140160)
141161
@@ -144,7 +164,7 @@ const propertyDelimiter = oneOf([
144164 sequence ( [ optional ( triviaExceptNewlines ) , newline , optionalTrivia ] ) ,
145165] )
146166
147- const argument = surroundedByParentheses ( propertyValue )
167+ const argument = surroundedByParentheses ( lazy ( ( ) => expression ) )
148168
149169const compactDottedKeyPathComponent = map (
150170 sequence ( [ dot , atomRequiringDotQuotation ] ) ,
@@ -272,12 +292,10 @@ const compactExpression: Parser<Molecule | Atom> = oneOf([
272292 // {}
273293 lazy ( ( ) => precededByOpeningBrace ) ,
274294 // 1
275- atomParser ,
295+ atom ,
276296] )
277297
278- const trailingInfixTokens : Parser <
279- readonly [ InfixToken , ...( readonly InfixToken [ ] ) ]
280- > = oneOrMore (
298+ const trailingInfixTokens = oneOrMore (
281299 map (
282300 sequence ( [
283301 trivia ,
@@ -301,9 +319,9 @@ const trailingInfixTokens: Parser<
301319 ) ,
302320)
303321
304- type InfixToken = readonly [
305- readonly [ Atom , readonly TrailingIndexOrArgument [ ] ] ,
306- Molecule | Atom ,
322+ type TrailingInfixToken = readonly [
323+ operator : readonly [ Atom , readonly TrailingIndexOrArgument [ ] ] ,
324+ operand : Molecule | Atom ,
307325]
308326type TrailingFunctionBodyOrInfixTokens =
309327 | {
@@ -313,11 +331,15 @@ type TrailingFunctionBodyOrInfixTokens =
313331 }
314332 | {
315333 readonly kind : 'infixTokens'
316- readonly tokens : readonly [ InfixToken , ...( readonly InfixToken [ ] ) ]
334+ readonly tokens : readonly [
335+ TrailingInfixToken ,
336+ ...( readonly TrailingInfixToken [ ] ) ,
337+ ]
317338 }
339+
318340const precededByAtomThenTrivia = map (
319341 sequence ( [
320- atomParser ,
342+ atom ,
321343 oneOf ( [
322344 // a => :b
323345 // a => {}
@@ -331,11 +353,11 @@ const precededByAtomThenTrivia = map(
331353 trivia ,
332354 zeroOrMore (
333355 map (
334- sequence ( [ atomParser , trivia , arrow , trivia ] ) ,
356+ sequence ( [ atom , trivia , arrow , trivia ] ) ,
335357 ( [ parameter , _trivia1 , _arrow , _trivia2 ] ) => parameter ,
336358 ) ,
337359 ) ,
338- propertyValue ,
360+ lazy ( ( ) => expression ) ,
339361 ] ) ,
340362 ( [
341363 _trivia1 ,
@@ -381,10 +403,10 @@ const precededByAtomThenTrivia = map(
381403 initialFunction ,
382404 )
383405 case 'infixTokens' :
384- return infixOperationToExpression (
406+ return infixTokensToExpression ( [
385407 initialAtom ,
386- trailingFunctionBodyOrInfixTokens . tokens ,
387- )
408+ ... trailingFunctionBodyOrInfixTokens . tokens . flat ( ) ,
409+ ] )
388410 }
389411 } ,
390412)
@@ -425,9 +447,10 @@ const precededByColonThenAtom = map(
425447 if ( firstToken === undefined ) {
426448 return initialExpression
427449 } else {
428- return infixOperationToExpression ( initialExpression , [
429- firstToken ,
430- ...additionalTokens ,
450+ return infixTokensToExpression ( [
451+ initialExpression ,
452+ ...firstToken ,
453+ ...additionalTokens . flat ( ) ,
431454 ] )
432455 }
433456 } ,
@@ -444,15 +467,18 @@ const precededByColonThenAtom = map(
444467const precededByOpeningParenthesis = oneOf ( [
445468 map (
446469 sequence ( [
447- surroundedByParentheses ( lazy ( ( ) => moleculeParser ) ) ,
470+ surroundedByParentheses ( lazy ( ( ) => expression ) ) ,
448471 trailingInfixTokens ,
449472 ] ) ,
450473 ( [ initialExpression , trailingInfixTokens ] ) =>
451- infixOperationToExpression ( initialExpression , trailingInfixTokens ) ,
474+ infixTokensToExpression ( [
475+ initialExpression ,
476+ ...trailingInfixTokens . flat ( ) ,
477+ ] ) ,
452478 ) ,
453479 map (
454480 sequence ( [
455- surroundedByParentheses ( lazy ( ( ) => moleculeParser ) ) ,
481+ surroundedByParentheses ( lazy ( ( ) => expression ) ) ,
456482 trailingIndexesAndArguments ,
457483 ] ) ,
458484 ( [ expression , trailingIndexesAndArguments ] ) =>
@@ -476,9 +502,10 @@ const precededByOpeningBrace = map(
476502 ) ,
477503)
478504
479- export const moleculeParser : Parser < Molecule > = oneOf ( [
505+ export const expression : Parser < Atom | Molecule > = oneOf ( [
480506 precededByOpeningParenthesis ,
481507 precededByOpeningBrace ,
482508 precededByColonThenAtom ,
483509 precededByAtomThenTrivia ,
510+ atom ,
484511] )
0 commit comments