0
# Parser Operators
1
2
Monadic and compositional operators for sequencing, choice, transformation, and control flow between parsers. These operators enable both infix notation and functional composition patterns, providing the core building blocks for parser combination.
3
4
## Capabilities
5
6
### Monadic Operations
7
8
Core monadic operations that enable sequential composition and value transformation in parser chains.
9
10
```python { .api }
11
def bind(self, fn):
12
"""
13
Monadic bind operation (>>=).
14
Passes the result of this parser to fn, continues with returned parser.
15
16
Args:
17
fn (function): Function that takes parsed value and returns Parser
18
19
Returns:
20
Parser: Combined parser
21
22
Note:
23
If this parser fails, fn is not called and failure is returned.
24
"""
25
26
def compose(self, other):
27
"""
28
Sequential composition (>>).
29
Runs this parser, then other parser, returns result of other.
30
31
Args:
32
other (Parser): Parser to run after this one
33
34
Returns:
35
Parser: Combined parser returning other's result
36
37
Note:
38
If this parser fails, other is not run.
39
"""
40
41
def parsecmap(self, fn):
42
"""
43
Transform the result of this parser with a function.
44
45
Args:
46
fn (function): Function to transform the parsed value
47
48
Returns:
49
Parser: Parser that returns fn(parsed_value)
50
51
Note:
52
Only applies fn if parsing succeeds.
53
"""
54
```
55
56
### Choice Operations
57
58
Alternative selection between parsers with different backtracking behaviors.
59
60
```python { .api }
61
def choice(self, other):
62
"""
63
Choice without backtrack (|).
64
Try this parser first. If it fails without consuming input, try other.
65
66
Args:
67
other (Parser): Alternative parser to try
68
69
Returns:
70
Parser: Parser that returns result of first successful parser
71
72
Note:
73
No backtracking - if this parser consumes input then fails,
74
other is not tried.
75
"""
76
77
def try_choice(self, other):
78
"""
79
Choice with backtrack (^).
80
Try this parser first. If it fails, try other regardless of input consumption.
81
82
Args:
83
other (Parser): Alternative parser to try
84
85
Returns:
86
Parser: Parser that returns result of first successful parser
87
88
Note:
89
Full backtracking - other is tried even if this parser consumed input.
90
"""
91
```
92
93
### Sequence Operations
94
95
Operations for combining parser results and controlling parser termination.
96
97
```python { .api }
98
def joint(self, other):
99
"""
100
Joint operation (+).
101
Combine results from two sequential parsers.
102
103
Args:
104
other (Parser): Parser to run after this one
105
106
Returns:
107
Parser: Parser that returns combined/concatenated results
108
109
Note:
110
Results are aggregated using Value.aggregate().
111
"""
112
113
def skip(self, other):
114
"""
115
Skip operation (<).
116
Run this parser, then other parser, return this parser's result.
117
Other parser consumes its input.
118
119
Args:
120
other (Parser): Parser to run and consume after this one
121
122
Returns:
123
Parser: Parser that returns this parser's result
124
"""
125
126
def ends_with(self, other):
127
"""
128
Ends with operation (<<).
129
Run this parser, then other parser, return this parser's result.
130
Other parser does not consume input.
131
132
Args:
133
other (Parser): Parser to check but not consume after this one
134
135
Returns:
136
Parser: Parser that returns this parser's result
137
138
Note:
139
other parser must succeed but doesn't advance position.
140
"""
141
```
142
143
### Parser Enhancement
144
145
Operations for adding metadata and error handling to parsers.
146
147
```python { .api }
148
def mark(self):
149
"""
150
Add position information to parser result.
151
152
Returns:
153
Parser: Parser that returns (start_pos, value, end_pos)
154
155
Note:
156
Positions are (line, column) tuples with 0-based indexing.
157
"""
158
159
def desc(self, description):
160
"""
161
Add description for better error messages.
162
163
Args:
164
description (str): Description of what this parser expects
165
166
Returns:
167
Parser: Parser with improved error reporting
168
169
Note:
170
If parser fails, error will show this description.
171
"""
172
```
173
174
### Operator Overloads
175
176
Python operator overloads for convenient infix syntax.
177
178
```python { .api }
179
# Infix operators available on Parser instances:
180
parser1 | parser2 # choice (calls choice())
181
parser1 ^ parser2 # try_choice (calls try_choice())
182
parser1 + parser2 # joint (calls joint())
183
parser1 >> parser2 # compose (calls compose())
184
parser1 >>= fn # bind (calls bind())
185
parser1 << parser2 # ends_with (calls ends_with())
186
parser1 < parser2 # skip (calls skip())
187
```
188
189
## Functional API
190
191
Standalone functions that mirror the Parser methods for functional programming style.
192
193
```python { .api }
194
def bind(p, fn):
195
"""Functional version of Parser.bind()"""
196
197
def compose(pa, pb):
198
"""Functional version of Parser.compose()"""
199
200
def joint(pa, pb):
201
"""Functional version of Parser.joint()"""
202
203
def choice(pa, pb):
204
"""Functional version of Parser.choice()"""
205
206
def try_choice(pa, pb):
207
"""Functional version of Parser.try_choice()"""
208
209
def parsecmap(p, fn):
210
"""
211
Functional version of Parser.parsecmap().
212
213
Note:
214
This function has a bug in the original implementation.
215
It calls p.map(fn) but should call p.parsecmap(fn).
216
"""
217
218
def mark(p):
219
"""Functional version of Parser.mark()"""
220
221
def parse(p, text, index):
222
"""
223
Utility function to parse with a parser.
224
225
Args:
226
p (Parser): Parser to use
227
text (str): Text to parse
228
index (int): Starting position
229
230
Returns:
231
Parsed result
232
233
Note:
234
This function appears to have a bug in the original implementation.
235
It calls p.parse(text, index) but Parser.parse() only takes text parameter.
236
"""
237
```
238
239
## Usage Examples
240
241
### Basic Sequencing
242
243
```python
244
from parsec import string, letter, digit, many
245
246
# Compose - run first parser, then second, return second result
247
parser = string("hello") >> string("world")
248
result = parser.parse("helloworld") # Returns "world"
249
250
# Joint - combine results from both parsers
251
parser = string("hello") + string("world")
252
result = parser.parse("helloworld") # Returns "helloworld"
253
254
# Skip - use first result, but consume second parser's input
255
parser = many(letter()) < string(".")
256
result = parser.parse("hello.") # Returns ['h','e','l','l','o']
257
258
# Ends with - first result, second must match but doesn't consume
259
parser = many(letter()) << string("123")
260
result = parser.parse("hello123more") # Returns ['h','e','l','l','o'], "123more" remains
261
```
262
263
### Choice Operations
264
265
```python
266
from parsec import string, letter
267
268
# Regular choice - no backtrack
269
parser = string("ab") | string("ac")
270
result = parser.parse("ab") # Returns "ab"
271
# parser.parse("ac") would fail because string("ab") consumed 'a' but failed on 'c'
272
273
# Try choice - with backtrack
274
parser = string("ab") ^ string("ac")
275
result = parser.parse("ab") # Returns "ab"
276
result = parser.parse("ac") # Returns "ac" (backtracked after "ab" failed)
277
278
# Multiple choices
279
parser = string("cat") ^ string("car") ^ string("card")
280
result = parser.parse("card") # Returns "card"
281
```
282
283
### Transformation and Binding
284
285
```python
286
from parsec import many1, digit, string
287
288
# Transform results
289
number_parser = many1(digit()).parsecmap(lambda digits: int("".join(digits)))
290
result = number_parser.parse("123") # Returns 123 (integer)
291
292
# Monadic binding for conditional parsing
293
@generate
294
def conditional_parser():
295
op = yield string("+") ^ string("-")
296
num1 = yield number_parser
297
num2 = yield number_parser
298
if op == "+":
299
return num1 + num2
300
else:
301
return num1 - num2
302
303
result = conditional_parser.parse("+123456") # Returns 579
304
305
# Using bind directly
306
def make_repeat_parser(char):
307
return many1(string(char))
308
309
parser = string("a").bind(make_repeat_parser)
310
result = parser.parse("aaaa") # Returns ['a', 'a', 'a', 'a']
311
```
312
313
### Position Marking
314
315
```python
316
from parsec import mark, many1, letter, string
317
318
# Mark parser positions
319
marked_word = mark(many1(letter()))
320
start_pos, word, end_pos = marked_word.parse("hello")
321
# start_pos: (0, 0), word: ['h','e','l','l','o'], end_pos: (0, 5)
322
323
# Mark multiple elements
324
@generate
325
def marked_lines():
326
lines = yield many(mark(many(letter())) < string("\n"))
327
return lines
328
329
result = marked_lines.parse("hello\nworld\n")
330
# Returns [((0,0), ['h','e','l','l','o'], (0,5)), ((1,0), ['w','o','r','l','d'], (1,5))]
331
```
332
333
### Error Descriptions
334
335
```python
336
from parsec import string, letter, many1
337
338
# Add descriptive error messages
339
identifier = many1(letter()).desc("identifier")
340
keyword = string("def").desc("def keyword")
341
342
parser = keyword >> identifier
343
344
try:
345
result = parser.parse("define") # Should be "def", not "define"
346
except ParseError as e:
347
print(e.expected) # "def keyword"
348
349
# Chain descriptions
350
complex_parser = (
351
string("function").desc("function keyword") >>
352
many1(letter()).desc("function name") >>
353
string("(").desc("opening parenthesis")
354
)
355
```
356
357
### Functional Style
358
359
```python
360
from parsec import bind, compose, choice, many1, letter, digit, parsecmap
361
362
# Functional composition
363
def to_int(digits):
364
return int("".join(digits))
365
366
number_parser = parsecmap(many1(digit()), to_int)
367
368
# Functional choice and composition
369
word_or_number = choice(
370
parsecmap(many1(letter()), "".join),
371
number_parser
372
)
373
374
# Functional binding
375
def repeat_char_parser(char):
376
return many1(string(char))
377
378
parser = bind(letter(), repeat_char_parser)
379
```