messageformat2 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ from typing import Any
2
+
3
+ from babel import Locale
4
+
5
+ from messageformat2.builtins import Formatter, Selector, default_registry
6
+ from messageformat2.datamodel import Node
7
+ from messageformat2.parser import parse
8
+ from messageformat2.runtime import format_message as _format_message
9
+
10
+
11
+ def format_message(
12
+ msg: str,
13
+ inputs: dict[str, Any] | None = None,
14
+ locale: Locale | str | None = None,
15
+ *,
16
+ formatters: dict[str, Formatter] | None = None,
17
+ selectors: dict[str, Selector] | None = None,
18
+ ) -> str:
19
+ """Format a message.
20
+
21
+ Use this function if you only need to format a message once.
22
+
23
+ Examples:
24
+ >>> message = Message("Hello, {$name}!")
25
+ >>> message.format({"name": "Alice"})
26
+ 'Hello, Alice!'
27
+ >>> message = Message('''\\
28
+ ... .match {$count :integer}
29
+ ... one {{You have one notification}}
30
+ ... * {{You have {$count} notifications}}''')
31
+ >>> message.format({"count": 3}, "en")
32
+ 'You have 3 notifications'
33
+
34
+ Args:
35
+ msg: The message to format.
36
+ locale: The locale in which to format the message. Defaults to the system locale.
37
+ inputs: Input variables referenced by the message.
38
+ formatters: Additional formatters.
39
+ selectors: Additional selectors.
40
+
41
+ Returns:
42
+ The formatted message.
43
+
44
+ Raises:
45
+ ParseError: If the message contains a syntax error.
46
+ DataModelError: If the message contains a semantic error.
47
+ FormatError: If the message cannot be formatted.
48
+ """
49
+ return Message(msg).format(inputs, locale, formatters=formatters, selectors=selectors)
50
+
51
+
52
+ class Message:
53
+ def __init__(self, msg: str):
54
+ """Create a new Message object that can be formatted.
55
+
56
+ Use this class if you need to format the same message multiple times as
57
+ the message is only parsed once.
58
+
59
+ Args:
60
+ msg: The message.
61
+
62
+ Raises:
63
+ ParseError: If the message contains a syntax error.
64
+ DataModelError: If the message contains a semantic error.
65
+ """
66
+ self.msg = msg
67
+ self._ast = parse(msg)
68
+
69
+ def format(
70
+ self,
71
+ inputs: dict[str, Any] | None = None,
72
+ locale: Locale | str | None = None,
73
+ *,
74
+ formatters: dict[str, Formatter] | None = None,
75
+ selectors: dict[str, Selector] | None = None,
76
+ ) -> str:
77
+ """Format the message.
78
+
79
+ Examples:
80
+ >>> message = Message("Hello, {$name}!")
81
+ >>> message.format({"name": "Alice"})
82
+ 'Hello, Alice!'
83
+ >>> message = Message('''\\
84
+ ... .match {$count :integer}
85
+ ... one {{You have one notification}}
86
+ ... * {{You have {$count} notifications}}''')
87
+ >>> message.format({"count": 3}, "en")
88
+ 'You have 3 notifications'
89
+
90
+ Args:
91
+ locale: The locale in which to format the message. Defaults to the system locale.
92
+ inputs: Input variables referenced by the message.
93
+ formatters: Additional formatters.
94
+ selectors: Additional selectors.
95
+
96
+ Returns:
97
+ The formatted message.
98
+
99
+ Raises:
100
+ FormatError: If the message cannot be formatted.
101
+ """
102
+ if locale is None:
103
+ locale = Locale.default()
104
+ elif not isinstance(locale, Locale):
105
+ locale = Locale.parse(locale)
106
+ if inputs is None:
107
+ inputs = {}
108
+ registry = (
109
+ default_registry
110
+ if not formatters and not selectors
111
+ else default_registry.extend(formatters=formatters, selectors=selectors)
112
+ )
113
+ return _format_message(self._ast, locale, inputs, registry)
114
+
115
+ @property
116
+ def datamodel(self) -> Node:
117
+ """Return the data model representation of the message.
118
+
119
+ Examples:
120
+ >>> message = Message("Hello, {$name}!")
121
+ >>> message.datamodel # doctest: +ELLIPSIS
122
+ PatternMessage(..., pattern=['Hello, ', VariableExpression(arg=VariableRef(name='name'), ...), '!'])
123
+ """
124
+ return self._ast
125
+
126
+ def __repr__(self) -> str:
127
+ return f"Message({self.msg})"
@@ -0,0 +1,557 @@
1
+ import re
2
+
3
+ from messageformat2.datamodel import (
4
+ Attribute,
5
+ CatchallKey,
6
+ DataModelValidator,
7
+ Declaration,
8
+ Expression,
9
+ FunctionAnnotation,
10
+ FunctionExpression,
11
+ InputDeclaration,
12
+ Literal,
13
+ LiteralExpression,
14
+ LocalDeclaration,
15
+ Markup,
16
+ Message,
17
+ Option,
18
+ Pattern,
19
+ PatternMessage,
20
+ SelectMessage,
21
+ UnsupportedAnnotation,
22
+ UnsupportedExpression,
23
+ UnsupportedStatement,
24
+ VariableExpression,
25
+ VariableRef,
26
+ Variant,
27
+ _Matcher,
28
+ )
29
+ from messageformat2.errors import ParseError
30
+
31
+
32
+ _name_start = (
33
+ "^"
34
+ "[a-zA-Z_"
35
+ "\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff"
36
+ "\u0370-\u037d\u037f-\u1fff\u200c-\u200d"
37
+ "\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff"
38
+ "\uf900-\ufdcf\ufdf0-\ufffd\U00010000-\U000effff]"
39
+ )
40
+ name_start = re.compile(_name_start)
41
+ name_char = re.compile(rf"{_name_start}|[0-9-.\u00B7\u0300-\u036F\u203F-\u2040]")
42
+
43
+ number_literal = re.compile(r"^-?(?:(?:0|[1-9])\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?")
44
+
45
+ _whitespace = r"[\s\u3000]"
46
+ whitespace = re.compile(f"^{_whitespace}")
47
+
48
+ text_escape = re.compile(r"^\\[\\{}]")
49
+ quoted_escape = re.compile(r"^\\[\\|]")
50
+ _reserved_escape = r"^\\[\\{|}]"
51
+ reserved_escape = re.compile(f"^{_reserved_escape}")
52
+
53
+ _content_char = (
54
+ "[\u0001-\u0008]|[\u000b-\u000c]|[\u000e-\u001f]|"
55
+ "[\u0021-\\\u002d]|" # \u002d is '-' so needs to be escaped
56
+ "[\u002f-\u003f]|[\u0041-\u005b]|"
57
+ "[\u005d-\u007a]|[\u007e-\u2fff]|[\u3001-\ud7ff]|[\ue000-\U0010ffff]"
58
+ )
59
+ content_char = re.compile(f"^{_content_char}")
60
+
61
+ simple_start_char = re.compile(rf"^{_content_char}|{_whitespace}|[@|]")
62
+ text_char = re.compile(rf"^{_content_char}|{_whitespace}|[.@|]")
63
+ quoted_char = re.compile(rf"^{_content_char}|{_whitespace}|[.@{{}}]")
64
+ _reserved_char = rf"{_content_char}|[.]"
65
+ reserved_char = re.compile(rf"^{_reserved_char}")
66
+
67
+ markup_start = re.compile(rf"{{{_whitespace}?[#/]")
68
+ annotation_start = re.compile(r"^[:^&!%*+<>?~]")
69
+ input_start = re.compile(r"^\.input")
70
+ local_start = re.compile(r"^\.local")
71
+ match_start = re.compile(r"^\.match")
72
+ quoted_pattern_start = re.compile(r"^\{\{")
73
+ quoted_pattern_end = re.compile(r"^\}\}")
74
+ reserved_body_part_start = re.compile(rf"^{_reserved_char}|{_reserved_escape}|\|")
75
+
76
+
77
+ class Queue:
78
+ def __init__(self, text: str) -> None:
79
+ self.queue = text
80
+
81
+ def pop(self, char: str | None = None) -> str:
82
+ if not self.queue:
83
+ msg = "Unexpect end of input"
84
+ raise ParseError(msg)
85
+
86
+ s, rest = self.queue[0], self.queue[1:]
87
+ self.queue = rest
88
+
89
+ if char is not None and s != char:
90
+ msg = f"Expected: {char}, got: {s}"
91
+ raise ParseError(msg)
92
+
93
+ return s
94
+
95
+ def peek(self) -> str | None:
96
+ if self.queue:
97
+ return self.queue[0]
98
+ return None
99
+
100
+ def peek_after_whitespace(self) -> str | None:
101
+ if self.queue:
102
+ i = 0
103
+ while i < len(self.queue) and whitespace.match(self.queue[i]):
104
+ i += 1
105
+ return self.queue[i]
106
+ return None
107
+
108
+ def matches(self, pattern: re.Pattern) -> bool:
109
+ if self.queue:
110
+ return bool(pattern.match(self.queue))
111
+ return False
112
+
113
+ def matches_after_whitespace(self, pattern: re.Pattern) -> bool:
114
+ if self.queue:
115
+ i = 0
116
+ while i < len(self.queue) and whitespace.match(self.queue[i]):
117
+ i += 1
118
+ return bool(pattern.match(self.queue[i:]))
119
+ return False
120
+
121
+ def pop_match(self, pattern: re.Pattern) -> str:
122
+ if self.queue:
123
+ match = pattern.match(self.queue)
124
+ if not match:
125
+ msg = f"Did not match: {self.queue[0]}"
126
+ raise ParseError(msg)
127
+ s, rest = match.group(), self.queue[match.end() :]
128
+ self.queue = rest
129
+ return s
130
+ msg = "Unexpected end of input"
131
+ raise ParseError(msg)
132
+
133
+ def __len__(self) -> int:
134
+ return len(self.queue)
135
+
136
+ def __bool__(self) -> bool:
137
+ return bool(self.queue)
138
+
139
+
140
+ def parse(msg: str) -> Message:
141
+ ast = parse_message(msg)
142
+ DataModelValidator().visit(ast)
143
+ return ast
144
+
145
+
146
+ def parse_message(msg: str) -> Message:
147
+ queue = Queue(msg)
148
+ message = parse_complex_message(queue) if queue.peek() == "." else parse_simple_message(queue)
149
+ if queue:
150
+ msg = f"Expected end of message but instead got: {queue.peek()}"
151
+ raise ParseError(msg)
152
+ return message
153
+
154
+
155
+ def parse_simple_message(queue: Queue) -> PatternMessage:
156
+ if not queue:
157
+ return PatternMessage(declarations=[], pattern=[])
158
+ simple_start = parse_simple_start(queue)
159
+ pattern = parse_pattern(queue)
160
+
161
+ if not pattern:
162
+ return PatternMessage(declarations=[], pattern=simple_start)
163
+
164
+ x = simple_start[-1]
165
+ y = pattern[0]
166
+ match (x, y):
167
+ case (str(), str()):
168
+ pattern = simple_start[:-1] + [x + y] + pattern[1:]
169
+ return PatternMessage(declarations=[], pattern=pattern)
170
+ case _:
171
+ return PatternMessage(declarations=[], pattern=simple_start + pattern)
172
+
173
+
174
+ def parse_complex_message(queue: Queue) -> Message:
175
+ declarations = []
176
+ while queue.peek() == "." and not queue.matches(match_start):
177
+ declarations.append(parse_declaration(queue))
178
+ parse_optional_whitespace(queue)
179
+
180
+ body = parse_complex_body(queue)
181
+ match body:
182
+ case _Matcher(selectors=selectors, variants=variants):
183
+ return SelectMessage(declarations=declarations, selectors=selectors, variants=variants)
184
+ case _:
185
+ return PatternMessage(declarations=declarations, pattern=body)
186
+
187
+
188
+ def parse_declaration(queue: Queue) -> Declaration:
189
+ if queue.matches(input_start):
190
+ return parse_input_declaration(queue)
191
+ if queue.matches(local_start):
192
+ return parse_local_declaration(queue)
193
+ return parse_reserved_statement(queue)
194
+
195
+
196
+ def parse_input_declaration(queue: Queue) -> InputDeclaration:
197
+ queue.pop_match(input_start)
198
+ parse_optional_whitespace(queue)
199
+ queue.pop("{")
200
+ expression = parse_variable_expression(queue)
201
+ queue.pop("}")
202
+ return InputDeclaration(name=expression.arg.name, value=expression)
203
+
204
+
205
+ def parse_local_declaration(queue: Queue) -> LocalDeclaration:
206
+ queue.pop_match(local_start)
207
+ parse_whitespace(queue)
208
+ variable = parse_variable(queue)
209
+ parse_optional_whitespace(queue)
210
+ queue.pop("=")
211
+ parse_optional_whitespace(queue)
212
+ return LocalDeclaration(name=variable.name, value=parse_expression(queue))
213
+
214
+
215
+ def parse_complex_body(queue: Queue) -> _Matcher | Pattern:
216
+ if queue.matches(quoted_pattern_start):
217
+ return parse_quoted_pattern(queue)
218
+ return parse_matcher(queue)
219
+
220
+
221
+ def parse_quoted_pattern(queue: Queue) -> Pattern:
222
+ queue.pop_match(quoted_pattern_start)
223
+ pattern = parse_pattern(queue)
224
+ queue.pop_match(quoted_pattern_end)
225
+ return pattern
226
+
227
+
228
+ def parse_matcher(queue: Queue) -> _Matcher:
229
+ queue.pop_match(match_start)
230
+ parse_optional_whitespace(queue)
231
+ selectors = [parse_selector(queue)]
232
+ while queue.peek_after_whitespace() == "{":
233
+ parse_optional_whitespace(queue)
234
+ selectors.append(parse_selector(queue))
235
+
236
+ parse_optional_whitespace(queue)
237
+ variants = [parse_variant(queue)]
238
+
239
+ while queue.peek_after_whitespace():
240
+ parse_optional_whitespace(queue)
241
+ variants.append(parse_variant(queue))
242
+
243
+ return _Matcher(selectors=selectors, variants=variants)
244
+
245
+
246
+ def parse_selector(queue: Queue) -> Expression:
247
+ return parse_expression(queue)
248
+
249
+
250
+ def parse_variant(queue: Queue) -> Variant:
251
+ keys = [parse_key(queue)]
252
+
253
+ while queue.peek_after_whitespace() and not queue.matches_after_whitespace(quoted_pattern_start):
254
+ parse_whitespace(queue)
255
+ keys.append(parse_key(queue))
256
+
257
+ parse_optional_whitespace(queue)
258
+ return Variant(keys=keys, value=parse_quoted_pattern(queue))
259
+
260
+
261
+ def parse_key(queue: Queue) -> Literal | CatchallKey:
262
+ if queue.peek() == "*":
263
+ queue.pop()
264
+ return CatchallKey(value=None)
265
+ return parse_literal(queue)
266
+
267
+
268
+ def parse_simple_start(queue: Queue) -> Pattern:
269
+ if queue.peek() == "{":
270
+ return [parse_placeholder(queue)]
271
+ if queue.matches(simple_start_char):
272
+ return [queue.pop()]
273
+ if queue.matches(text_escape):
274
+ text = queue.pop_match(text_escape)
275
+ text = text.replace(r"\{", "{").replace(r"\}", "}").replace(r"\\", "\\")
276
+ return [text]
277
+ msg = f"Invalid character: {queue.peek()}"
278
+ raise ParseError(msg)
279
+
280
+
281
+ def parse_pattern(queue: Queue) -> Pattern:
282
+ pattern = []
283
+ string = ""
284
+ while queue and not queue.matches(quoted_pattern_end):
285
+ if queue.matches(text_char):
286
+ string += queue.pop()
287
+ elif queue.matches(text_escape):
288
+ escape = queue.pop_match(text_escape)
289
+ string += escape.replace(r"\{", "{").replace(r"\}", "}").replace(r"\\", "\\")
290
+ else:
291
+ if string:
292
+ pattern.append(string)
293
+ string = ""
294
+ pattern.append(parse_placeholder(queue))
295
+ if string:
296
+ pattern.append(string)
297
+ string = ""
298
+ return pattern
299
+
300
+
301
+ def parse_placeholder(queue: Queue) -> Markup | Expression:
302
+ if queue.matches(markup_start):
303
+ return parse_markup(queue)
304
+ return parse_expression(queue)
305
+
306
+
307
+ def parse_markup(queue: Queue) -> Markup:
308
+ queue.pop("{")
309
+ parse_optional_whitespace(queue)
310
+
311
+ type_ = queue.pop()
312
+ standalone = False
313
+
314
+ name = parse_identifier(queue)
315
+ options = []
316
+ attributes = []
317
+
318
+ while queue.peek_after_whitespace() not in {"/", "}"}:
319
+ parse_whitespace(queue)
320
+ if queue.peek() == "@":
321
+ attributes.append(parse_attribute(queue))
322
+ else:
323
+ options.append(parse_option(queue))
324
+
325
+ parse_optional_whitespace(queue)
326
+
327
+ if type_ == "#" and queue.peek() == "/":
328
+ standalone = True
329
+ queue.pop()
330
+
331
+ queue.pop("}")
332
+ kind = "standalone" if standalone else "open" if type_ == "#" else "close"
333
+ return Markup(kind=kind, name=name, options=options, attributes=attributes)
334
+
335
+
336
+ def parse_expression(queue: Queue) -> Expression:
337
+ queue.pop("{")
338
+ parse_optional_whitespace(queue)
339
+
340
+ if queue.peek() == "$":
341
+ expression = parse_variable_expression(queue)
342
+ elif queue.peek() == ":":
343
+ annotation = parse_function_annotation(queue)
344
+ attributes = []
345
+ while queue.peek_after_whitespace() != "}":
346
+ parse_whitespace(queue)
347
+ attributes.append(parse_attribute(queue))
348
+ expression = FunctionExpression(annotation=annotation, attributes=attributes)
349
+ elif queue.peek() in {"^", "&", "!", "%", "*", "+", "<", ">", "?", "~"}:
350
+ annotation = parse_unsupported_annotation(queue)
351
+ attributes = []
352
+ while queue.peek_after_whitespace() != "}":
353
+ parse_whitespace(queue)
354
+ attributes.append(parse_attribute(queue))
355
+ expression = UnsupportedExpression(annotation=annotation, attributes=attributes)
356
+ else:
357
+ literal = parse_literal(queue)
358
+ annotation = None
359
+ attributes = []
360
+
361
+ if queue.peek_after_whitespace() in {":", "^", "&", "!", "%", "*", "+", "<", ">", "?", "~"}:
362
+ parse_whitespace(queue)
363
+ annotation = parse_annotation(queue)
364
+
365
+ while queue.peek_after_whitespace() != "}":
366
+ parse_whitespace(queue)
367
+ attributes.append(parse_attribute(queue))
368
+ expression = LiteralExpression(arg=literal, annotation=annotation, attributes=attributes)
369
+
370
+ parse_optional_whitespace(queue)
371
+ queue.pop("}")
372
+ return expression
373
+
374
+
375
+ def parse_variable_expression(queue: Queue) -> VariableExpression:
376
+ variable = parse_variable(queue)
377
+ annotation = None
378
+ attributes = []
379
+
380
+ if queue.peek_after_whitespace() in {":", "^", "&", "!", "%", "*", "+", "<", ">", "?", "~"}:
381
+ parse_whitespace(queue)
382
+ annotation = parse_annotation(queue)
383
+
384
+ while queue.peek_after_whitespace() != "}":
385
+ parse_whitespace(queue)
386
+ attributes.append(parse_attribute(queue))
387
+ return VariableExpression(arg=variable, annotation=annotation, attributes=attributes)
388
+
389
+
390
+ def parse_identifier(queue: Queue) -> str:
391
+ namespace = parse_namespace(queue)
392
+ if queue.peek() == ":":
393
+ queue.pop()
394
+ name = parse_name(queue)
395
+ return f"{namespace}:{name}"
396
+ return namespace
397
+
398
+
399
+ def parse_namespace(queue: Queue) -> str:
400
+ return parse_name(queue)
401
+
402
+
403
+ def parse_name(queue: Queue) -> str:
404
+ if not queue.matches(name_start):
405
+ msg = f"Invalid name start: {queue.peek()}"
406
+ raise ParseError(msg)
407
+ name = queue.pop()
408
+
409
+ while queue.matches(name_char):
410
+ name += queue.pop()
411
+ return name
412
+
413
+
414
+ def parse_annotation(queue: Queue) -> FunctionAnnotation | UnsupportedAnnotation:
415
+ if queue.peek() == ":":
416
+ return parse_function_annotation(queue)
417
+ return parse_unsupported_annotation(queue)
418
+
419
+
420
+ def parse_function_annotation(queue: Queue) -> FunctionAnnotation:
421
+ queue.pop(":")
422
+ name = parse_identifier(queue)
423
+ options = []
424
+ while queue.peek_after_whitespace() and queue.matches_after_whitespace(name_start):
425
+ parse_whitespace(queue)
426
+ options.append(parse_option(queue))
427
+
428
+ return FunctionAnnotation(name=name, options=options)
429
+
430
+
431
+ def parse_unsupported_annotation(queue: Queue) -> UnsupportedAnnotation:
432
+ source = queue.pop()
433
+ if queue.matches_after_whitespace(reserved_body_part_start):
434
+ source += parse_optional_whitespace(queue)
435
+ source += parse_reserved_body(queue)
436
+ return UnsupportedAnnotation(source=source)
437
+
438
+
439
+ def parse_reserved_statement(queue: Queue) -> UnsupportedStatement:
440
+ queue.pop(".")
441
+ keyword = parse_name(queue)
442
+
443
+ body = ""
444
+ if queue.matches_after_whitespace(reserved_body_part_start):
445
+ body += parse_whitespace(queue)
446
+ body += parse_reserved_body(queue)
447
+
448
+ parse_optional_whitespace(queue)
449
+ expressions = [parse_expression(queue)]
450
+
451
+ while queue.peek_after_whitespace() == "{":
452
+ parse_optional_whitespace(queue)
453
+ expressions.append(parse_expression(queue))
454
+
455
+ return UnsupportedStatement(keyword=keyword, body=body or None, expressions=expressions)
456
+
457
+
458
+ def parse_reserved_body(queue: Queue) -> str:
459
+ body = parse_reserved_body_part(queue)
460
+ while queue.matches_after_whitespace(reserved_body_part_start):
461
+ body += parse_optional_whitespace(queue)
462
+ body += parse_reserved_body_part(queue)
463
+ return body
464
+
465
+
466
+ def parse_reserved_body_part(queue: Queue) -> str:
467
+ part = ""
468
+ while True:
469
+ if queue.matches(reserved_char):
470
+ part += queue.pop()
471
+ elif queue.matches(reserved_escape):
472
+ part += queue.pop_match(reserved_escape)
473
+ elif queue.peek() == "|":
474
+ part += str(parse_quoted_literal(queue))
475
+ else:
476
+ break
477
+ return part
478
+
479
+
480
+ def parse_attribute(queue: Queue) -> Attribute:
481
+ queue.pop("@")
482
+ name = parse_identifier(queue)
483
+
484
+ if queue.peek_after_whitespace() != "=":
485
+ return Attribute(name=name, value=None)
486
+
487
+ parse_optional_whitespace(queue)
488
+ queue.pop() # '='
489
+ parse_optional_whitespace(queue)
490
+
491
+ value = parse_variable(queue) if queue.peek() == "$" else parse_literal(queue)
492
+ return Attribute(name=name, value=value)
493
+
494
+
495
+ def parse_option(queue: Queue) -> Option:
496
+ name = parse_identifier(queue)
497
+
498
+ parse_optional_whitespace(queue)
499
+ if queue.peek() != "=":
500
+ msg = f"Invalid option: {name}"
501
+ raise ParseError(msg)
502
+ queue.pop()
503
+ parse_optional_whitespace(queue)
504
+
505
+ value = parse_variable(queue) if queue.peek() == "$" else parse_literal(queue)
506
+ return Option(name=name, value=value)
507
+
508
+
509
+ def parse_variable(queue: Queue) -> VariableRef:
510
+ if queue.peek() != "$":
511
+ msg = f"Invalid variable: {queue.peek()}"
512
+ raise ParseError(msg)
513
+ queue.pop()
514
+ return VariableRef(name=parse_name(queue))
515
+
516
+
517
+ def parse_literal(queue: Queue) -> Literal:
518
+ return parse_quoted_literal(queue) if queue.peek() == "|" else parse_unquoted_literal(queue)
519
+
520
+
521
+ def parse_quoted_literal(queue: Queue) -> Literal:
522
+ literal = ""
523
+ queue.pop("|")
524
+ while True:
525
+ if queue.matches(quoted_escape):
526
+ escape = queue.pop_match(quoted_escape)
527
+ if escape == r"\\":
528
+ literal += "\\"
529
+ else:
530
+ literal += "|"
531
+ elif queue.matches(quoted_char):
532
+ literal += queue.pop()
533
+ else:
534
+ break
535
+
536
+ queue.pop("|")
537
+ return Literal(value=literal)
538
+
539
+
540
+ def parse_unquoted_literal(queue: Queue) -> Literal:
541
+ if queue.matches(number_literal):
542
+ return Literal(value=queue.pop_match(number_literal))
543
+ return Literal(value=parse_name(queue))
544
+
545
+
546
+ def parse_whitespace(queue: Queue) -> str:
547
+ if not queue.matches(whitespace):
548
+ msg = f"Expected whitespace: {queue.peek()}"
549
+ raise ParseError(msg)
550
+ return parse_optional_whitespace(queue)
551
+
552
+
553
+ def parse_optional_whitespace(queue: Queue) -> str:
554
+ ws = ""
555
+ while queue.matches(whitespace):
556
+ ws += queue.pop()
557
+ return ws