1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
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
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
|
"""Tests for the plain-English explanation renderer in :mod:`edify.introspect.explain`."""
from edify import RegexBuilder
from edify.elements.types.base import BaseElement
from edify.elements.types.captures import (
BackReferenceElement,
CaptureElement,
NamedBackReferenceElement,
NamedCaptureElement,
)
from edify.elements.types.chars import (
AnyOfCharsElement,
AnythingButCharsElement,
AnythingButRangeElement,
AnythingButStringElement,
CharElement,
RangeElement,
StringElement,
)
from edify.elements.types.groups import (
AnyOfElement,
AssertAheadElement,
AssertBehindElement,
AssertNotAheadElement,
AssertNotBehindElement,
GroupElement,
SubexpressionElement,
)
from edify.elements.types.leaves import (
AlphanumericElement,
AnyCharElement,
CarriageReturnElement,
DigitElement,
EndOfInputElement,
LetterElement,
LowercaseElement,
NewLineElement,
NonDigitElement,
NonWhitespaceCharElement,
NonWordBoundaryElement,
NonWordElement,
NoopElement,
NullByteElement,
StartOfInputElement,
TabElement,
UppercaseElement,
WhitespaceCharElement,
WordBoundaryElement,
WordElement,
)
from edify.elements.types.quantifiers import (
AtLeastElement,
AtMostElement,
BetweenElement,
BetweenLazyElement,
ExactlyElement,
OneOrMoreElement,
OneOrMoreLazyElement,
OptionalElement,
ZeroOrMoreElement,
ZeroOrMoreLazyElement,
)
from edify.introspect.explain import explain_elements
def _explain(*elements: BaseElement) -> str:
return explain_elements(tuple(elements))
def _inline_phrase(element: BaseElement) -> str:
return _explain(GroupElement(children=(element,)))
def _plural_phrase(element: BaseElement) -> str:
return _explain(OneOrMoreElement(child=element))
def _optional_phrase(element: BaseElement) -> str:
return _explain(OptionalElement(child=element))
def _accepted_examples(output: str) -> list[str]:
if "Text this pattern accepts:" not in output:
return []
accept_block = output.split("Text this pattern accepts:")[1]
stripped_block = accept_block.strip()
lines = stripped_block.splitlines()
return [line.strip() for line in lines if line.strip()]
def test_empty_elements_produces_empty_string_notice():
assert _explain() == "This pattern is empty and matches an empty string."
def test_single_digit_reads_as_must_contain_one_digit():
output = _explain(DigitElement())
assert output.startswith("- The text must contain one digit (0-9).")
assert "Text this pattern accepts:" in output
def test_start_of_input_switches_lead_in_to_must_start_with():
output = _explain(StartOfInputElement(), DigitElement())
assert "The text must start with" in output
def test_trailing_end_of_input_alone_is_absorbed_silently():
output = _explain(DigitElement(), EndOfInputElement())
assert "The text must contain one digit" in output
assert "very end" not in output
def test_trailing_assert_ahead_then_end_becomes_must_end_at_line():
output = _explain(
DigitElement(),
AssertAheadElement(children=(CharElement(value="/"),)),
EndOfInputElement(),
)
assert 'The text must end at "/".' in output
def test_second_element_uses_then_the_text_must_have():
output = _explain(DigitElement(), CharElement(value="-"))
assert "Then the text must have" in output
def test_optional_element_gets_dedicated_optional_prefix():
output = _explain(OptionalElement(child=DigitElement()))
assert "Optional:" in output
def test_zero_or_more_reads_as_zero_or_more_phrase():
output = _explain(ZeroOrMoreElement(child=DigitElement()))
assert "zero or more digits" in output
def test_zero_or_more_lazy_reads_as_as_few_as_possible():
output = _explain(ZeroOrMoreLazyElement(child=DigitElement()))
assert "as few as possible" in output
def test_one_or_more_reads_as_one_or_more_phrase():
output = _explain(OneOrMoreElement(child=DigitElement()))
assert "one or more digits" in output
def test_one_or_more_lazy_reads_as_as_few_as_possible():
output = _explain(OneOrMoreLazyElement(child=DigitElement()))
assert "as few as possible" in output
def test_exactly_reads_as_exactly_n_phrase():
output = _explain(ExactlyElement(times=4, child=DigitElement()))
assert "exactly 4 digits" in output
def test_at_least_reads_as_at_least_n_phrase():
output = _explain(AtLeastElement(times=3, child=DigitElement()))
assert "at least 3 digits" in output
def test_at_most_reads_as_at_most_n_phrase():
output = _explain(AtMostElement(times=5, child=DigitElement()))
assert "at most 5 digits" in output
def test_between_reads_as_between_lower_and_upper_phrase():
output = _explain(BetweenElement(lower=2, upper=5, child=DigitElement()))
assert "between 2 and 5 digits" in output
def test_between_lazy_reads_as_as_few_as_possible():
output = _explain(BetweenLazyElement(lower=2, upper=5, child=DigitElement()))
assert "as few as possible" in output
def test_word_boundary_gets_a_dedicated_bullet():
output = _explain(WordBoundaryElement())
assert "word boundary" in output
def test_non_word_boundary_gets_a_dedicated_bullet():
output = _explain(NonWordBoundaryElement())
assert "must NOT fall on a word boundary" in output
def test_capture_step_describes_children_inline_phrase():
output = _explain(CaptureElement(children=(DigitElement(),)))
assert "one digit" in output
def test_named_capture_step_describes_children_inline_phrase():
output = _explain(NamedCaptureElement(name="year", children=(DigitElement(),)))
assert "one digit" in output
def test_backreference_step_names_the_group_index():
output = _explain(BackReferenceElement(index=2))
assert "group #2" in output
def test_named_backreference_step_names_the_group_label():
output = _explain(NamedBackReferenceElement(name="year"))
assert 'group labeled "year"' in output
def test_assert_ahead_reads_as_must_be_followed_by():
output = _explain(DigitElement(), AssertAheadElement(children=(CharElement(value="/"),)))
assert "must be followed by" in output
def test_assert_not_ahead_reads_as_must_not_be_followed_by():
output = _explain(
DigitElement(),
AssertNotAheadElement(children=(CharElement(value="/"),)),
)
assert "must NOT be followed by" in output
def test_assert_behind_reads_as_just_before_this_point():
output = _explain(DigitElement(), AssertBehindElement(children=(CharElement(value="/"),)))
assert "Just before this point" in output
def test_assert_not_behind_reads_as_just_before_must_not_have_appeared():
output = _explain(
DigitElement(),
AssertNotBehindElement(children=(CharElement(value="/"),)),
)
assert "must NOT have appeared" in output
def test_group_step_describes_children_inline_phrase():
output = _explain(GroupElement(children=(DigitElement(),)))
assert "one digit" in output
def test_alternation_uses_either_or_for_two_alternatives():
output = _explain(
AnyOfElement(children=(StringElement(value="cat"), StringElement(value="dog")))
)
assert 'either "cat" or "dog"' in output
def test_alternation_uses_either_a_b_or_c_for_three_alternatives():
output = _explain(
AnyOfElement(
children=(
StringElement(value="cat"),
StringElement(value="dog"),
StringElement(value="fish"),
)
)
)
assert 'either "cat", "dog", or "fish"' in output
def test_alternation_with_single_child_falls_through_to_single_phrase():
output = _explain(AnyOfElement(children=(StringElement(value="only"),)))
assert '"only"' in output
def test_alternation_with_no_children_reads_as_nothing():
output = _explain(AnyOfElement(children=()))
assert "nothing" in output
def test_examples_section_lists_up_to_three_accepted_strings():
output = _explain(DigitElement())
strings = _accepted_examples(output)
assert 1 <= len(strings) <= 3
def test_examples_are_unique_across_the_three_seeds():
output = _explain(DigitElement())
strings = _accepted_examples(output)
assert len(set(strings)) == len(strings)
def test_examples_section_omitted_when_only_empty_strings_are_generated():
output = _explain(StartOfInputElement(), EndOfInputElement())
assert "Text this pattern accepts:" not in output
def test_subexpression_is_flattened_into_the_step_list():
output = _explain(SubexpressionElement(children=(DigitElement(), CharElement(value="-"))))
assert output.count("- ") >= 2
def test_char_element_reads_as_quoted_literal():
output = _explain(CharElement(value="\\-"))
assert '"-"' in output
def test_string_element_reads_as_quoted_literal():
output = _explain(StringElement(value="hi"))
assert '"hi"' in output
def test_range_element_describes_from_start_to_end():
output = _explain(RangeElement(start="a", end="z"))
assert 'from "a" through "z"' in output
def test_any_of_chars_describes_from_the_set():
output = _explain(AnyOfCharsElement(value="abc"))
assert 'from the set "abc"' in output
def test_anything_but_chars_describes_not_from_the_set():
output = _explain(AnythingButCharsElement(value="abc"))
assert 'NOT from the set "abc"' in output
def test_anything_but_range_describes_outside_range():
output = _explain(AnythingButRangeElement(start="a", end="z"))
assert 'outside "a" through "z"' in output
def test_anything_but_string_describes_position_for_position_mismatch():
output = _explain(AnythingButStringElement(value="stop"))
assert "position-for-position" in output
def test_any_char_reads_as_any_single_character():
output = _explain(AnyCharElement())
assert "any single character" in output
def test_whitespace_char_mentions_space_tab_newline():
output = _explain(WhitespaceCharElement())
assert "whitespace character" in output
def test_non_whitespace_char_reads_as_non_whitespace():
output = _explain(NonWhitespaceCharElement())
assert "non-whitespace character" in output
def test_non_digit_char_reads_as_non_digit_character():
output = _explain(NonDigitElement())
assert "non-digit character" in output
def test_word_element_reads_as_letter_digit_or_underscore():
output = _explain(WordElement())
assert "one letter, digit, or underscore" in output
def test_non_word_element_reads_as_not_letter_digit_or_underscore():
output = _explain(NonWordElement())
assert "not a letter, digit, or underscore" in output
def test_new_line_element_labeled_as_line_feed():
output = _explain(NewLineElement())
assert "line-feed character" in output
def test_carriage_return_element_labeled_as_carriage_return():
output = _explain(CarriageReturnElement())
assert "carriage-return character" in output
def test_tab_element_labeled_as_tab_character():
output = _explain(TabElement())
assert "tab character" in output
def test_null_byte_element_labeled_as_null_byte():
output = _explain(NullByteElement())
assert "null byte" in output
def test_letter_element_labeled_a_to_z_case_insensitive():
output = _explain(LetterElement())
assert "one letter" in output
def test_uppercase_element_labeled_uppercase_range():
output = _explain(UppercaseElement())
assert "uppercase letter" in output
def test_lowercase_element_labeled_lowercase_range():
output = _explain(LowercaseElement())
assert "lowercase letter" in output
def test_alphanumeric_element_labeled_letter_or_digit():
output = _explain(AlphanumericElement())
assert "letter or digit" in output
def test_noop_element_labeled_nothing_inline_phrase():
output = _explain(GroupElement(children=(NoopElement(),)))
assert "nothing" in output
def test_regex_explain_end_to_end_via_builder():
output = RegexBuilder().digit().to_regex().explain()
assert "one digit" in output
def test_examples_use_digit_rotation_for_a_pure_digit_pattern():
output = _explain(DigitElement())
strings = _accepted_examples(output)
assert all(text.isdigit() for text in strings)
def test_examples_for_alternation_pick_a_different_branch_per_seed():
output = _explain(
AnyOfElement(
children=(
StringElement(value="cat"),
StringElement(value="dog"),
StringElement(value="fish"),
)
)
)
strings = set(_accepted_examples(output))
assert strings == {"cat", "dog", "fish"}
def test_examples_for_optional_include_both_present_and_absent_forms():
output = _explain(DigitElement(), OptionalElement(child=CharElement(value="x")), DigitElement())
strings = _accepted_examples(output)
assert any("x" in text for text in strings)
def test_examples_for_exactly_produce_that_length_string():
output = _explain(ExactlyElement(times=4, child=DigitElement()))
strings = _accepted_examples(output)
assert all(len(text) == 4 for text in strings)
def test_examples_for_at_least_produce_more_than_lower_bound_length():
output = _explain(AtLeastElement(times=2, child=DigitElement()))
strings = _accepted_examples(output)
assert all(len(text) >= 2 for text in strings)
def test_examples_for_at_most_produce_bounded_length():
output = _explain(AtMostElement(times=3, child=DigitElement()))
strings = _accepted_examples(output)
assert all(1 <= len(text) <= 3 for text in strings)
def test_examples_for_between_stay_within_range():
output = _explain(BetweenElement(lower=2, upper=4, child=DigitElement()))
strings = _accepted_examples(output)
assert all(2 <= len(text) <= 4 for text in strings)
def test_examples_for_between_lazy_stay_within_range():
output = _explain(BetweenLazyElement(lower=2, upper=4, child=DigitElement()))
strings = _accepted_examples(output)
assert all(2 <= len(text) <= 4 for text in strings)
def test_examples_for_backreference_use_a_placeholder():
output = _explain(
CaptureElement(children=(DigitElement(),)),
BackReferenceElement(index=1),
)
strings = _accepted_examples(output)
assert all("a" in text for text in strings)
def test_examples_for_named_backreference_use_a_placeholder():
output = _explain(
NamedCaptureElement(name="a", children=(DigitElement(),)),
NamedBackReferenceElement(name="a"),
)
strings = _accepted_examples(output)
assert all("a" in text for text in strings)
def test_examples_for_anything_but_chars_pick_a_char_not_in_set():
output = _explain(AnythingButCharsElement(value="abc"))
strings = _accepted_examples(output)
for text in strings:
for character in text:
assert character not in "abc"
def test_examples_for_anything_but_range_pick_a_char_outside_range():
output = _explain(AnythingButRangeElement(start="a", end="z"))
strings = _accepted_examples(output)
for text in strings:
for character in text:
assert not ("a" <= character <= "z")
def test_examples_for_anything_but_string_have_matching_length():
output = _explain(AnythingButStringElement(value="stop"))
strings = _accepted_examples(output)
assert all(len(text) == 4 for text in strings)
def test_examples_for_anything_but_string_empty_produces_empty_seed():
output = _explain(AnythingButStringElement(value=""))
assert "Text this pattern accepts:" not in output
def test_examples_for_any_of_chars_empty_uses_default_letter():
output = _explain(AnyOfCharsElement(value=""))
strings = _accepted_examples(output)
assert all(text == "a" for text in strings)
def test_examples_for_negative_lookaround_contribute_nothing():
output = _explain(
DigitElement(),
AssertNotAheadElement(children=(CharElement(value="/"),)),
)
strings = _accepted_examples(output)
assert all(text.isdigit() for text in strings)
def test_pick_character_not_in_uses_bang_when_set_exhausts_alphanumerics():
everything = "abcdefghijklmnopqrstuvwxyz0123456789"
output = _explain(AnythingButCharsElement(value=everything))
assert _accepted_examples(output) == ["!"]
def test_pick_character_outside_range_uses_bang_when_range_covers_alphanumerics():
output = _explain(AnythingButRangeElement(start="0", end="z"))
assert _accepted_examples(output) == ["!"]
def test_optional_inner_wraps_char_literal():
assert 'Optional: "-".' in _optional_phrase(CharElement(value="\\-"))
def test_optional_inner_wraps_string_literal():
assert 'Optional: "hi".' in _optional_phrase(StringElement(value="hi"))
def test_optional_inner_delegates_to_inline_for_leaf_elements():
assert "one digit" in _optional_phrase(DigitElement())
def test_describe_inline_falls_back_to_class_name_for_unknown_type():
class MysteryElement(BaseElement):
pass
assert "MysteryElement" in _inline_phrase(MysteryElement())
def test_describe_plural_falls_back_to_of_inline_for_unknown_type():
class MysteryElement(BaseElement):
pass
assert "one or more of MysteryElement" in _plural_phrase(MysteryElement())
def test_plural_any_char_reads_as_generic_characters():
assert "characters (any character)" in _plural_phrase(AnyCharElement())
def test_plural_whitespace_reads_as_whitespace_characters():
assert "whitespace characters" in _plural_phrase(WhitespaceCharElement())
def test_plural_non_whitespace_reads_as_non_whitespace_characters():
assert "non-whitespace characters" in _plural_phrase(NonWhitespaceCharElement())
def test_plural_non_digit_reads_as_non_digit_characters():
assert "non-digit characters" in _plural_phrase(NonDigitElement())
def test_plural_word_reads_as_letters_digits_underscores():
assert "letters, digits, or underscores" in _plural_phrase(WordElement())
def test_plural_non_word_reads_as_not_letters_digits_or_underscores():
assert "not letters, digits, or underscores" in _plural_phrase(NonWordElement())
def test_plural_new_line_reads_as_line_feed_characters():
assert "line-feed characters" in _plural_phrase(NewLineElement())
def test_plural_carriage_return_reads_as_carriage_return_characters():
assert "carriage-return characters" in _plural_phrase(CarriageReturnElement())
def test_plural_tab_reads_as_tab_characters():
assert "tab characters" in _plural_phrase(TabElement())
def test_plural_null_byte_reads_as_null_bytes():
assert "null bytes" in _plural_phrase(NullByteElement())
def test_plural_letter_reads_as_letters_a_to_z():
assert "letters (a-z or A-Z)" in _plural_phrase(LetterElement())
def test_plural_uppercase_reads_as_uppercase_letters():
assert "uppercase letters" in _plural_phrase(UppercaseElement())
def test_plural_lowercase_reads_as_lowercase_letters():
assert "lowercase letters" in _plural_phrase(LowercaseElement())
def test_plural_alphanumeric_reads_as_letters_or_digits():
assert "letters or digits" in _plural_phrase(AlphanumericElement())
def test_plural_char_literal_reads_as_copies_of_character():
assert "copies of the character" in _plural_phrase(CharElement(value="\\-"))
def test_plural_string_literal_reads_as_copies_of_text():
assert "copies of the text" in _plural_phrase(StringElement(value="hi"))
def test_plural_range_reads_as_from_through():
assert 'from "a" through "z"' in _plural_phrase(RangeElement(start="a", end="z"))
def test_plural_any_of_chars_reads_as_from_the_set():
assert 'from the set "abc"' in _plural_phrase(AnyOfCharsElement(value="abc"))
def test_plural_anything_but_chars_reads_as_not_from_the_set():
assert 'NOT from the set "abc"' in _plural_phrase(AnythingButCharsElement(value="abc"))
def test_plural_anything_but_range_reads_as_outside_range():
assert 'outside "a" through "z"' in _plural_phrase(AnythingButRangeElement(start="a", end="z"))
def test_plural_unknown_element_falls_back_to_of_inline_form():
class MysteryElement(BaseElement):
pass
assert "one or more of MysteryElement" in _plural_phrase(MysteryElement())
def test_inline_start_of_input_reads_as_very_beginning_of_text():
assert "the very beginning of the text" in _inline_phrase(StartOfInputElement())
def test_inline_end_of_input_reads_as_very_end_of_text():
assert "the very end of the text" in _inline_phrase(EndOfInputElement())
def test_inline_word_boundary_reads_as_word_boundary():
assert "a word boundary" in _inline_phrase(WordBoundaryElement())
def test_inline_non_word_boundary_reads_as_non_word_boundary_position():
assert "a non-word-boundary position" in _inline_phrase(NonWordBoundaryElement())
def test_inline_optional_reads_as_an_optional_x():
assert "an optional " in _inline_phrase(OptionalElement(child=DigitElement()))
def test_inline_zero_or_more_reads_as_zero_or_more_x():
assert "zero or more" in _inline_phrase(ZeroOrMoreElement(child=DigitElement()))
def test_inline_zero_or_more_lazy_notes_as_few_as_possible():
assert "as few as possible" in _inline_phrase(ZeroOrMoreLazyElement(child=DigitElement()))
def test_inline_one_or_more_reads_as_one_or_more_x():
assert "one or more" in _inline_phrase(OneOrMoreElement(child=DigitElement()))
def test_inline_one_or_more_lazy_notes_as_few_as_possible():
assert "as few as possible" in _inline_phrase(OneOrMoreLazyElement(child=DigitElement()))
def test_inline_exactly_reads_as_exactly_n_x():
assert "exactly 4" in _inline_phrase(ExactlyElement(times=4, child=DigitElement()))
def test_inline_at_least_reads_as_at_least_n_x():
assert "at least 3" in _inline_phrase(AtLeastElement(times=3, child=DigitElement()))
def test_inline_at_most_reads_as_at_most_n_x():
assert "at most 2" in _inline_phrase(AtMostElement(times=2, child=DigitElement()))
def test_inline_between_reads_as_between_lower_and_upper():
assert "between 2 and 5" in _inline_phrase(
BetweenElement(lower=2, upper=5, child=DigitElement())
)
def test_inline_between_lazy_notes_as_few_as_possible():
assert "as few as possible" in _inline_phrase(
BetweenLazyElement(lower=2, upper=5, child=DigitElement())
)
def test_inline_capture_reads_as_child_captured_marker():
assert "(captured)" in _inline_phrase(CaptureElement(children=(DigitElement(),)))
def test_inline_named_capture_includes_the_label():
assert 'label "year"' in _inline_phrase(
NamedCaptureElement(name="year", children=(DigitElement(),))
)
def test_inline_group_reads_as_children_inline_phrase():
assert "one digit" in _inline_phrase(GroupElement(children=(DigitElement(),)))
def test_inline_subexpression_reads_as_children_inline_phrase():
alternation = AnyOfElement(
children=(SubexpressionElement(children=(DigitElement(),)), StringElement(value="x"))
)
assert "one digit" in _inline_phrase(alternation)
def test_inline_alternation_reads_as_either_or_phrase():
assert "either" in _inline_phrase(
AnyOfElement(children=(StringElement(value="a"), StringElement(value="b")))
)
def test_inline_backreference_reads_as_same_text_that_group_captured():
assert "group #1 captured" in _inline_phrase(BackReferenceElement(index=1))
def test_inline_named_backreference_reads_as_that_the_name_group_captured():
assert 'the "year" group captured' in _inline_phrase(NamedBackReferenceElement(name="year"))
def test_optional_inner_reads_group_children_inline_phrase():
assert "one digit" in _optional_phrase(GroupElement(children=(DigitElement(),)))
def test_describe_inline_children_reads_as_nothing_when_empty():
assert "The text must contain nothing" in _explain(GroupElement(children=()))
def test_describe_inline_children_joins_multiple_phrases_with_then():
joined = _explain(GroupElement(children=(DigitElement(), StringElement(value="X"))))
assert ", then " in joined
def test_example_for_unknown_element_returns_empty_string():
class MysteryElement(BaseElement):
pass
assert _accepted_examples(_explain(MysteryElement())) == []
|