1
- import type { Either } from '@matt.kantor/either'
1
+ import type { Either , Right } from '@matt.kantor/either'
2
2
import * as either from '@matt.kantor/either'
3
3
import { nothing } from './constructors.js'
4
4
import type {
5
5
InvalidInputError ,
6
6
Parser ,
7
+ ParserResult ,
7
8
ParserWhichAlwaysSucceeds ,
8
9
Success ,
9
10
} from './parser.js'
10
11
11
12
/**
12
13
* Substitute the output of a successful parse.
13
14
*/
14
- export const as =
15
- < NewOutput > (
16
- parser : Parser < unknown > ,
17
- newOutput : NewOutput ,
18
- ) : Parser < NewOutput > =>
19
- input =>
20
- either . map ( parser ( input ) , success => ( {
21
- output : newOutput ,
22
- remainingInput : success . remainingInput ,
23
- } ) )
15
+ export const as = < NewOutput > (
16
+ parser : Parser < unknown > ,
17
+ newOutput : NewOutput ,
18
+ ) : Parser < NewOutput > => {
19
+ const replaceOutput = ( success : Success < unknown > ) => ( {
20
+ output : newOutput ,
21
+ remainingInput : success . remainingInput ,
22
+ } )
23
+ return input => either . map ( parser ( input ) , replaceOutput )
24
+ }
24
25
25
26
/**
26
27
* Attempt to parse input with `parser`. If successful, ensure the same input
@@ -31,39 +32,40 @@ export const as =
31
32
* butNot(anySingleCharacter, literal('a'), 'the letter a') // parses any character besides 'a'
32
33
* ```
33
34
*/
34
- export const butNot =
35
- < Output > (
36
- parser : Parser < Output > ,
37
- not : Parser < unknown > ,
38
- notName : string ,
39
- ) : Parser < Output > =>
40
- input =>
35
+ export const butNot = < Output > (
36
+ parser : Parser < Output > ,
37
+ not : Parser < unknown > ,
38
+ notName : string ,
39
+ ) : Parser < Output > => {
40
+ const errorMessage = `input was unexpectedly ${ notName } `
41
+ return input =>
41
42
either . flatMap ( parser ( input ) , success => {
42
43
const notResult = not ( input )
43
44
if ( ! either . isLeft ( notResult ) ) {
44
45
return either . makeLeft ( {
45
46
input,
46
- message : `input was unexpectedly ${ notName } ` ,
47
+ message : errorMessage ,
47
48
} )
48
49
} else {
49
50
return either . makeRight ( success )
50
51
}
51
52
} )
53
+ }
52
54
53
55
/**
54
56
* Map the output of `parser` to another `Parser` which is then applied to the
55
57
* remaining input, flattening the parse results.
56
58
*/
57
- export const flatMap =
58
- < Output , NewOutput > (
59
- parser : Parser < Output > ,
60
- f : ( output : Output ) => Parser < NewOutput > ,
61
- ) : Parser < NewOutput > =>
62
- input =>
63
- either . flatMap ( parser ( input ) , success => {
64
- const nextParser = f ( success . output )
65
- return nextParser ( success . remainingInput )
66
- } )
59
+ export const flatMap = < Output , NewOutput > (
60
+ parser : Parser < Output > ,
61
+ f : ( output : Output ) => Parser < NewOutput > ,
62
+ ) : Parser < NewOutput > => {
63
+ const applyF = ( success : Success < Output > ) => {
64
+ const nextParser = f ( success . output )
65
+ return nextParser ( success . remainingInput )
66
+ }
67
+ return input => either . flatMap ( parser ( input ) , applyF )
68
+ }
67
69
68
70
/**
69
71
* Create a `Parser` from a thunk. This can be useful for recursive parsers.
@@ -82,60 +84,61 @@ export const lazy =
82
84
* lookaheadNot(anySingleCharacter, literal('a'), 'the letter a') // parses the first character of 'ab', but not 'aa'
83
85
* ```
84
86
*/
85
- export const lookaheadNot =
86
- < Output > (
87
- parser : Parser < Output > ,
88
- notFollowedBy : Parser < unknown > ,
89
- followedByName : string ,
90
- ) : Parser < Output > =>
91
- input =>
87
+ export const lookaheadNot = < Output > (
88
+ parser : Parser < Output > ,
89
+ notFollowedBy : Parser < unknown > ,
90
+ followedByName : string ,
91
+ ) : Parser < Output > => {
92
+ const errorMessage = `input was unexpectedly followed by ${ followedByName } `
93
+ return input =>
92
94
either . flatMap ( parser ( input ) , success =>
93
95
either . match ( notFollowedBy ( success . remainingInput ) , {
94
96
left : _ => either . makeRight ( success ) ,
95
97
right : _ =>
96
98
either . makeLeft ( {
97
99
input,
98
- message : `input was unexpectedly followed by ${ followedByName } ` ,
100
+ message : errorMessage ,
99
101
} ) ,
100
102
} ) ,
101
103
)
104
+ }
102
105
103
106
/**
104
107
* Map the output of `parser` to new output.
105
108
*/
106
- export const map =
107
- < Output , NewOutput > (
108
- parser : Parser < Output > ,
109
- f : ( output : Output ) => NewOutput ,
110
- ) : Parser < NewOutput > =>
111
- input =>
112
- either . map ( parser ( input ) , success => ( {
113
- output : f ( success . output ) ,
114
- remainingInput : success . remainingInput ,
115
- } ) )
109
+ export const map = < Output , NewOutput > (
110
+ parser : Parser < Output > ,
111
+ f : ( output : Output ) => NewOutput ,
112
+ ) : Parser < NewOutput > => {
113
+ const applyF = ( success : Success < Output > ) => ( {
114
+ output : f ( success . output ) ,
115
+ remainingInput : success . remainingInput ,
116
+ } )
117
+ return input => either . map ( parser ( input ) , applyF )
118
+ }
116
119
117
120
/**
118
121
* Apply the given `parsers` to the same input until one succeeds or all fail.
119
122
*/
120
- export const oneOf =
121
- <
122
- Parsers extends readonly [
123
- Parser < unknown > ,
124
- Parser < unknown > ,
125
- ...( readonly Parser < unknown > [ ] ) ,
126
- ] ,
127
- > (
128
- parsers : Parsers ,
129
- ) : Parser < OneOfOutput < Parsers > > =>
130
- input =>
131
- parsers . reduce (
123
+ export const oneOf = <
124
+ Parsers extends readonly [
125
+ Parser < unknown > ,
126
+ Parser < unknown > ,
127
+ ...( readonly Parser < unknown > [ ] ) ,
128
+ ] ,
129
+ > (
130
+ parsers : Parsers ,
131
+ ) : Parser < OneOfOutput < Parsers > > => {
132
+ const [ firstParser , ...otherParsers ] = parsers
133
+ return input => {
134
+ const firstResult = firstParser ( input )
135
+ return otherParsers . reduce (
132
136
( result : ReturnType < Parser < OneOfOutput < Parsers > > > , parser ) =>
133
- either . match ( result , {
134
- right : either . makeRight ,
135
- left : _ => parser ( input ) ,
136
- } ) ,
137
- either . makeLeft ( { input, message : '' } ) , // `parsers` is non-empty so this is never returned
137
+ either . isLeft ( result ) ? parser ( input ) : result ,
138
+ firstResult ,
138
139
)
140
+ }
141
+ }
139
142
type OneOfOutput < Parsers extends readonly Parser < unknown > [ ] > = {
140
143
[ Index in keyof Parsers ] : OutputOf < Parsers [ Index ] >
141
144
} [ number ]
@@ -162,62 +165,55 @@ export const sequence =
162
165
> (
163
166
parsers : Parsers ,
164
167
) : Parser < SequenceOutput < Parsers > > =>
165
- input =>
166
- either . map (
167
- parsers . reduce (
168
- (
169
- results : ReturnType <
170
- Parser < readonly SequenceOutput < Parsers > [ number ] [ ] >
171
- > ,
172
- parser ,
173
- ) =>
174
- either . match ( results , {
175
- right : successes =>
176
- either . map ( parser ( successes . remainingInput ) , newSuccess => ( {
177
- remainingInput : newSuccess . remainingInput ,
178
- output : [ ...successes . output , newSuccess . output ] ,
179
- } ) ) ,
180
- left : either . makeLeft ,
181
- } ) ,
182
- either . makeRight ( { remainingInput : input , output : [ ] } ) , // `parsers` is non-empty so this is never returned
183
- ) ,
184
- ( { output, remainingInput } ) => ( {
185
- // The above `reduce` callback constructs `output` such that its
186
- // elements align with `Parsers`, but TypeScript doesn't know that.
187
- output : output as SequenceOutput < Parsers > ,
188
- remainingInput,
189
- } ) ,
168
+ input => {
169
+ const parseResult = parsers . reduce (
170
+ (
171
+ results : ReturnType < Parser < readonly SequenceOutput < Parsers > [ number ] [ ] > > ,
172
+ parser ,
173
+ ) =>
174
+ either . isRight ( results )
175
+ ? either . map ( parser ( results . value . remainingInput ) , newSuccess => ( {
176
+ remainingInput : newSuccess . remainingInput ,
177
+ output : [ ...results . value . output , newSuccess . output ] ,
178
+ } ) )
179
+ : results ,
180
+ either . makeRight ( { remainingInput : input , output : [ ] } ) , // `parsers` is non-empty so this is never returned
190
181
)
182
+ // The above `reduce` callback constructs `output` such that its
183
+ // elements align with `Parsers`, but TypeScript doesn't know that.
184
+ return parseResult as ParserResult < SequenceOutput < Parsers > >
185
+ }
191
186
type SequenceOutput < Parsers extends readonly Parser < unknown > [ ] > = {
192
187
[ Index in keyof Parsers ] : OutputOf < Parsers [ Index ] >
193
188
}
194
189
195
190
/**
196
191
* Refine/transform the output of `parser` via a function which may fail.
197
192
*/
198
- export const transformOutput =
199
- < Output , NewOutput > (
200
- parser : Parser < Output > ,
201
- f : ( output : Output ) => Either < InvalidInputError , NewOutput > ,
202
- ) : Parser < NewOutput > =>
203
- input =>
204
- either . flatMap ( parser ( input ) , success =>
205
- either . map ( f ( success . output ) , output => ( {
206
- output,
207
- remainingInput : success . remainingInput ,
208
- } ) ) ,
209
- )
193
+ export const transformOutput = < Output , NewOutput > (
194
+ parser : Parser < Output > ,
195
+ f : ( output : Output ) => Either < InvalidInputError , NewOutput > ,
196
+ ) : Parser < NewOutput > => {
197
+ const transformation = ( success : Success < Output > ) =>
198
+ either . map ( f ( success . output ) , output => ( {
199
+ output,
200
+ remainingInput : success . remainingInput ,
201
+ } ) )
202
+ return input => either . flatMap ( parser ( input ) , transformation )
203
+ }
210
204
211
205
/**
212
206
* Repeatedly apply `parser` to the input as long as it keeps succeeding.
213
207
* Outputs are collected in an array.
214
208
*/
215
- export const zeroOrMore =
216
- < Output > (
217
- parser : Parser < Output > ,
218
- ) : ParserWhichAlwaysSucceeds < readonly Output [ ] > =>
219
- input => {
220
- const result = oneOf ( [ parser , nothing ] ) ( input )
209
+ export const zeroOrMore = < Output > (
210
+ parser : Parser < Output > ,
211
+ ) : ParserWhichAlwaysSucceeds < readonly Output [ ] > => {
212
+ const parserOrNothing = oneOf ( [ parser , nothing ] )
213
+
214
+ // Give this a name so it can be recursively referenced.
215
+ const thisParser = ( input : string ) : Right < Success < readonly Output [ ] > > => {
216
+ const result = parserOrNothing ( input )
221
217
const success = either . match ( result , {
222
218
left : _ => ( {
223
219
output : [ ] ,
@@ -230,7 +226,7 @@ export const zeroOrMore =
230
226
remainingInput : lastSuccess . remainingInput ,
231
227
}
232
228
} else {
233
- const nextResult = zeroOrMore ( parser ) ( lastSuccess . remainingInput )
229
+ const nextResult = thisParser ( lastSuccess . remainingInput )
234
230
return {
235
231
output : [ lastSuccess . output , ...nextResult . value . output ] ,
236
232
remainingInput : nextResult . value . remainingInput ,
@@ -241,6 +237,9 @@ export const zeroOrMore =
241
237
return either . makeRight ( success )
242
238
}
243
239
240
+ return thisParser
241
+ }
242
+
244
243
type OutputOf < SpecificParser extends Parser < unknown > > = Extract <
245
244
ReturnType < SpecificParser > [ 'value' ] ,
246
245
Success < unknown >
0 commit comments