nomen-lang 0.6.1 → 0.6.3
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.
- package/core/System/List.nm +12 -0
- package/core/System/StringBuilder.nm +12 -4
- package/core/System/Text/Regex.nm +704 -77
- package/core/docs/System/Text/Regex.md +1 -1
- package/dist/index.mjs +1362 -362
- package/package.json +1 -1
- package/src/index.ts +8 -56
- package/src/init.ts +12 -3
- package/src/test.ts +57 -3
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
//
|
|
3
3
|
// `test` reports whether a pattern matches anywhere in the input; `match`
|
|
4
4
|
// returns the first matched substring (or "" when there is no match).
|
|
5
|
-
// Patterns use POSIX Extended Regular Expression syntax
|
|
5
|
+
// Patterns use POSIX Extended Regular Expression syntax, extended with
|
|
6
|
+
// non-capturing groups `(?:...)`, lookaround (`(?=...)` / `(?!...)` /
|
|
7
|
+
// `(?<=...)` / `(?<!...)`), and counted repetition (`{n}` / `{n,}` / `{n,m}`).
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* Regular-expression helpers (`test`/`match`/`count`/`replace_all`) using POSIX ERE syntax
|
|
10
|
+
* Regular-expression helpers (`test`/`match`/`count`/`replace_all`) using POSIX ERE syntax plus `(?:...)`, lookaround, and `{n,m}` counted repetition
|
|
9
11
|
**/
|
|
10
12
|
pub struct Regex {
|
|
11
13
|
// Test whether pattern matches anywhere in input.
|
|
@@ -255,12 +257,13 @@ func fold_pattern = (string pattern, move out string) {
|
|
|
255
257
|
while i < n; i += 1 {
|
|
256
258
|
var char c = pattern.at(i)
|
|
257
259
|
if (c as int) == 92 {
|
|
258
|
-
// Escaped char: shorthands
|
|
259
|
-
//
|
|
260
|
-
// punctuation is copied
|
|
260
|
+
// Escaped char: shorthands, backrefs, and control escapes
|
|
261
|
+
// (\n \r \t) keep their backslash; other escaped letters
|
|
262
|
+
// become both-case classes; escaped punctuation is copied
|
|
263
|
+
// literally.
|
|
261
264
|
if i + 1 < n {
|
|
262
265
|
var char e = pattern.at(i + 1)
|
|
263
|
-
if is_shorthand(e) || is_backref_digit(e) {
|
|
266
|
+
if is_shorthand(e) || is_backref_digit(e) || is_control_escape(e) {
|
|
264
267
|
sb.append_char(c)
|
|
265
268
|
sb.append_char(e)
|
|
266
269
|
} else {
|
|
@@ -317,9 +320,10 @@ func fold_class = (string pattern, int cs, int ce, ref StringBuilder dst) {
|
|
|
317
320
|
if i + 1 < content_end {
|
|
318
321
|
var char e = pattern.at(i + 1)
|
|
319
322
|
dst.append_char(e)
|
|
320
|
-
// A shorthand
|
|
321
|
-
// gain their case
|
|
322
|
-
|
|
323
|
+
// A shorthand or control escape (\n \r \t) keeps its
|
|
324
|
+
// backslash; other escaped letters gain their case
|
|
325
|
+
// counterpart.
|
|
326
|
+
if is_alpha_byte(e) && !is_shorthand(e) && !is_control_escape(e) {
|
|
323
327
|
dst.append_char(swap_case_byte(e))
|
|
324
328
|
}
|
|
325
329
|
i += 1
|
|
@@ -478,9 +482,11 @@ func alt_first_bytes = (string pattern, int start, int end, ref StringBuilder sb
|
|
|
478
482
|
while i < end {
|
|
479
483
|
var char c = pattern.at(i)
|
|
480
484
|
if (c as int) == 92 {
|
|
481
|
-
// escaped literal: first byte is the
|
|
482
|
-
//
|
|
483
|
-
//
|
|
485
|
+
// escaped literal: first byte is the byte the escape names
|
|
486
|
+
// (\n is LF, \r is CR, \t is TAB; any other escaped character
|
|
487
|
+
// is its own literal byte) — unless it's a backreference
|
|
488
|
+
// (depends on a capture) or a complement shorthand (not an
|
|
489
|
+
// inclusion set), which disable the prefilter.
|
|
484
490
|
if i + 1 < end {
|
|
485
491
|
var char e = pattern.at(i + 1)
|
|
486
492
|
if is_backref_digit(e) {
|
|
@@ -491,7 +497,7 @@ func alt_first_bytes = (string pattern, int start, int end, ref StringBuilder sb
|
|
|
491
497
|
return -1
|
|
492
498
|
}
|
|
493
499
|
} else {
|
|
494
|
-
sb.append_char(e)
|
|
500
|
+
sb.append_char(escaped_byte_value(e) as char)
|
|
495
501
|
}
|
|
496
502
|
// A '?'/'*' quantifier makes the atom nullable — the first
|
|
497
503
|
// byte can also come from whatever follows it.
|
|
@@ -526,9 +532,17 @@ func alt_first_bytes = (string pattern, int start, int end, ref StringBuilder sb
|
|
|
526
532
|
}
|
|
527
533
|
if c == '(' {
|
|
528
534
|
var int a_end = atom_end_pos(pattern, i, end)
|
|
535
|
+
var int kind = group_kind(pattern, i, end)
|
|
536
|
+
if kind >= 2 {
|
|
537
|
+
// Lookaround: zero-width — contributes no byte, so the first
|
|
538
|
+
// byte comes from whatever follows.
|
|
539
|
+
i = a_end
|
|
540
|
+
continue
|
|
541
|
+
}
|
|
529
542
|
// The group's first bytes are the union of its content's
|
|
530
543
|
// top-level alternatives' first bytes.
|
|
531
|
-
|
|
544
|
+
var int inner = group_inner_start(pattern, i, end)
|
|
545
|
+
if collect_first_bytes(pattern, inner, a_end - 1, ref sb) < 0 {
|
|
532
546
|
return -1
|
|
533
547
|
}
|
|
534
548
|
var int cont = nullable_quant_after(pattern, a_end, end)
|
|
@@ -583,13 +597,128 @@ func nullable_quant_after = (string pattern, int after, int end, out int) {
|
|
|
583
597
|
}
|
|
584
598
|
return j
|
|
585
599
|
}
|
|
600
|
+
if q == '{' {
|
|
601
|
+
// A counted bound is nullable only when its minimum is 0 ({0}, {0,},
|
|
602
|
+
// {0,m}); the first byte then also comes from what follows.
|
|
603
|
+
var int qe = counted_quant_end(pattern, j, end)
|
|
604
|
+
if qe >= 0 {
|
|
605
|
+
var int qmin = counted_quant_min(pattern, j)
|
|
606
|
+
if qmin == 0 {
|
|
607
|
+
var int k = qe
|
|
608
|
+
if k < end {
|
|
609
|
+
if pattern.at(k) == '?' {
|
|
610
|
+
k += 1
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return k
|
|
614
|
+
}
|
|
615
|
+
return -1
|
|
616
|
+
}
|
|
617
|
+
}
|
|
586
618
|
return -1
|
|
587
619
|
}
|
|
588
620
|
|
|
621
|
+
// Parse a counted repetition bound `{n}`, `{n,}`, or `{n,m}` whose '{' is at
|
|
622
|
+
// pos. Returns the position just past the closing '}', or -1 when the braces
|
|
623
|
+
// are not a valid bound (the '{' is then a literal character).
|
|
624
|
+
func counted_quant_end = (string pattern, int pos, int pat_end, out int) {
|
|
625
|
+
var int i = pos + 1
|
|
626
|
+
var int digits = i
|
|
627
|
+
while i < pat_end {
|
|
628
|
+
if is_digit_byte(pattern.at(i)) {
|
|
629
|
+
i += 1
|
|
630
|
+
} else {
|
|
631
|
+
break
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if i == digits {
|
|
635
|
+
return -1
|
|
636
|
+
}
|
|
637
|
+
if i < pat_end {
|
|
638
|
+
if pattern.at(i) == ',' {
|
|
639
|
+
i += 1
|
|
640
|
+
while i < pat_end {
|
|
641
|
+
if is_digit_byte(pattern.at(i)) {
|
|
642
|
+
i += 1
|
|
643
|
+
} else {
|
|
644
|
+
break
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if i < pat_end {
|
|
650
|
+
if pattern.at(i) == '}' {
|
|
651
|
+
return i + 1
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return -1
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// The minimum repetition count of a `{...}` bound at pos (assumes a valid
|
|
658
|
+
// bound was found by counted_quant_end).
|
|
659
|
+
func counted_quant_min = (string pattern, int pos, out int) {
|
|
660
|
+
var int i = pos + 1
|
|
661
|
+
var value = 0
|
|
662
|
+
while i < pattern.length {
|
|
663
|
+
if is_digit_byte(pattern.at(i)) {
|
|
664
|
+
value = value * 10 + ((pattern.at(i) as int) - 48)
|
|
665
|
+
i += 1
|
|
666
|
+
} else {
|
|
667
|
+
break
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return value
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// The maximum repetition count of a `{...}` bound at pos: the value after the
|
|
674
|
+
// comma for `{n,m}`, -1 for the unbounded `{n,}`, and the minimum for `{n}`.
|
|
675
|
+
func counted_quant_max = (string pattern, int pos, int pat_end, out int) {
|
|
676
|
+
var int i = pos + 1
|
|
677
|
+
while i < pat_end {
|
|
678
|
+
if is_digit_byte(pattern.at(i)) {
|
|
679
|
+
i += 1
|
|
680
|
+
} else {
|
|
681
|
+
break
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if i >= pat_end {
|
|
685
|
+
return counted_quant_min(pattern, pos)
|
|
686
|
+
}
|
|
687
|
+
if pattern.at(i) != ',' {
|
|
688
|
+
return counted_quant_min(pattern, pos)
|
|
689
|
+
}
|
|
690
|
+
var int j = i + 1
|
|
691
|
+
var value = 0
|
|
692
|
+
var int digits = j
|
|
693
|
+
while j < pat_end {
|
|
694
|
+
if is_digit_byte(pattern.at(j)) {
|
|
695
|
+
value = value * 10 + ((pattern.at(j) as int) - 48)
|
|
696
|
+
j += 1
|
|
697
|
+
} else {
|
|
698
|
+
break
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if j == digits {
|
|
702
|
+
// `{n,}` — unbounded maximum.
|
|
703
|
+
return -1
|
|
704
|
+
}
|
|
705
|
+
return value
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Whether a pattern character is an ASCII digit.
|
|
709
|
+
func is_digit_byte = (char c, out bool) {
|
|
710
|
+
var int code = c as int
|
|
711
|
+
return code >= 48 && code <= 57
|
|
712
|
+
}
|
|
713
|
+
|
|
589
714
|
// Append the members of character class [start..end) to sb. Ranges (a-z) are
|
|
590
715
|
// expanded; negation (^) is ignored (we want all candidates).
|
|
591
|
-
func add_class_chars = (
|
|
592
|
-
|
|
716
|
+
func add_class_chars = (
|
|
717
|
+
string pattern,
|
|
718
|
+
int start,
|
|
719
|
+
int end,
|
|
720
|
+
ref StringBuilder sb
|
|
721
|
+
) { var int cs = start
|
|
593
722
|
if cs < end {
|
|
594
723
|
if pattern.at(cs) == '^' {
|
|
595
724
|
cs += 1
|
|
@@ -597,22 +726,34 @@ func add_class_chars = (string pattern, int start, int end, ref StringBuilder sb
|
|
|
597
726
|
}
|
|
598
727
|
var int i = cs
|
|
599
728
|
while i < end {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
729
|
+
var char c = pattern.at(i)
|
|
730
|
+
if (c as int) == 92 {
|
|
731
|
+
// Escaped byte: contribute the byte the escape names (\n is
|
|
732
|
+
// LF, \r is CR, \t is TAB; any other escaped character is its
|
|
733
|
+
// own literal byte) so the prefilter stays a superset of the
|
|
734
|
+
// matcher's first bytes.
|
|
735
|
+
if i + 1 < end {
|
|
736
|
+
sb.append_char(escaped_byte_value(pattern.at(i + 1)) as char)
|
|
737
|
+
}
|
|
738
|
+
i += 2
|
|
739
|
+
} else {
|
|
740
|
+
if i + 2 < end {
|
|
741
|
+
if pattern.at(i + 1) == '-' {
|
|
742
|
+
var int lo = pattern.at(i) as int
|
|
743
|
+
var int hi = pattern.at(i + 2) as int
|
|
744
|
+
while lo <= hi {
|
|
745
|
+
sb.append_char(lo as char)
|
|
746
|
+
lo += 1
|
|
747
|
+
}
|
|
748
|
+
i += 3
|
|
749
|
+
} else {
|
|
750
|
+
sb.append_char(c)
|
|
751
|
+
i += 1
|
|
607
752
|
}
|
|
608
|
-
i += 3
|
|
609
753
|
} else {
|
|
610
|
-
sb.append_char(
|
|
754
|
+
sb.append_char(c)
|
|
611
755
|
i += 1
|
|
612
756
|
}
|
|
613
|
-
} else {
|
|
614
|
-
sb.append_char(pattern.at(i))
|
|
615
|
-
i += 1
|
|
616
757
|
}
|
|
617
758
|
}
|
|
618
759
|
}
|
|
@@ -698,6 +839,28 @@ func match_here = (
|
|
|
698
839
|
return match_here(pattern, pat_pos + 1, pat_end, input, in_pos, in_len, ref caps)
|
|
699
840
|
}
|
|
700
841
|
|
|
842
|
+
// Lookaround assertions are zero-width: evaluate the inner pattern and
|
|
843
|
+
// continue matching the rest at the SAME input position.
|
|
844
|
+
if pc == '(' && is_non_capturing_open(pattern, pat_pos, pat_end) {
|
|
845
|
+
var int look_kind = group_kind(pattern, pat_pos, pat_end)
|
|
846
|
+
if look_kind >= 2 {
|
|
847
|
+
var int look_end = atom_end_pos(pattern, pat_pos, pat_end)
|
|
848
|
+
var int look_inner = group_inner_start(pattern, pat_pos, pat_end)
|
|
849
|
+
if lookaround_succeeds(
|
|
850
|
+
look_kind,
|
|
851
|
+
pattern,
|
|
852
|
+
look_inner,
|
|
853
|
+
look_end - 1,
|
|
854
|
+
input,
|
|
855
|
+
in_pos,
|
|
856
|
+
in_len,
|
|
857
|
+
) == false {
|
|
858
|
+
return -1
|
|
859
|
+
}
|
|
860
|
+
return match_here(pattern, look_end, pat_end, input, in_pos, in_len, ref caps)
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
701
864
|
// Determine the bounds of the current atom.
|
|
702
865
|
var int a_end = atom_end_pos(pattern, pat_pos, pat_end)
|
|
703
866
|
|
|
@@ -758,9 +921,56 @@ func match_here = (
|
|
|
758
921
|
ref caps,
|
|
759
922
|
)
|
|
760
923
|
}
|
|
924
|
+
if q == '{' {
|
|
925
|
+
// Counted repetition: {n}, {n,}, {n,m}, optionally lazy ({..}?).
|
|
926
|
+
var int qe = counted_quant_end(pattern, a_end, pat_end)
|
|
927
|
+
if qe >= 0 {
|
|
928
|
+
var int qmin = counted_quant_min(pattern, a_end)
|
|
929
|
+
var int qmax = counted_quant_max(pattern, a_end, pat_end)
|
|
930
|
+
var counted_lazy = 0
|
|
931
|
+
var int counted_rest = qe
|
|
932
|
+
if counted_rest < pat_end {
|
|
933
|
+
if pattern.at(counted_rest) == '?' {
|
|
934
|
+
counted_lazy = 1
|
|
935
|
+
counted_rest += 1
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return match_counted(
|
|
939
|
+
pattern,
|
|
940
|
+
pat_pos,
|
|
941
|
+
a_end,
|
|
942
|
+
counted_rest,
|
|
943
|
+
pat_end,
|
|
944
|
+
input,
|
|
945
|
+
in_pos,
|
|
946
|
+
qmin,
|
|
947
|
+
qmax,
|
|
948
|
+
counted_lazy,
|
|
949
|
+
0,
|
|
950
|
+
in_len,
|
|
951
|
+
ref caps,
|
|
952
|
+
)
|
|
953
|
+
}
|
|
954
|
+
}
|
|
761
955
|
}
|
|
762
956
|
|
|
763
|
-
// No quantifier: match the atom once, then the rest of the pattern.
|
|
957
|
+
// No quantifier: match the atom once, then the rest of the pattern. A
|
|
958
|
+
// GROUP atom goes through the continuation-aware path: its inner
|
|
959
|
+
// alternatives retry when the rest fails on an earlier one (JS-style
|
|
960
|
+
// backtracking — `(head|header)(\s|$|>)` must still match "header>").
|
|
961
|
+
if pc == '(' {
|
|
962
|
+
return match_group_then_rest(
|
|
963
|
+
pattern,
|
|
964
|
+
pat_pos,
|
|
965
|
+
a_end,
|
|
966
|
+
a_end,
|
|
967
|
+
pat_end,
|
|
968
|
+
input,
|
|
969
|
+
in_pos,
|
|
970
|
+
in_len,
|
|
971
|
+
ref caps,
|
|
972
|
+
)
|
|
973
|
+
}
|
|
764
974
|
var int next = match_single_atom(pattern, pat_pos, a_end, input, in_pos, in_len, ref caps)
|
|
765
975
|
if next < 0 {
|
|
766
976
|
return -1
|
|
@@ -768,6 +978,97 @@ func match_here = (
|
|
|
768
978
|
return match_here(pattern, a_end, pat_end, input, next, in_len, ref caps)
|
|
769
979
|
}
|
|
770
980
|
|
|
981
|
+
// The position of the first top-level '|' in [start, end), or -1. Escapes,
|
|
982
|
+
// character classes, and nested groups are skipped so only depth-0
|
|
983
|
+
// alternation operators count (same scanning discipline as match_here).
|
|
984
|
+
func next_top_level_alt = (string pattern, int start, int end, out int) {
|
|
985
|
+
var depth = 0
|
|
986
|
+
var int i = start
|
|
987
|
+
while i < end {
|
|
988
|
+
var char c = pattern.at(i)
|
|
989
|
+
if (c as int) == 92 {
|
|
990
|
+
i += 2
|
|
991
|
+
} else {
|
|
992
|
+
if c == '[' {
|
|
993
|
+
i = atom_end_pos(pattern, i, end)
|
|
994
|
+
} else {
|
|
995
|
+
if c == '(' {
|
|
996
|
+
depth += 1
|
|
997
|
+
}
|
|
998
|
+
if c == ')' {
|
|
999
|
+
if depth > 0 {
|
|
1000
|
+
depth -= 1
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (c as int) == 124 {
|
|
1004
|
+
if depth == 0 {
|
|
1005
|
+
return i
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
i += 1
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return -1
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Match the group atom spanning [atom_start, atom_end) ('(' at atom_start)
|
|
1016
|
+
// and then the continuation [rest, pat_end), retrying the group's LATER
|
|
1017
|
+
// alternatives whenever the continuation fails on an earlier one. Without
|
|
1018
|
+
// this, a group commits to the first alternative that matches the group
|
|
1019
|
+
// ALONE and a failing continuation aborts the whole match — the engine had
|
|
1020
|
+
// no backtracking into a group's alternatives (PORT.md: `(head|header)`
|
|
1021
|
+
// never reached `header` when `(\s|$|>)` rejected what `head` left behind).
|
|
1022
|
+
// A group without a top-level '|' behaves exactly like the old
|
|
1023
|
+
// match-atom-then-continue path, and a failed trial restores the group's
|
|
1024
|
+
// previous capture span before the next attempt.
|
|
1025
|
+
func match_group_then_rest = (
|
|
1026
|
+
string pattern,
|
|
1027
|
+
int atom_start,
|
|
1028
|
+
int atom_end,
|
|
1029
|
+
int rest,
|
|
1030
|
+
int pat_end,
|
|
1031
|
+
string input,
|
|
1032
|
+
int in_pos,
|
|
1033
|
+
int in_len,
|
|
1034
|
+
ref List<int> caps,
|
|
1035
|
+
out int
|
|
1036
|
+
) {
|
|
1037
|
+
var int g = group_index_at(pattern, atom_start)
|
|
1038
|
+
var int at = last_capture_index(caps, g)
|
|
1039
|
+
var int old_start = -1
|
|
1040
|
+
var int old_end = -1
|
|
1041
|
+
if at >= 0 {
|
|
1042
|
+
old_start = caps.at(at + 1)
|
|
1043
|
+
old_end = caps.at(at + 2)
|
|
1044
|
+
}
|
|
1045
|
+
var int inner_start = group_inner_start(pattern, atom_start, pat_end)
|
|
1046
|
+
var int inner_end = atom_end - 1
|
|
1047
|
+
var int s = inner_start
|
|
1048
|
+
while s <= inner_end {
|
|
1049
|
+
var int bar = next_top_level_alt(pattern, s, inner_end)
|
|
1050
|
+
var int e = inner_end
|
|
1051
|
+
if bar >= 0 {
|
|
1052
|
+
e = bar
|
|
1053
|
+
}
|
|
1054
|
+
var int next = match_here(pattern, s, e, input, in_pos, in_len, ref caps)
|
|
1055
|
+
if next >= 0 {
|
|
1056
|
+
record_capture(ref caps, g, in_pos, next)
|
|
1057
|
+
var int r = match_here(pattern, rest, pat_end, input, next, in_len, ref caps)
|
|
1058
|
+
if r >= 0 {
|
|
1059
|
+
return r
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
record_capture(ref caps, g, old_start, old_end)
|
|
1063
|
+
if bar >= 0 {
|
|
1064
|
+
s = bar + 1
|
|
1065
|
+
} else {
|
|
1066
|
+
s = inner_end + 1
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return -1
|
|
1070
|
+
}
|
|
1071
|
+
|
|
771
1072
|
// Return the position just past the atom that starts at pat_pos. Handles
|
|
772
1073
|
// character classes [...], groups (...), and escaped characters (\x).
|
|
773
1074
|
func atom_end_pos = (string pattern, int pat_pos, int pat_end, out int) {
|
|
@@ -857,7 +1158,7 @@ func match_single_atom = (
|
|
|
857
1158
|
}
|
|
858
1159
|
var int next = match_here(
|
|
859
1160
|
pattern,
|
|
860
|
-
atom_start
|
|
1161
|
+
group_inner_start(pattern, atom_start, atom_end),
|
|
861
1162
|
atom_end - 1,
|
|
862
1163
|
input,
|
|
863
1164
|
in_pos,
|
|
@@ -888,7 +1189,7 @@ func match_single_atom = (
|
|
|
888
1189
|
return -1
|
|
889
1190
|
}
|
|
890
1191
|
if in_pos < in_len {
|
|
891
|
-
if input.at(in_pos) == ec {
|
|
1192
|
+
if (input.at(in_pos) as int) == escaped_byte_value(ec) {
|
|
892
1193
|
return in_pos + 1
|
|
893
1194
|
}
|
|
894
1195
|
}
|
|
@@ -936,13 +1237,156 @@ func char_matches_atom = (
|
|
|
936
1237
|
if is_shorthand(ec) {
|
|
937
1238
|
return matches_shorthand(ec, ic)
|
|
938
1239
|
}
|
|
939
|
-
return ic == ec
|
|
1240
|
+
return (ic as int) == escaped_byte_value(ec)
|
|
940
1241
|
}
|
|
941
1242
|
return false
|
|
942
1243
|
}
|
|
943
1244
|
return pc == ic
|
|
944
1245
|
}
|
|
945
1246
|
|
|
1247
|
+
// Evaluate a lookaround assertion at in_pos:
|
|
1248
|
+
// 2 (?=inner) succeeds when inner matches starting here
|
|
1249
|
+
// 3 (?!inner) succeeds when inner does NOT match starting here
|
|
1250
|
+
// 4 (?<=inner) succeeds when inner matches ending exactly here
|
|
1251
|
+
// 5 (?<!inner) succeeds when no such match ends here
|
|
1252
|
+
// Captures inside lookarounds are not recorded (a throwaway capture log is
|
|
1253
|
+
// used) — the supported patterns are capture-free assertions.
|
|
1254
|
+
func lookaround_succeeds = (
|
|
1255
|
+
int kind,
|
|
1256
|
+
string pattern,
|
|
1257
|
+
int inner,
|
|
1258
|
+
int inner_end,
|
|
1259
|
+
string input,
|
|
1260
|
+
int in_pos,
|
|
1261
|
+
int in_len,
|
|
1262
|
+
out bool
|
|
1263
|
+
) {
|
|
1264
|
+
if kind == 2 {
|
|
1265
|
+
var trial = List<int>()
|
|
1266
|
+
return match_here(pattern, inner, inner_end, input, in_pos, in_len, ref trial) >= 0
|
|
1267
|
+
}
|
|
1268
|
+
if kind == 3 {
|
|
1269
|
+
var trial = List<int>()
|
|
1270
|
+
return match_here(pattern, inner, inner_end, input, in_pos, in_len, ref trial) < 0
|
|
1271
|
+
}
|
|
1272
|
+
var found = false
|
|
1273
|
+
var s = 0
|
|
1274
|
+
while s <= in_pos {
|
|
1275
|
+
var trial = List<int>()
|
|
1276
|
+
var int e = match_here(pattern, inner, inner_end, input, s, in_len, ref trial)
|
|
1277
|
+
if e == in_pos {
|
|
1278
|
+
found = true
|
|
1279
|
+
break
|
|
1280
|
+
}
|
|
1281
|
+
s += 1
|
|
1282
|
+
}
|
|
1283
|
+
if kind == 4 {
|
|
1284
|
+
return found
|
|
1285
|
+
}
|
|
1286
|
+
return found == false
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Match the atom [atom_start..atom_end) between min_count and max_count times
|
|
1290
|
+
// (max_count -1 means unbounded), then the continuation [rest..pat_end). This
|
|
1291
|
+
// backs counted repetition `{n}` / `{n,}` / `{n,m}`; greedy tries one more
|
|
1292
|
+
// iteration before stopping, lazy stops first. An atom that matches the empty
|
|
1293
|
+
// string is not repeated further (an empty iteration cannot make progress).
|
|
1294
|
+
func match_counted = (
|
|
1295
|
+
string pattern,
|
|
1296
|
+
int atom_start,
|
|
1297
|
+
int atom_end,
|
|
1298
|
+
int rest,
|
|
1299
|
+
int pat_end,
|
|
1300
|
+
string input,
|
|
1301
|
+
int in_pos,
|
|
1302
|
+
int min_count,
|
|
1303
|
+
int max_count,
|
|
1304
|
+
int lazy,
|
|
1305
|
+
int count,
|
|
1306
|
+
int in_len,
|
|
1307
|
+
ref List<int> caps,
|
|
1308
|
+
out int
|
|
1309
|
+
) {
|
|
1310
|
+
var can_stop = count >= min_count
|
|
1311
|
+
var can_more = max_count < 0 || count < max_count
|
|
1312
|
+
if lazy > 0 {
|
|
1313
|
+
if can_stop {
|
|
1314
|
+
var int stop = match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
1315
|
+
if stop >= 0 {
|
|
1316
|
+
return stop
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if can_more {
|
|
1320
|
+
var int next = match_single_atom(
|
|
1321
|
+
pattern,
|
|
1322
|
+
atom_start,
|
|
1323
|
+
atom_end,
|
|
1324
|
+
input,
|
|
1325
|
+
in_pos,
|
|
1326
|
+
in_len,
|
|
1327
|
+
ref caps,
|
|
1328
|
+
)
|
|
1329
|
+
if next >= 0 {
|
|
1330
|
+
if next != in_pos {
|
|
1331
|
+
return match_counted(
|
|
1332
|
+
pattern,
|
|
1333
|
+
atom_start,
|
|
1334
|
+
atom_end,
|
|
1335
|
+
rest,
|
|
1336
|
+
pat_end,
|
|
1337
|
+
input,
|
|
1338
|
+
next,
|
|
1339
|
+
min_count,
|
|
1340
|
+
max_count,
|
|
1341
|
+
lazy,
|
|
1342
|
+
count + 1,
|
|
1343
|
+
in_len,
|
|
1344
|
+
ref caps,
|
|
1345
|
+
)
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
return -1
|
|
1350
|
+
}
|
|
1351
|
+
if can_more {
|
|
1352
|
+
var int next = match_single_atom(
|
|
1353
|
+
pattern,
|
|
1354
|
+
atom_start,
|
|
1355
|
+
atom_end,
|
|
1356
|
+
input,
|
|
1357
|
+
in_pos,
|
|
1358
|
+
in_len,
|
|
1359
|
+
ref caps,
|
|
1360
|
+
)
|
|
1361
|
+
if next >= 0 {
|
|
1362
|
+
if next != in_pos {
|
|
1363
|
+
var int r = match_counted(
|
|
1364
|
+
pattern,
|
|
1365
|
+
atom_start,
|
|
1366
|
+
atom_end,
|
|
1367
|
+
rest,
|
|
1368
|
+
pat_end,
|
|
1369
|
+
input,
|
|
1370
|
+
next,
|
|
1371
|
+
min_count,
|
|
1372
|
+
max_count,
|
|
1373
|
+
lazy,
|
|
1374
|
+
count + 1,
|
|
1375
|
+
in_len,
|
|
1376
|
+
ref caps,
|
|
1377
|
+
)
|
|
1378
|
+
if r >= 0 {
|
|
1379
|
+
return r
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
if can_stop {
|
|
1385
|
+
return match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
1386
|
+
}
|
|
1387
|
+
return -1
|
|
1388
|
+
}
|
|
1389
|
+
|
|
946
1390
|
// Match the atom [atom_start..atom_end) followed by the rest of the pattern
|
|
947
1391
|
// pattern[rest..pat_end). min_count is 0 for '*' and 1 for '+'; lazy picks
|
|
948
1392
|
// the shortest repetition first instead of the longest. Single-character
|
|
@@ -1046,29 +1490,99 @@ func match_star_group = (
|
|
|
1046
1490
|
) {
|
|
1047
1491
|
if lazy > 0 {
|
|
1048
1492
|
// Lazy: stop repeating first (minimum met), then try one more
|
|
1049
|
-
// iteration before giving up.
|
|
1493
|
+
// iteration before giving up. The iteration retries the group's
|
|
1494
|
+
// top-level alternatives (see match_group_iteration).
|
|
1050
1495
|
if min_count <= 0 {
|
|
1051
1496
|
var int stop = match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
1052
1497
|
if stop >= 0 {
|
|
1053
1498
|
return stop
|
|
1054
1499
|
}
|
|
1055
1500
|
}
|
|
1056
|
-
|
|
1501
|
+
return match_group_iteration(
|
|
1057
1502
|
pattern,
|
|
1058
1503
|
atom_start,
|
|
1059
1504
|
atom_end,
|
|
1505
|
+
rest,
|
|
1506
|
+
pat_end,
|
|
1060
1507
|
input,
|
|
1061
1508
|
in_pos,
|
|
1509
|
+
min_count,
|
|
1510
|
+
lazy,
|
|
1062
1511
|
in_len,
|
|
1063
1512
|
ref caps,
|
|
1064
1513
|
)
|
|
1514
|
+
}
|
|
1515
|
+
// Try matching the group one more time (greedy) — each top-level
|
|
1516
|
+
// alternative in turn — before stopping.
|
|
1517
|
+
var int r = match_group_iteration(
|
|
1518
|
+
pattern,
|
|
1519
|
+
atom_start,
|
|
1520
|
+
atom_end,
|
|
1521
|
+
rest,
|
|
1522
|
+
pat_end,
|
|
1523
|
+
input,
|
|
1524
|
+
in_pos,
|
|
1525
|
+
min_count,
|
|
1526
|
+
lazy,
|
|
1527
|
+
in_len,
|
|
1528
|
+
ref caps,
|
|
1529
|
+
)
|
|
1530
|
+
if r >= 0 {
|
|
1531
|
+
return r
|
|
1532
|
+
}
|
|
1533
|
+
// Stop repeating and try the rest of the pattern, if the minimum is met.
|
|
1534
|
+
if min_count <= 0 {
|
|
1535
|
+
return match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
1536
|
+
}
|
|
1537
|
+
return -1
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Try ONE further iteration of a quantified group at in_pos, retrying the
|
|
1541
|
+
// group's top-level alternatives while the remaining star recursion fails.
|
|
1542
|
+
// Returns the recursion's result, or -1 when no alternative leads to a
|
|
1543
|
+
// match. An alternative that matches the empty string is not recursed into
|
|
1544
|
+
// (an empty iteration cannot make progress; the caller's stop path covers
|
|
1545
|
+
// it), and every failed trial restores the group's previous capture span.
|
|
1546
|
+
func match_group_iteration = (
|
|
1547
|
+
string pattern,
|
|
1548
|
+
int atom_start,
|
|
1549
|
+
int atom_end,
|
|
1550
|
+
int rest,
|
|
1551
|
+
int pat_end,
|
|
1552
|
+
string input,
|
|
1553
|
+
int in_pos,
|
|
1554
|
+
int min_count,
|
|
1555
|
+
int lazy,
|
|
1556
|
+
int in_len,
|
|
1557
|
+
ref List<int> caps,
|
|
1558
|
+
out int
|
|
1559
|
+
) {
|
|
1560
|
+
var int g = group_index_at(pattern, atom_start)
|
|
1561
|
+
var int at = last_capture_index(caps, g)
|
|
1562
|
+
var int old_start = -1
|
|
1563
|
+
var int old_end = -1
|
|
1564
|
+
if at >= 0 {
|
|
1565
|
+
old_start = caps.at(at + 1)
|
|
1566
|
+
old_end = caps.at(at + 2)
|
|
1567
|
+
}
|
|
1568
|
+
var int inner_start = group_inner_start(pattern, atom_start, pat_end)
|
|
1569
|
+
var int inner_end = atom_end - 1
|
|
1570
|
+
var int s = inner_start
|
|
1571
|
+
while s <= inner_end {
|
|
1572
|
+
var int bar = next_top_level_alt(pattern, s, inner_end)
|
|
1573
|
+
var int e = inner_end
|
|
1574
|
+
if bar >= 0 {
|
|
1575
|
+
e = bar
|
|
1576
|
+
}
|
|
1577
|
+
var int next = match_here(pattern, s, e, input, in_pos, in_len, ref caps)
|
|
1065
1578
|
if next >= 0 {
|
|
1579
|
+
record_capture(ref caps, g, in_pos, next)
|
|
1066
1580
|
if next != in_pos {
|
|
1067
1581
|
var new_min = 0
|
|
1068
1582
|
if min_count > 0 {
|
|
1069
1583
|
new_min = min_count - 1
|
|
1070
1584
|
}
|
|
1071
|
-
|
|
1585
|
+
var int r = match_star_group(
|
|
1072
1586
|
pattern,
|
|
1073
1587
|
atom_start,
|
|
1074
1588
|
atom_end,
|
|
@@ -1081,40 +1595,18 @@ func match_star_group = (
|
|
|
1081
1595
|
in_len,
|
|
1082
1596
|
ref caps,
|
|
1083
1597
|
)
|
|
1598
|
+
if r >= 0 {
|
|
1599
|
+
return r
|
|
1600
|
+
}
|
|
1084
1601
|
}
|
|
1085
1602
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
if next != in_pos {
|
|
1092
|
-
var new_min = 0
|
|
1093
|
-
if min_count > 0 {
|
|
1094
|
-
new_min = min_count - 1
|
|
1095
|
-
}
|
|
1096
|
-
var int r = match_star_group(
|
|
1097
|
-
pattern,
|
|
1098
|
-
atom_start,
|
|
1099
|
-
atom_end,
|
|
1100
|
-
rest,
|
|
1101
|
-
pat_end,
|
|
1102
|
-
input,
|
|
1103
|
-
next,
|
|
1104
|
-
new_min,
|
|
1105
|
-
lazy,
|
|
1106
|
-
in_len,
|
|
1107
|
-
ref caps,
|
|
1108
|
-
)
|
|
1109
|
-
if r >= 0 {
|
|
1110
|
-
return r
|
|
1111
|
-
}
|
|
1603
|
+
record_capture(ref caps, g, old_start, old_end)
|
|
1604
|
+
if bar >= 0 {
|
|
1605
|
+
s = bar + 1
|
|
1606
|
+
} else {
|
|
1607
|
+
s = inner_end + 1
|
|
1112
1608
|
}
|
|
1113
1609
|
}
|
|
1114
|
-
// Stop repeating and try the rest of the pattern, if the minimum is met.
|
|
1115
|
-
if min_count <= 0 {
|
|
1116
|
-
return match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
1117
|
-
}
|
|
1118
1610
|
return -1
|
|
1119
1611
|
}
|
|
1120
1612
|
|
|
@@ -1138,6 +1630,21 @@ func match_optional = (
|
|
|
1138
1630
|
if skip >= 0 {
|
|
1139
1631
|
return skip
|
|
1140
1632
|
}
|
|
1633
|
+
// A group atom includes continuation-aware alternative retries (see
|
|
1634
|
+
// match_group_then_rest) in both moods.
|
|
1635
|
+
if pattern.at(atom_start) == '(' {
|
|
1636
|
+
return match_group_then_rest(
|
|
1637
|
+
pattern,
|
|
1638
|
+
atom_start,
|
|
1639
|
+
atom_end,
|
|
1640
|
+
rest,
|
|
1641
|
+
pat_end,
|
|
1642
|
+
input,
|
|
1643
|
+
in_pos,
|
|
1644
|
+
in_len,
|
|
1645
|
+
ref caps,
|
|
1646
|
+
)
|
|
1647
|
+
}
|
|
1141
1648
|
var int next = match_single_atom(
|
|
1142
1649
|
pattern,
|
|
1143
1650
|
atom_start,
|
|
@@ -1152,11 +1659,36 @@ func match_optional = (
|
|
|
1152
1659
|
}
|
|
1153
1660
|
return -1
|
|
1154
1661
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1662
|
+
if pattern.at(atom_start) == '(' {
|
|
1663
|
+
var int grouped = match_group_then_rest(
|
|
1664
|
+
pattern,
|
|
1665
|
+
atom_start,
|
|
1666
|
+
atom_end,
|
|
1667
|
+
rest,
|
|
1668
|
+
pat_end,
|
|
1669
|
+
input,
|
|
1670
|
+
in_pos,
|
|
1671
|
+
in_len,
|
|
1672
|
+
ref caps,
|
|
1673
|
+
)
|
|
1674
|
+
if grouped >= 0 {
|
|
1675
|
+
return grouped
|
|
1676
|
+
}
|
|
1677
|
+
} else {
|
|
1678
|
+
var int next = match_single_atom(
|
|
1679
|
+
pattern,
|
|
1680
|
+
atom_start,
|
|
1681
|
+
atom_end,
|
|
1682
|
+
input,
|
|
1683
|
+
in_pos,
|
|
1684
|
+
in_len,
|
|
1685
|
+
ref caps,
|
|
1686
|
+
)
|
|
1687
|
+
if next >= 0 {
|
|
1688
|
+
var int r = match_here(pattern, rest, pat_end, input, next, in_len, ref caps)
|
|
1689
|
+
if r >= 0 {
|
|
1690
|
+
return r
|
|
1691
|
+
}
|
|
1160
1692
|
}
|
|
1161
1693
|
}
|
|
1162
1694
|
return match_here(pattern, rest, pat_end, input, in_pos, in_len, ref caps)
|
|
@@ -1201,7 +1733,7 @@ func char_in_class = (
|
|
|
1201
1733
|
found = true
|
|
1202
1734
|
}
|
|
1203
1735
|
} else {
|
|
1204
|
-
if ic == e {
|
|
1736
|
+
if (ic as int) == escaped_byte_value(e) {
|
|
1205
1737
|
found = true
|
|
1206
1738
|
}
|
|
1207
1739
|
}
|
|
@@ -1242,9 +1774,70 @@ func char_in_class = (
|
|
|
1242
1774
|
return found
|
|
1243
1775
|
}
|
|
1244
1776
|
|
|
1777
|
+
// Whether the '(' at open_pos opens a non-capturing construct: `(?:` or one
|
|
1778
|
+
// of the lookaround forms `(?=`, `(?!`, `(?<=`, `(?<!`. Every supported
|
|
1779
|
+
// group whose open paren is immediately followed by '?' is non-capturing.
|
|
1780
|
+
func is_non_capturing_open = (string pattern, int open_pos, int pat_end, out bool) {
|
|
1781
|
+
if open_pos + 1 >= pat_end {
|
|
1782
|
+
return false
|
|
1783
|
+
}
|
|
1784
|
+
return pattern.at(open_pos + 1) == '?'
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Classify the group opening at open_pos:
|
|
1788
|
+
// 0 capturing group (...)
|
|
1789
|
+
// 1 non-capturing group (?:...)
|
|
1790
|
+
// 2 positive lookahead (?=...)
|
|
1791
|
+
// 3 negative lookahead (?!...)
|
|
1792
|
+
// 4 positive lookbehind (?<=...)
|
|
1793
|
+
// 5 negative lookbehind (?<!...)
|
|
1794
|
+
func group_kind = (string pattern, int open_pos, int pat_end, out int) {
|
|
1795
|
+
if is_non_capturing_open(pattern, open_pos, pat_end) == false {
|
|
1796
|
+
return 0
|
|
1797
|
+
}
|
|
1798
|
+
if open_pos + 2 >= pat_end {
|
|
1799
|
+
return 0
|
|
1800
|
+
}
|
|
1801
|
+
var char c = pattern.at(open_pos + 2)
|
|
1802
|
+
if c == ':' {
|
|
1803
|
+
return 1
|
|
1804
|
+
}
|
|
1805
|
+
if c == '=' {
|
|
1806
|
+
return 2
|
|
1807
|
+
}
|
|
1808
|
+
if c == '!' {
|
|
1809
|
+
return 3
|
|
1810
|
+
}
|
|
1811
|
+
if c == '<' {
|
|
1812
|
+
if open_pos + 3 < pat_end {
|
|
1813
|
+
if pattern.at(open_pos + 3) == '=' {
|
|
1814
|
+
return 4
|
|
1815
|
+
}
|
|
1816
|
+
if pattern.at(open_pos + 3) == '!' {
|
|
1817
|
+
return 5
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
return 0
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// The position of the first byte INSIDE the group whose '(' opens at
|
|
1825
|
+
// open_pos: past `(?:`, `(?=`, `(?!`, or `(?<=`/`(?<!` for the prefixed
|
|
1826
|
+
// forms, or open_pos + 1 for a plain capturing group.
|
|
1827
|
+
func group_inner_start = (string pattern, int open_pos, int pat_end, out int) {
|
|
1828
|
+
var int kind = group_kind(pattern, open_pos, pat_end)
|
|
1829
|
+
if kind == 1 || kind == 2 || kind == 3 {
|
|
1830
|
+
return open_pos + 3
|
|
1831
|
+
}
|
|
1832
|
+
if kind == 4 || kind == 5 {
|
|
1833
|
+
return open_pos + 4
|
|
1834
|
+
}
|
|
1835
|
+
return open_pos + 1
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1245
1838
|
// The number of capture groups in the whole pattern: every '(' that is not
|
|
1246
|
-
// escaped
|
|
1247
|
-
//
|
|
1839
|
+
// escaped, not inside a character class, and not a non-capturing prefix
|
|
1840
|
+
// (`(?:`, `(?=`, `(?!`, `(?<=`, `(?<!`).
|
|
1248
1841
|
func capture_count = (string pattern, out int) {
|
|
1249
1842
|
var int n = pattern.length
|
|
1250
1843
|
var count = 0
|
|
@@ -1258,7 +1851,9 @@ func capture_count = (string pattern, out int) {
|
|
|
1258
1851
|
i = atom_end_pos(pattern, i, n) - 1
|
|
1259
1852
|
} else {
|
|
1260
1853
|
if c == '(' {
|
|
1261
|
-
|
|
1854
|
+
if is_non_capturing_open(pattern, i, n) == false {
|
|
1855
|
+
count += 1
|
|
1856
|
+
}
|
|
1262
1857
|
}
|
|
1263
1858
|
}
|
|
1264
1859
|
}
|
|
@@ -1267,8 +1862,9 @@ func capture_count = (string pattern, out int) {
|
|
|
1267
1862
|
}
|
|
1268
1863
|
|
|
1269
1864
|
// The capture-group index (1-based) of the group whose '(' opens at
|
|
1270
|
-
// open_pos: one more than the number of
|
|
1271
|
-
// pattern (the whole match is group 0).
|
|
1865
|
+
// open_pos: one more than the number of capturing opens before it in the
|
|
1866
|
+
// pattern (the whole match is group 0). Returns 0 for a non-capturing
|
|
1867
|
+
// construct so callers record no span.
|
|
1272
1868
|
func group_index_at = (string pattern, int open_pos, out int) {
|
|
1273
1869
|
var count = 0
|
|
1274
1870
|
var i = 0
|
|
@@ -1281,11 +1877,16 @@ func group_index_at = (string pattern, int open_pos, out int) {
|
|
|
1281
1877
|
i = atom_end_pos(pattern, i, open_pos) - 1
|
|
1282
1878
|
} else {
|
|
1283
1879
|
if c == '(' {
|
|
1284
|
-
|
|
1880
|
+
if is_non_capturing_open(pattern, i, open_pos) == false {
|
|
1881
|
+
count += 1
|
|
1882
|
+
}
|
|
1285
1883
|
}
|
|
1286
1884
|
}
|
|
1287
1885
|
}
|
|
1288
1886
|
}
|
|
1887
|
+
if is_non_capturing_open(pattern, open_pos, pattern.length) {
|
|
1888
|
+
return 0
|
|
1889
|
+
}
|
|
1289
1890
|
return count + 1
|
|
1290
1891
|
}
|
|
1291
1892
|
|
|
@@ -1294,6 +1895,9 @@ func group_index_at = (string pattern, int open_pos, out int) {
|
|
|
1294
1895
|
// a repeated group reports its last iteration; a failed group records
|
|
1295
1896
|
// (-1, -1) so stale spans from an abandoned attempt never surface.
|
|
1296
1897
|
func record_capture = (ref List<int> caps, int group, int start, int end) {
|
|
1898
|
+
if group <= 0 {
|
|
1899
|
+
return
|
|
1900
|
+
}
|
|
1297
1901
|
var int at = last_capture_index(caps, group)
|
|
1298
1902
|
if at >= 0 {
|
|
1299
1903
|
caps.set(at + 1, start)
|
|
@@ -1456,6 +2060,29 @@ func is_shorthand = (char c, out bool) {
|
|
|
1456
2060
|
return code == 100 || code == 68 || code == 119 || code == 87 || code == 115 || code == 83
|
|
1457
2061
|
}
|
|
1458
2062
|
|
|
2063
|
+
// Whether the escaped character c names a control-character shorthand
|
|
2064
|
+
// (\n \r \t) that the engine maps to its real control byte.
|
|
2065
|
+
func is_control_escape = (char c, out bool) {
|
|
2066
|
+
var int code = c as int
|
|
2067
|
+
return code == 110 || code == 114 || code == 116
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// The byte an escaped atom names: \n is LF (10), \r is CR (13), \t is TAB
|
|
2071
|
+
// (9); every other escaped character is the literal byte of that character.
|
|
2072
|
+
func escaped_byte_value = (char c, out int) {
|
|
2073
|
+
var int code = c as int
|
|
2074
|
+
if code == 110 {
|
|
2075
|
+
return 10
|
|
2076
|
+
}
|
|
2077
|
+
if code == 114 {
|
|
2078
|
+
return 13
|
|
2079
|
+
}
|
|
2080
|
+
if code == 116 {
|
|
2081
|
+
return 9
|
|
2082
|
+
}
|
|
2083
|
+
return code
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1459
2086
|
// Whether byte b belongs to the shorthand class named by c. Uppercase names
|
|
1460
2087
|
// are the complements (S/D/W match anything the lowercase set excludes).
|
|
1461
2088
|
func matches_shorthand = (char c, char b, out bool) {
|