+
+
+
A Parser is an object that wraps a function whose arguments are
+a string to be parsed and the index on which to begin parsing.
+The function should return either Result.success(next_index, value)
,
+where the next index is where to continue the parse and the value is
+the yielded value, or Result.failure(index, expected)
, where expected
+is a string indicating what was expected, and the index is the index
+of the failure.
+
+
Creates a new Parser from a function that takes a stream
+and returns a Result.
+
+
+ Source code in persil/parser.py
+ 24
+25
+26
+27
+28
+29
+30
+31
+32 | def __init__(
+ self,
+ wrapped_fn: Callable[[Input, int], Result[Output]],
+):
+ """
+ Creates a new Parser from a function that takes a stream
+ and returns a Result.
+ """
+ self.wrapped_fn = wrapped_fn
+
|
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ cut
+
+
+
+
+
+
+
Commit to the current branch by raising the error if it's returned.
+
+
+ Source code in persil/parser.py
+ 37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47 | def cut(self) -> "Parser[Input, Output]":
+ """
+ Commit to the current branch by raising the error if it's returned.
+ """
+
+ @Parser
+ def cut_parser(stream: Input, index: int) -> Result[Output]:
+ result = self(stream, index)
+ return result.ok_or_raise()
+
+ return cut_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ then
+
+
+
+
+
+
+
Returns a parser which, if the initial parser succeeds, will
+continue parsing with other
. This will produce the
+value produced by other
.
+
+
+ Source code in persil/parser.py
+ 49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65 | def then(self, other: "Parser[In, T]") -> "Parser[Input, T]":
+ """
+ Returns a parser which, if the initial parser succeeds, will
+ continue parsing with `other`. This will produce the
+ value produced by `other`.
+ """
+
+ @Parser
+ def bound_parser(stream: Input, index: int) -> Result[T]:
+ result = self(stream, index)
+
+ if isinstance(result, Err):
+ return result
+
+ return other(stream, result.index) # type: ignore
+
+ return bound_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ skip
+
+
+
+
+
+
+
Returns a parser which, if the initial parser succeeds, will
+continue parsing with other
. It will produce the
+value produced by the initial parser.
+
+
+ Source code in persil/parser.py
+ 67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88 | def skip(self, other: "Parser") -> "Parser[Input, Output]":
+ """
+ Returns a parser which, if the initial parser succeeds, will
+ continue parsing with ``other``. It will produce the
+ value produced by the initial parser.
+ """
+
+ @Parser
+ def bound_parser(stream: Input, index: int) -> Result[Output]:
+ result = self(stream, index)
+
+ if isinstance(result, Err):
+ return result
+
+ other_result = other(stream, result.index)
+
+ if isinstance(other_result, Err):
+ return other_result
+
+ return result
+
+ return bound_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ combine
+
+
+
+
+
+
+
Returns a parser which, if the initial parser succeeds, will
+continue parsing with other
. It will produce a tuple
+containing the results from both parsers, in order.
+
The resulting parser fails if other
fails.
+
+
+ Source code in persil/parser.py
+ 104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130 | def combine(
+ self,
+ other: "Parser[Input, T]",
+) -> "Parser[Input, tuple[Output, T]]":
+ """
+ Returns a parser which, if the initial parser succeeds, will
+ continue parsing with `other`. It will produce a tuple
+ containing the results from both parsers, in order.
+
+ The resulting parser fails if `other` fails.
+ """
+
+ @Parser
+ def combined_parser(stream: Input, index: int) -> Result[tuple[Output, T]]:
+ res1 = self(stream, index)
+
+ if isinstance(res1, Err):
+ return res1
+
+ res2 = other(stream, res1.index)
+
+ if isinstance(res2, Err):
+ return res2
+
+ return Ok((res1.value, res2.value), res2.index)
+
+ return combined_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ parse
+
+
+
+
+
+
+
Parses a string or list of tokens and returns the result or raise a ParseError.
+
+
+ Source code in persil/parser.py
+ 138
+139
+140
+141
+142
+143
+144 | def parse(
+ self,
+ stream: Input,
+) -> Output:
+ """Parses a string or list of tokens and returns the result or raise a ParseError."""
+ (result, _) = (self << eof).parse_partial(stream)
+ return result
+
|
+
+
+
+
+
+
+
+
+
+
+
+ parse_partial
+
+
+
+
+
+
+
Parses the longest possible prefix of a given string.
+Returns a tuple of the result and the unparsed remainder,
+or raises ParseError
.
+
+
+ Source code in persil/parser.py
+ 146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164 | def parse_partial(
+ self,
+ stream: Input,
+) -> tuple[Output, Input]:
+ """
+ Parses the longest possible prefix of a given string.
+ Returns a tuple of the result and the unparsed remainder,
+ or raises `ParseError`.
+ """
+
+ result = self(stream, 0)
+
+ if isinstance(result, Err):
+ raise result
+
+ value = result.value
+ remainder = cast(Input, stream[result.index :])
+
+ return (value, remainder)
+
|
+
+
+
+
+
+
+
+
+
+
+
+ map
+
+
+
+
+
+
+
Returns a parser that transforms the produced value of the initial parser
+with map_function
.
+
+
+ Source code in persil/parser.py
+ 182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196 | def map(
+ self,
+ map_function: Callable[[Output], T],
+) -> "Parser[Input, T]":
+ """
+ Returns a parser that transforms the produced value of the initial parser
+ with `map_function`.
+ """
+
+ @Parser
+ def mapped_parser(stream: Input, index: int) -> Result[T]:
+ res = self(stream, index)
+ return res.map(map_function)
+
+ return mapped_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ result
+
+
+
+
+
+
+
Returns a parser that, if the initial parser succeeds, always produces
+the passed in value
.
+
+
+ Source code in persil/parser.py
+ 198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213 | def result(self, value: T) -> "Parser[Input, T]":
+ """
+ Returns a parser that, if the initial parser succeeds, always produces
+ the passed in `value`.
+ """
+
+ @Parser
+ def result_parser(stream: Input, index: int) -> Result[T]:
+ res = self(stream, index)
+
+ if isinstance(res, Err):
+ return res
+
+ return Ok(value, res.index)
+
+ return result_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ times
+
+
+
+
+
+
+
Returns a parser that expects the initial parser at least min
times,
+and at most max
times, and produces a list of the results. If only one
+argument is given, the parser is expected exactly that number of times.
+
+
+
+
+
+
+ PARAMETER |
+ DESCRIPTION |
+
+
+
+
+ min |
+
+
+ Minimal number of times the parser should match.
+
+
+
+ TYPE:
+ int
+
+
+ |
+
+
+ max |
+
+
+ Maximal number of times the parser should match.
+Equals to min by default
+
+
+
+ TYPE:
+ int | None
+
+
+ DEFAULT:
+ None
+
+
+ |
+
+
+
+
+
+ Source code in persil/parser.py
+ 215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256 | def times(
+ self,
+ min: int,
+ max: int | None = None,
+ check_next: bool = False,
+) -> "Parser[Input, list[Output]]":
+ """
+ Returns a parser that expects the initial parser at least `min` times,
+ and at most `max` times, and produces a list of the results. If only one
+ argument is given, the parser is expected exactly that number of times.
+
+ Parameters
+ ----------
+ min
+ Minimal number of times the parser should match.
+ max
+ Maximal number of times the parser should match.
+ Equals to `min` by default
+ """
+ if max is None:
+ max = min
+
+ @Parser
+ def times_parser(stream: Input, index: int) -> Result[list[Output]]:
+ values = []
+ times = 0
+ result = None
+
+ while times < max:
+ result = self(stream, index)
+ if isinstance(result, Ok):
+ values.append(result.value)
+ index = result.index
+ times += 1
+ elif times >= min:
+ break
+ else:
+ return result
+
+ return Ok(values, index)
+
+ return times_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ many
+
+
+
+
+
+
+
Returns a parser that expects the initial parser 0 or more times, and
+produces a list of the results.
+
+
+ Source code in persil/parser.py
+ 258
+259
+260
+261
+262
+263 | def many(self) -> "Parser[Input, list[Output]]":
+ """
+ Returns a parser that expects the initial parser 0 or more times, and
+ produces a list of the results.
+ """
+ return self.times(0, 9999999)
+
|
+
+
+
+
+
+
+
+
+
+
+
+ at_most
+
+
+
+
+
+
+
Returns a parser that expects the initial parser at most n
times, and
+produces a list of the results.
+
+
+ Source code in persil/parser.py
+ 265
+266
+267
+268
+269
+270 | def at_most(self, n: int) -> "Parser[Input, list[Output]]":
+ """
+ Returns a parser that expects the initial parser at most ``n`` times, and
+ produces a list of the results.
+ """
+ return self.times(0, n)
+
|
+
+
+
+
+
+
+
+
+
+
+
+ at_least
+
+
+
+
+
+
+
Returns a parser that expects the initial parser at least n
times, and
+produces a list of the results.
+
+
+ Source code in persil/parser.py
+ 272
+273
+274
+275
+276
+277 | def at_least(self, n: int) -> "Parser[Input, list[Output]]":
+ """
+ Returns a parser that expects the initial parser at least ``n`` times, and
+ produces a list of the results.
+ """
+ return self.times(n) + self.many()
+
|
+
+
+
+
+
+
+
+
+
+
+
+ optional
+
+
+
+
+
+
+
Returns a parser that expects the initial parser zero or once, and maps
+the result to a given default value in the case of no match. If no default
+value is given, None
is used.
+
+
+ Source code in persil/parser.py
+ 279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295 | def optional(self, default: T | None = None) -> "Parser[Input, Output | T | None]":
+ """
+ Returns a parser that expects the initial parser zero or once, and maps
+ the result to a given default value in the case of no match. If no default
+ value is given, ``None`` is used.
+ """
+
+ @Parser
+ def optional_parser(stream: Input, index: int) -> Result[Output | T | None]:
+ res = self(stream, index)
+
+ if isinstance(res, Ok):
+ return Ok(res.value, res.index)
+
+ return Ok(default, index)
+
+ return optional_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ until
+
+
+
+
+
+
+
Returns a parser that expects the initial parser followed by other
.
+The initial parser is expected at least min
times and at most max
times.
+By default, it does not consume other
and it produces a list of the
+results excluding other
. If consume_other
is True
then
+other
is consumed and its result is included in the list of results.
+
+
+ Source code in persil/parser.py
+ 297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348 | def until(
+ self,
+ other: "Parser[Input, T]",
+ min: int = 0,
+ max: int = 999999,
+) -> "Parser[Input, list[Output]]":
+ """
+ Returns a parser that expects the initial parser followed by ``other``.
+ The initial parser is expected at least ``min`` times and at most ``max`` times.
+ By default, it does not consume ``other`` and it produces a list of the
+ results excluding ``other``. If ``consume_other`` is ``True`` then
+ ``other`` is consumed and its result is included in the list of results.
+ """
+
+ @Parser
+ def until_parser(stream: Input, index: int) -> Result[list[Output]]:
+ values: list[Output] = []
+ times = 0
+
+ while True:
+ # try parser first
+ res = other(stream, index)
+
+ if isinstance(res, Ok) and times >= min:
+ return Ok(values, index)
+
+ # exceeded max?
+ if isinstance(res, Ok) and times >= max:
+ # return failure, it matched parser more than max times
+ return Err(index, [f"at most {max} items"], stream)
+
+ # failed, try parser
+ result = self(stream, index)
+
+ if isinstance(result, Ok):
+ values.append(result.value)
+ index = result.index
+ times += 1
+ continue
+
+ if times >= min:
+ # return failure, parser is not followed by other
+ return Err(index, ["did not find other parser"], stream)
+ else:
+ # return failure, it did not match parser at least min times
+ return Err(
+ index,
+ [f"at least {min} items; got {times} item(s)"],
+ stream,
+ )
+
+ return until_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ sep_by
+
+
+
+
+
+
+
Returns a new parser that repeats the initial parser and
+collects the results in a list. Between each item, the sep
parser
+is run (and its return value is discarded). By default it
+repeats with no limit, but minimum and maximum values can be supplied.
+
+
+ Source code in persil/parser.py
+ 350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366 | def sep_by(
+ self, sep: "Parser", *, min: int = 0, max: int = 999999
+) -> "Parser[Input, list[Output]]":
+ """
+ Returns a new parser that repeats the initial parser and
+ collects the results in a list. Between each item, the ``sep`` parser
+ is run (and its return value is discarded). By default it
+ repeats with no limit, but minimum and maximum values can be supplied.
+ """
+ zero_times: Parser[Input, list[Output]] = success([])
+
+ if max == 0:
+ return zero_times
+ res = self.times(1) + (sep >> self).times(min - 1, max - 1)
+ if min == 0:
+ res |= zero_times
+ return res
+
|
+
+
+
+
+
+
+
+
+
+
+
+ desc
+
+
+
+
+
+
+
Returns a new parser with a description added, which is used in the error message
+if parsing fails.
+
+
+ Source code in persil/parser.py
+ 368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381 | def desc(self, description: str) -> "Parser[Input, Output]":
+ """
+ Returns a new parser with a description added, which is used in the error message
+ if parsing fails.
+ """
+
+ @Parser
+ def desc_parser(stream: Input, index: int) -> Result[Output]:
+ result = self(stream, index)
+ if isinstance(result, Ok):
+ return result
+ return Err(index, [description], stream)
+
+ return desc_parser
+
|
+
+
+
+
+
+
+
+
+
+
+
+ should_fail
+
+
+
+
+
+
+
Returns a parser that fails when the initial parser succeeds, and succeeds
+when the initial parser fails (consuming no input). A description must
+be passed which is used in parse failure messages.
+
This is essentially a negative lookahead
+
+
+ Source code in persil/parser.py
+ 383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399 | def should_fail(self, description: str) -> "Parser[Input, Result[Output]]":
+ """
+ Returns a parser that fails when the initial parser succeeds, and succeeds
+ when the initial parser fails (consuming no input). A description must
+ be passed which is used in parse failure messages.
+
+ This is essentially a negative lookahead
+ """
+
+ @Parser
+ def fail_parser(stream: Input, index: int):
+ res = self(stream, index)
+ if isinstance(res, Ok):
+ return Err(index, [description], stream)
+ return Ok(res, index)
+
+ return fail_parser
+
|
+
+
+
+
+
+
+
+
+
+