nomen-lang 0.6.2 → 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/Text/Regex.nm +416 -15
- package/core/docs/System/Text/Regex.md +1 -1
- package/dist/index.mjs +905 -304
- package/package.json +1 -1
- package/src/index.ts +8 -56
- package/src/init.ts +12 -3
- package/src/test.ts +34 -2
|
@@ -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.
|
|
@@ -530,9 +532,17 @@ func alt_first_bytes = (string pattern, int start, int end, ref StringBuilder sb
|
|
|
530
532
|
}
|
|
531
533
|
if c == '(' {
|
|
532
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
|
+
}
|
|
533
542
|
// The group's first bytes are the union of its content's
|
|
534
543
|
// top-level alternatives' first bytes.
|
|
535
|
-
|
|
544
|
+
var int inner = group_inner_start(pattern, i, end)
|
|
545
|
+
if collect_first_bytes(pattern, inner, a_end - 1, ref sb) < 0 {
|
|
536
546
|
return -1
|
|
537
547
|
}
|
|
538
548
|
var int cont = nullable_quant_after(pattern, a_end, end)
|
|
@@ -587,13 +597,128 @@ func nullable_quant_after = (string pattern, int after, int end, out int) {
|
|
|
587
597
|
}
|
|
588
598
|
return j
|
|
589
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
|
+
}
|
|
590
618
|
return -1
|
|
591
619
|
}
|
|
592
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
|
+
|
|
593
714
|
// Append the members of character class [start..end) to sb. Ranges (a-z) are
|
|
594
715
|
// expanded; negation (^) is ignored (we want all candidates).
|
|
595
|
-
func add_class_chars = (
|
|
596
|
-
|
|
716
|
+
func add_class_chars = (
|
|
717
|
+
string pattern,
|
|
718
|
+
int start,
|
|
719
|
+
int end,
|
|
720
|
+
ref StringBuilder sb
|
|
721
|
+
) { var int cs = start
|
|
597
722
|
if cs < end {
|
|
598
723
|
if pattern.at(cs) == '^' {
|
|
599
724
|
cs += 1
|
|
@@ -714,6 +839,28 @@ func match_here = (
|
|
|
714
839
|
return match_here(pattern, pat_pos + 1, pat_end, input, in_pos, in_len, ref caps)
|
|
715
840
|
}
|
|
716
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
|
+
|
|
717
864
|
// Determine the bounds of the current atom.
|
|
718
865
|
var int a_end = atom_end_pos(pattern, pat_pos, pat_end)
|
|
719
866
|
|
|
@@ -774,6 +921,37 @@ func match_here = (
|
|
|
774
921
|
ref caps,
|
|
775
922
|
)
|
|
776
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
|
+
}
|
|
777
955
|
}
|
|
778
956
|
|
|
779
957
|
// No quantifier: match the atom once, then the rest of the pattern. A
|
|
@@ -864,7 +1042,7 @@ func match_group_then_rest = (
|
|
|
864
1042
|
old_start = caps.at(at + 1)
|
|
865
1043
|
old_end = caps.at(at + 2)
|
|
866
1044
|
}
|
|
867
|
-
var int inner_start = atom_start
|
|
1045
|
+
var int inner_start = group_inner_start(pattern, atom_start, pat_end)
|
|
868
1046
|
var int inner_end = atom_end - 1
|
|
869
1047
|
var int s = inner_start
|
|
870
1048
|
while s <= inner_end {
|
|
@@ -980,7 +1158,7 @@ func match_single_atom = (
|
|
|
980
1158
|
}
|
|
981
1159
|
var int next = match_here(
|
|
982
1160
|
pattern,
|
|
983
|
-
atom_start
|
|
1161
|
+
group_inner_start(pattern, atom_start, atom_end),
|
|
984
1162
|
atom_end - 1,
|
|
985
1163
|
input,
|
|
986
1164
|
in_pos,
|
|
@@ -1066,6 +1244,149 @@ func char_matches_atom = (
|
|
|
1066
1244
|
return pc == ic
|
|
1067
1245
|
}
|
|
1068
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
|
+
|
|
1069
1390
|
// Match the atom [atom_start..atom_end) followed by the rest of the pattern
|
|
1070
1391
|
// pattern[rest..pat_end). min_count is 0 for '*' and 1 for '+'; lazy picks
|
|
1071
1392
|
// the shortest repetition first instead of the longest. Single-character
|
|
@@ -1244,7 +1565,7 @@ func match_group_iteration = (
|
|
|
1244
1565
|
old_start = caps.at(at + 1)
|
|
1245
1566
|
old_end = caps.at(at + 2)
|
|
1246
1567
|
}
|
|
1247
|
-
var int inner_start = atom_start
|
|
1568
|
+
var int inner_start = group_inner_start(pattern, atom_start, pat_end)
|
|
1248
1569
|
var int inner_end = atom_end - 1
|
|
1249
1570
|
var int s = inner_start
|
|
1250
1571
|
while s <= inner_end {
|
|
@@ -1354,7 +1675,15 @@ func match_optional = (
|
|
|
1354
1675
|
return grouped
|
|
1355
1676
|
}
|
|
1356
1677
|
} else {
|
|
1357
|
-
var int next = match_single_atom(
|
|
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
|
+
)
|
|
1358
1687
|
if next >= 0 {
|
|
1359
1688
|
var int r = match_here(pattern, rest, pat_end, input, next, in_len, ref caps)
|
|
1360
1689
|
if r >= 0 {
|
|
@@ -1445,9 +1774,70 @@ func char_in_class = (
|
|
|
1445
1774
|
return found
|
|
1446
1775
|
}
|
|
1447
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
|
+
|
|
1448
1838
|
// The number of capture groups in the whole pattern: every '(' that is not
|
|
1449
|
-
// escaped
|
|
1450
|
-
//
|
|
1839
|
+
// escaped, not inside a character class, and not a non-capturing prefix
|
|
1840
|
+
// (`(?:`, `(?=`, `(?!`, `(?<=`, `(?<!`).
|
|
1451
1841
|
func capture_count = (string pattern, out int) {
|
|
1452
1842
|
var int n = pattern.length
|
|
1453
1843
|
var count = 0
|
|
@@ -1461,7 +1851,9 @@ func capture_count = (string pattern, out int) {
|
|
|
1461
1851
|
i = atom_end_pos(pattern, i, n) - 1
|
|
1462
1852
|
} else {
|
|
1463
1853
|
if c == '(' {
|
|
1464
|
-
|
|
1854
|
+
if is_non_capturing_open(pattern, i, n) == false {
|
|
1855
|
+
count += 1
|
|
1856
|
+
}
|
|
1465
1857
|
}
|
|
1466
1858
|
}
|
|
1467
1859
|
}
|
|
@@ -1470,8 +1862,9 @@ func capture_count = (string pattern, out int) {
|
|
|
1470
1862
|
}
|
|
1471
1863
|
|
|
1472
1864
|
// The capture-group index (1-based) of the group whose '(' opens at
|
|
1473
|
-
// open_pos: one more than the number of
|
|
1474
|
-
// 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.
|
|
1475
1868
|
func group_index_at = (string pattern, int open_pos, out int) {
|
|
1476
1869
|
var count = 0
|
|
1477
1870
|
var i = 0
|
|
@@ -1484,11 +1877,16 @@ func group_index_at = (string pattern, int open_pos, out int) {
|
|
|
1484
1877
|
i = atom_end_pos(pattern, i, open_pos) - 1
|
|
1485
1878
|
} else {
|
|
1486
1879
|
if c == '(' {
|
|
1487
|
-
|
|
1880
|
+
if is_non_capturing_open(pattern, i, open_pos) == false {
|
|
1881
|
+
count += 1
|
|
1882
|
+
}
|
|
1488
1883
|
}
|
|
1489
1884
|
}
|
|
1490
1885
|
}
|
|
1491
1886
|
}
|
|
1887
|
+
if is_non_capturing_open(pattern, open_pos, pattern.length) {
|
|
1888
|
+
return 0
|
|
1889
|
+
}
|
|
1492
1890
|
return count + 1
|
|
1493
1891
|
}
|
|
1494
1892
|
|
|
@@ -1497,6 +1895,9 @@ func group_index_at = (string pattern, int open_pos, out int) {
|
|
|
1497
1895
|
// a repeated group reports its last iteration; a failed group records
|
|
1498
1896
|
// (-1, -1) so stale spans from an abandoned attempt never surface.
|
|
1499
1897
|
func record_capture = (ref List<int> caps, int group, int start, int end) {
|
|
1898
|
+
if group <= 0 {
|
|
1899
|
+
return
|
|
1900
|
+
}
|
|
1500
1901
|
var int at = last_capture_index(caps, group)
|
|
1501
1902
|
if at >= 0 {
|
|
1502
1903
|
caps.set(at + 1, start)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## `struct Regex`
|
|
4
4
|
|
|
5
|
-
Regular-expression helpers (`test`/`match`/`count`/`replace_all`) using POSIX ERE syntax
|
|
5
|
+
Regular-expression helpers (`test`/`match`/`count`/`replace_all`) using POSIX ERE syntax plus `(?:...)`, lookaround, and `{n,m}` counted repetition
|
|
6
6
|
|
|
7
7
|
**Members:**
|
|
8
8
|
|