1 ## oils_failures_allowed: 0
2
3 #### s ~ regex and s !~ regex
4 shopt -s ysh:upgrade
5
6 var s = 'foo'
7 if (s ~ '.([[:alpha:]]+)') { # ERE syntax
8 echo matches
9 argv.py $[_group(0)] $[_group(1)]
10 }
11 if (s !~ '[[:digit:]]+') {
12 echo "does not match"
13 argv.py $[_group(0)] $[_group(1)]
14 }
15
16 if (s ~ '[[:digit:]]+') {
17 echo "matches"
18 }
19 # Should be cleared now
20 # should this be Undef rather than ''?
21 try {
22 var x = _group(0)
23 }
24 if (_status === 3) {
25 echo 'got expected status 3'
26 }
27
28 try {
29 var y = _group(1)
30 }
31 if (_status === 3) {
32 echo 'got expected status 3'
33 }
34
35 ## STDOUT:
36 matches
37 ['foo', 'oo']
38 does not match
39 ['foo', 'oo']
40 got expected status 3
41 got expected status 3
42 ## END
43
44 #### Invalid regex has libc error message
45
46 shopt -s ysh:upgrade
47
48 # Hm it's hard to test this, we can't get stderr of YSH from within YSH?
49 #redir 2>err.txt {
50 # if ('abc' ~ '+') {
51 # echo 'bad'
52 # }
53 #}
54
55 if ('abc' ~ '+') {
56 echo 'bad'
57 }
58
59 ## status: 2
60 ## STDOUT:
61 ## END
62
63 #### Eggex flags to ignore case are respected
64 shopt -s ysh:upgrade
65
66 # based on Python's spelling
67 var pat = / 'abc' ; i /
68 var pat2 = / @pat 'def' ; reg_icase / # this is allowed
69
70 if ('-abcdef-' ~ pat2) {
71 echo 'yes'
72 }
73
74 if ('-ABCDEF-' ~ pat2) {
75 echo 'yes'
76 }
77
78 if ('ABCDE' ~ pat2) {
79 echo 'BUG'
80 }
81
82 ## STDOUT:
83 yes
84 yes
85 ## END
86
87 #### Eggex flags to treat newlines as special are respected
88 shopt -s ysh:upgrade
89
90 if (u'abc123\n' ~ / digit %end /) {
91 echo 'BUG'
92 }
93 if (u'abc\n123' ~ / %start digit /) {
94 echo 'BUG'
95 }
96
97 if (u'abc123\n' ~ / digit %end ; reg_newline /) {
98 echo 'yes'
99 }
100 if (u'abc\n123' ~ / %start digit ; reg_newline /) {
101 echo 'yes'
102 }
103
104 if (u'\n' ~ / . /) {
105 echo 'yes'
106 }
107 if (u'\n' ~ / !digit /) {
108 echo 'yes'
109 }
110
111 if (u'\n' ~ / . ; reg_newline /) {
112 echo 'BUG'
113 }
114 if (u'\n' ~ / !digit ; reg_newline /) {
115 echo 'BUG'
116 }
117
118 ## STDOUT:
119 yes
120 yes
121 yes
122 yes
123 ## END
124
125 #### leftMatch() combined with REG_NEWLINE - interaction with ^
126 shopt --set ysh:upgrade
127
128 # REG_NEWLINE
129 # . does NOT match newline
130 # [^a] does NOT match newline
131 # ^ matches the empty string after a newline (regardless of REG_NOTBOL)
132 # $ matches the empty string before a newline (regardless of REG_NOTBOL)
133 #
134 # note: REG_NOTBOL is not used in leftMatch()
135
136 var lines = '''
137 one
138 2
139 three
140 '''
141
142 if (0) {
143 = lines
144 = ${p}
145 echo ---
146 }
147
148 proc show-matches(; pattern) {
149 var pos = 0
150 while (true) {
151 var m = lines.leftMatch(pattern, pos=pos)
152 #var m = lines.search(pattern, pos=pos)
153
154 if (m is null) {
155 return
156 }
157
158 #pp test_ ([m.group(0), m.group(1), m.group(2)])
159 pp test_ ([pos, m.group(0)])
160
161 setvar pos = m.end(0)
162 }
163 }
164
165 var p1 = / <capture [a-z] as letter> /
166 var p2 = / <capture [a-z] as letter> ; ; reg_newline /
167
168 show-matches (p1)
169 echo
170 show-matches (p2)
171
172 ## STDOUT:
173 (List) [0,"o"]
174 (List) [1,"n"]
175 (List) [2,"e"]
176
177 (List) [0,"o"]
178 (List) [1,"n"]
179 (List) [2,"e"]
180 ## END
181
182 #### Bug fix: leftMatch() can be used with a|b (ERE precedence is respected)
183 shopt --set ysh:upgrade
184
185 var lines = '''
186 one
187 2
188 three
189 '''
190
191 proc show-matches(; pattern) {
192 var pos = 0
193 while (true) {
194 var m = lines.leftMatch(pattern, pos=pos)
195 #var m = lines.search(pattern, pos=pos)
196
197 if (m is null) {
198 return
199 }
200
201 #pp test_ ([m.group(0), m.group(1), m.group(2)])
202 pp test_ ([pos, m.group(0)])
203
204 setvar pos = m.end(0)
205 }
206 }
207
208 # Alternation requires special logic
209 var p3 = / <capture [0-9] as number> | <capture [a-z] as letter> /
210 var p4 = / <capture [0-9] as number> | <capture [a-z] as letter> ; ; reg_newline /
211
212 var m = lines.leftMatch(p3, pos=3)
213 assert [m is null]
214
215 var m = lines.leftMatch(p3, pos=4)
216 pp test_ ([m.group(0), m.group(1), m.group(2)])
217
218 var m = lines.leftMatch(p3, pos=5)
219 assert [m is null]
220
221 var m = lines.leftMatch(p3, pos=6)
222 pp test_ ([m.group(0), m.group(1), m.group(2)])
223
224 echo
225 show-matches (p3)
226
227 echo
228 show-matches (p4)
229
230 ## STDOUT:
231 (List) ["2","2",null]
232 (List) ["t",null,"t"]
233
234 (List) [0,"o"]
235 (List) [1,"n"]
236 (List) [2,"e"]
237
238 (List) [0,"o"]
239 (List) [1,"n"]
240 (List) [2,"e"]
241 ## END
242
243 #### Positional captures with _group
244 shopt -s ysh:upgrade
245
246 var x = 'zz 2020-08-20'
247
248 if [[ $x =~ ([[:digit:]]+)-([[:digit:]]+) ]] {
249 argv.py "${BASH_REMATCH[@]}"
250 }
251
252 # THIS IS A NO-OP. The variable is SHADOWED by the special name.
253 # I think that's OK.
254 setvar BASH_REMATCH = :| reset |
255
256 if (x ~ /<capture d+> '-' <capture d+>/) {
257 argv.py "${BASH_REMATCH[@]}"
258 argv.py $[_group(0)] $[_group(1)] $[_group(2)]
259
260 # TODO: Also test _start() and _end()
261 }
262 ## STDOUT:
263 ['2020-08', '2020', '08']
264 ['2020-08', '2020', '08']
265 ['2020-08', '2020', '08']
266 ## END
267
268 #### _group() returns null when group doesn't match
269 shopt -s ysh:upgrade
270
271 var pat = / <capture 'a'> | <capture 'b'> /
272 if ('b' ~ pat) {
273 echo "$[_group(1)] $[_group(2)]"
274 }
275 ## STDOUT:
276 null b
277 ## END
278
279 #### _start() and _end()
280 shopt -s ysh:upgrade
281
282 var s = 'foo123bar'
283 if (s ~ /digit+/) {
284 echo start=$[_start(0)] end=$[_end(0)]
285 }
286 echo ---
287
288 if (s ~ / <capture [a-z]+> <capture digit+> /) {
289 echo start=$[_start(1)] end=$[_end(1)]
290 echo start=$[_start(2)] end=$[_end(2)]
291 }
292 echo ---
293
294 if (s ~ / <capture [a-z]+> | <capture digit+> /) {
295 echo start=$[_start(1)] end=$[_end(1)]
296 echo start=$[_start(2)] end=$[_end(2)]
297 }
298
299 ## STDOUT:
300 start=3 end=6
301 ---
302 start=0 end=3
303 start=3 end=6
304 ---
305 start=0 end=3
306 start=-1 end=-1
307 ## END
308
309 #### Str->search() method returns value.Match object
310
311 var s = '= Hi5- Bye6-'
312
313 var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /)
314 echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
315 echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
316 echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
317
318 echo ---
319
320 var pos = m.end(0) # search from end position
321 var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /, pos=pos)
322 echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
323 echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
324 echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
325
326 ## STDOUT:
327 g0 2 6 Hi5-
328 g1 2 4 Hi
329 g2 4 5 5
330 ---
331 g0 7 12 Bye6-
332 g1 7 10 Bye
333 g2 10 11 6
334 ## END
335
336 #### Str->search() only matches %start ^ when pos == 0
337
338 shopt -s ysh:upgrade
339
340 var anchored = / %start <capture d+> '-' /
341 var free = / <capture d+> '-' /
342
343 var s = '12-34-'
344
345 for pat in ([anchored, free]) {
346 echo "pat=$pat"
347
348 var pos = 0
349 while (true) {
350 var m = s.search(pat, pos=pos)
351 if (not m) {
352 break
353 }
354 echo $[m.group(0)]
355 setvar pos = m.end(0)
356 }
357
358 }
359
360 ## STDOUT:
361 pat=^([[:digit:]]+)-
362 12-
363 pat=([[:digit:]]+)-
364 12-
365 34-
366 ## END
367
368
369 #### search() and leftMatch() accept ERE string
370
371 var s = '= hi5- bye6-'
372
373 var m = s.search('([[:alpha:]]+)([[:digit:]]+)-')
374 echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
375 echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
376 echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
377 echo ---
378
379 var m = s[2:].leftMatch('([[:alpha:]]+)([[:digit:]]+)-')
380 echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
381 echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
382 echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
383
384 ## STDOUT:
385 g0 2 6 hi5-
386 g1 2 4 hi
387 g2 4 5 5
388 ---
389 g0 0 4 hi5-
390 g1 0 2 hi
391 g2 2 3 5
392 ## END
393
394 #### Str.leftMatch() can implement lexer pattern
395
396 shopt -s ysh:upgrade
397
398 var lexer = / <capture d+> | <capture [a-z]+> | <capture s+> /
399 #echo $lexer
400
401 proc show-tokens (s) {
402 var pos = 0
403
404 while (true) {
405 echo "pos=$pos"
406
407 var m = s.leftMatch(lexer, pos=pos)
408 if (not m) {
409 break
410 }
411 # TODO: add groups()
412 #var groups = [m.group(1), m.group(2), m.group(3)]
413 echo "$[m.group(1)]/$[m.group(2)]/$[m.group(3)]/"
414
415 echo
416
417 setvar pos = m.end(0)
418 }
419 }
420
421 show-tokens 'ab 12'
422
423 echo '==='
424
425 # There's a token here that doesn't leftMatch()
426 show-tokens 'ab+12'
427
428 ## STDOUT:
429 pos=0
430 null/ab/null/
431
432 pos=2
433 null/null/ /
434
435 pos=3
436 12/null/null/
437
438 pos=5
439 ===
440 pos=0
441 null/ab/null/
442
443 pos=2
444 ## END
445
446 #### Named captures with m.group()
447 shopt -s ysh:all
448
449 var s = 'zz 2020-08-20'
450 var pat = /<capture d+ as year> '-' <capture d+ as month>/
451
452 var m = s.search(pat)
453 argv.py $[m.group('year')] $[m.group('month')]
454 echo $[m.start('year')] $[m.end('year')]
455 echo $[m.start('month')] $[m.end('month')]
456
457 argv.py $[m.group('oops')]
458 echo 'error'
459
460 ## status: 3
461 ## STDOUT:
462 ['2020', '08']
463 3 7
464 8 10
465 ## END
466
467 #### Named captures with _group() _start() _end()
468 shopt -s ysh:all
469
470 var x = 'zz 2020-08-20'
471
472 if (x ~ /<capture d+ as year> '-' <capture d+ as month>/) {
473 argv.py $[_group('year')] $[_group('month')]
474 echo $[_start('year')] $[_end('year')]
475 echo $[_start('month')] $[_end('month')]
476 }
477
478 argv.py $[_group('oops')]
479
480 ## status: 3
481 ## STDOUT:
482 ['2020', '08']
483 3 7
484 8 10
485 ## END
486
487 #### Named Capture Decays Without Name
488 shopt -s ysh:all
489 var pat = /<capture d+ as month>/
490 echo $pat
491
492 if ('123' ~ pat) {
493 echo yes
494 }
495
496 ## STDOUT:
497 ([[:digit:]]+)
498 yes
499 ## END
500
501 #### Nested Named Capture Uses ( ordering
502
503 shopt -s ysh:upgrade
504
505 var Date = /<capture d+ as year> '-' <capture d+ as month>/
506 var Time = /<capture d+ as hour> ':' <capture d+ as minute> (':' <capture d+ as secs>)? /
507
508 var pat = / 'when: ' (<capture Date> | <capture Time as two>) /
509 #echo $pat
510
511 proc show-groups (; m) {
512 echo 0 $[m.group(0)]
513 echo 1 $[m.group(1)] # this is everything except when
514 echo 2 $[m.group(2)]
515 echo
516 echo $[m.group('two')]
517 echo $[m.group('year')] $[m.group('month')]
518 echo $[m.group('hour')] $[m.group('minute')] $[m.group('secs')]
519 }
520
521 var m = 'when: 2023-10'.leftMatch(pat)
522
523 show-groups (m)
524
525 var m = 'when: 23:30'.leftMatch(pat)
526
527 echo ---
528 show-groups (m)
529
530 var m = 'when: 23:30:59'.leftMatch(pat)
531
532 echo ---
533 show-groups (m)
534
535 ## STDOUT:
536 0 when: 2023-10
537 1 2023-10
538 2 2023-10
539
540 null
541 2023 10
542 null null null
543 ---
544 0 when: 23:30
545 1 23:30
546 2 null
547
548 23:30
549 null null
550 23 30 null
551 ---
552 0 when: 23:30:59
553 1 23:30:59
554 2 null
555
556 23:30:59
557 null null
558 23 30 59
559 ## END
560
561 #### Capture with Type Conversion Func
562 shopt -s ysh:upgrade
563
564 var s = 'hi 42-3.14'
565 var pat = / <capture d+: int> '-' <capture d+ '.' d+ : float> /
566
567 if (s ~ pat) {
568 var g1 = _group(1) # Int
569 var g2 = _group(2) # Float
570 echo $[type(g1)] $[type(g2)]
571 }
572
573 var m = s.search(pat)
574 if (m) {
575 echo $[m.group(1) => type()] $[m.group(2) => type()]
576 }
577
578 ## STDOUT:
579 Int Float
580 Int Float
581 ## END
582
583
584 #### Named Capture with Type Conversion Func
585 shopt -s ysh:upgrade
586
587 func floatNegate(x) {
588 return (-float(x))
589 }
590
591 var s = 'hi 42-3.14'
592 var pat = / <capture d+ as left: int> '-' <capture d+ '.' d+ as right: floatNegate> /
593
594 if (s ~ pat) {
595 var g1 = _group('left') # Int
596 var g2 = _group('right') # Float
597 echo $g2
598 echo $[type(g1)] $[type(g2)]
599 }
600
601 var m = s.search(pat)
602 if (m) {
603 echo $[m.group('right')]
604 echo $[m.group('left') => type()] $[m.group('right') => type()]
605 }
606
607 ## STDOUT:
608 -3.14
609 Int Float
610 -3.14
611 Int Float
612 ## END
613
614 #### Can't splice eggex with different flags
615 shopt -s ysh:upgrade
616
617 var pat = / 'abc' ; i /
618 var pat2 = / @pat 'def' ; reg_icase / # this is allowed
619
620 var pat3 = / @pat 'def' /
621 = pat3
622
623 ## status: 1
624 ## STDOUT:
625 ## END
626
627 #### Eggex with translation preference has arbitrary flags
628 shopt -s ysh:upgrade
629
630 # TODO: can provide introspection so users can translate it?
631 # This is kind of a speculative corner of the language.
632
633 var pat = / d+ ; ignorecase ; PCRE /
634
635 # This uses ERE, as a test
636 if ('ab 12' ~ pat) {
637 echo yes
638 }
639
640 ## STDOUT:
641 yes
642 ## END
643
644
645 #### Invalid sh operation on eggex
646 var pat = / d+ /
647 #pat[invalid]=1
648 pat[invalid]+=1
649 ## status: 1
650 ## stdout-json: ""
651
652 #### Long Python Example
653
654 # https://docs.python.org/3/reference/lexical_analysis.html#integer-literals
655
656 # integer ::= decinteger | bininteger | octinteger | hexinteger
657 # decinteger ::= nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
658 # bininteger ::= "0" ("b" | "B") (["_"] bindigit)+
659 # octinteger ::= "0" ("o" | "O") (["_"] octdigit)+
660 # hexinteger ::= "0" ("x" | "X") (["_"] hexdigit)+
661 # nonzerodigit ::= "1"..."9"
662 # digit ::= "0"..."9"
663 # bindigit ::= "0" | "1"
664 # octdigit ::= "0"..."7"
665 # hexdigit ::= digit | "a"..."f" | "A"..."F"
666
667 shopt -s ysh:all
668
669 const DecDigit = / [0-9] /
670 const BinDigit = / [0-1] /
671 const OctDigit = / [0-7] /
672 const HexDigit = / [0-9 a-f A-F] / # note: not splicing Digit into character class
673
674 const DecInt = / [1-9] ('_'? DecDigit)* | '0'+ ('_'? '0')* /
675 const BinInt = / '0' [b B] ('_'? BinDigit)+ /
676 const OctInt = / '0' [o O] ('_'? OctDigit)+ /
677 const HexInt = / '0' [x X] ('_'? HexDigit)+ /
678
679 const Integer = / %start (DecInt | BinInt | OctInt | HexInt) %end /
680
681 #echo $Integer
682
683 if ( '123' ~ Integer) { echo 'Y' }
684 if ( 'zzz' !~ Integer) { echo 'N' }
685
686 if ('123_000' ~ Integer) { echo 'Y decimal' }
687 if ('000_123' !~ Integer) { echo 'N decimal' }
688
689 if ( '0b100' ~ Integer) { echo 'Y binary' }
690 if ( '0b102' !~ Integer) { echo 'N binary' }
691
692 if ( '0o755' ~ Integer) { echo 'Y octal' }
693 if ( '0o778' !~ Integer) { echo 'N octal' }
694
695 if ( '0xFF' ~ Integer) { echo 'Y hex' }
696 if ( '0xFG' !~ Integer) { echo 'N hex' }
697
698 ## STDOUT:
699 Y
700 N
701 Y decimal
702 N decimal
703 Y binary
704 N binary
705 Y octal
706 N octal
707 Y hex
708 N hex
709 ## END
710
711 #### Regex in a loop (bug regression)
712
713 shopt --set ysh:all
714
715 var content = [ 1, 2 ]
716 var i = 0
717 while (i < len(content)) {
718 var line = content[i]
719 write $[content[i]]
720 if (str(line) ~ / s* 'imports' s* '=' s* .* /) {
721 exit
722 }
723 setvar i += 1
724 }
725
726 ## STDOUT:
727 1
728 2
729 ## END
730
731
732 #### Regex in a loop depending on var
733
734 shopt --set ysh:all
735
736 var lines = ['foo', 'bar']
737 for line in (lines) {
738 write "line $line"
739
740 # = / $line /
741
742 if ("x$line" ~ / dot @line /) {
743 #if (line ~ / $line /) {
744 write "matched $line"
745 }
746 }
747
748 ## STDOUT:
749 line foo
750 matched foo
751 line bar
752 matched bar
753 ## END
754
755
756 #### Regex with [ (bug regression)
757 shopt --set ysh:all
758
759 if ('[' ~ / '[' /) {
760 echo 'sq'
761 }
762
763 if ('[' ~ / [ '[' ] /) {
764 echo 'char class'
765 }
766
767 # User-reported string
768 if ("a" ~ / s* 'imports' s* '=' s* '[' /) {
769 echo "yes"
770 }
771
772 ## STDOUT:
773 sq
774 char class
775 ## END
776
777 #### Str.replace(Str, Str)
778 shopt --set ysh:all
779
780 var mystr = 'abca'
781 write $[mystr.replace('a', 'A')] # Two matches
782 write $[mystr.replace('b', 'B')] # One match
783 write $[mystr.replace('x', 'y')] # No matches
784
785 write $[mystr.replace('abc', '')] # Empty substitution
786 write $[mystr.replace('', 'new')] # Empty substring
787 ## STDOUT:
788 AbcA
789 aBca
790 abca
791 a
792 newanewbnewcnewanew
793 ## END
794
795 #### Str.replace(Eggex, Str)
796 shopt --set ysh:all
797
798 var mystr = 'mangled----kebab--case'
799 write $[mystr.replace(/ '-'+ /, '-')]
800
801 setvar mystr = 'smaller-to-bigger'
802 write $[mystr.replace(/ '-'+ /, '---')]
803 ## STDOUT:
804 mangled-kebab-case
805 smaller---to---bigger
806 ## END
807
808 #### Str.replace(Eggex, Expr)
809 shopt --set ysh:all
810
811 var mystr = 'name: Bob'
812 write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1")]
813 write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1 (extracted from '$0')")]
814 ## STDOUT:
815 Hello Bob
816 Hello Bob (extracted from 'name: Bob')
817 ## END
818
819 #### Str.replace(*, Expr), $0
820 shopt --set ysh:all
821
822 # Functionality
823 var mystr = 'class Foo: # this class is called Foo'
824 write $[mystr.replace("Foo", ^"$0Bar")]
825 write $[mystr.replace(/ 'Foo' /, ^"$0Bar")]
826
827 # Edge-cases
828 var dollar0 = "$0"
829 #echo dollar0=$dollar0
830 #echo "0 = $0"
831
832 var expected = "f($dollar0)($dollar0)"
833 #echo "expected = $expected"
834
835 # Eager replacement
836 assert [expected === "foo".replace("o", "($0)")]
837
838 assert ['f(o)(o)' === "foo".replace("o", ^"($0)")]
839
840 func f() { return ( "<$0>" ) }
841 assert ["<$dollar0>" === f()]
842
843 assert ['f<o><o>' === "foo".replace("o", ^[f()])]
844
845 ## STDOUT:
846 class FooBar: # this class is called FooBar
847 class FooBar: # this class is called FooBar
848 ## END
849
850 #### Str.replace(Eggex, Expr), scopes
851 shopt --set ysh:all
852
853 var mystr = '123'
854
855 var anotherVar = 'surprise!'
856 write $[mystr.replace(/ <capture d+> /, ^"Hello $1 ($anotherVar)")]
857
858 var globalName = '456'
859 write $[mystr.replace(/ <capture d+ as globalName> /, ^"Hello $globalName")]
860
861 write $[mystr.replace(/ <capture d+ as localName> /, ^"Hello $localName, $globalName")]
862 ## STDOUT:
863 Hello 123 (surprise!)
864 Hello 123
865 Hello 123, 456
866 ## END
867
868 #### Str.replace(Eggex, *, count)
869 shopt --set ysh:all
870
871 var mystr = '1abc2abc3abc'
872
873 for count in (-2..<4) {
874 write $[mystr.replace('abc', "-", count=count)]
875 write $[mystr.replace('abc', ^"-", count=count)]
876 write $[mystr.replace(/ [a-z]+ /, "-", count=count)]
877 write $[mystr.replace(/ [a-z]+ /, "-", count=count)]
878 }
879 ## STDOUT:
880 1-2-3-
881 1-2-3-
882 1-2-3-
883 1-2-3-
884 1-2-3-
885 1-2-3-
886 1-2-3-
887 1-2-3-
888 1abc2abc3abc
889 1abc2abc3abc
890 1abc2abc3abc
891 1abc2abc3abc
892 1-2abc3abc
893 1-2abc3abc
894 1-2abc3abc
895 1-2abc3abc
896 1-2-3abc
897 1-2-3abc
898 1-2-3abc
899 1-2-3abc
900 1-2-3-
901 1-2-3-
902 1-2-3-
903 1-2-3-
904 ## END
905
906 #### Str.replace(Str, Str), empty new/old strings
907 var mystr = 'abca'
908 write $[mystr.replace('abc', '')] # Empty substitution
909 write $[mystr.replace('', 'new')] # Empty substring
910 write $[mystr.replace('', 'new', count=1)] # Empty substring, count != -1
911 write $[mystr.replace('', 'new', count=10)] # Empty substring, count too large
912 ## STDOUT:
913 a
914 newanewbnewcnewanew
915 newabca
916 newanewbnewcnewanew
917 ## END
918
919 #### Str.replace(Eggex, Lazy), convert_func
920 shopt --set ysh:all
921
922 var mystr = '123'
923
924 write $[mystr.replace(/ <capture d+ as n : int> /, ^"$[n + 1]")]
925
926 # values automatically get stringified
927 write $[mystr.replace(/ <capture d+ as n : int> /, ^"$1")]
928
929 func not_str(inp) {
930 return ({ "value": inp })
931 }
932
933 # should fail to stringify $1
934 try { call mystr.replace(/ <capture d+ : not_str> /, ^"$1") }
935 write status=$_status
936 ## STDOUT:
937 124
938 123
939 status=3
940 ## END
941
942 #### Str.replace(Eggex, *), eflags
943 shopt --set ysh:all
944
945 var mystr = b'1-2-3\n4-5'
946 write $[mystr.replace(/ d+ /, ^"[$0]")]
947 write $[mystr.replace(/ ^ d+ /, ^"[$0]")]
948 write $[mystr.replace(/ ^ d+ ; reg_newline /, ^"[$0]")]
949 ## STDOUT:
950 [1]-[2]-[3]
951 [4]-[5]
952 [1]-2-3
953 4-5
954 [1]-2-3
955 [4]-5
956 ## END
957
958 #### Str.replace(Eggex, *), guard against infinite loop
959 shopt --set ysh:all
960
961 var mystr = 'foo bar baz'
962 write $[mystr.replace(/ space* /, ' ')]
963 ## status: 3
964 ## STDOUT:
965 ## END
966
967 #### Str.replace(Eggex, *), str cannot contain NUL bytes
968 shopt --set ysh:all
969
970 var mystr = b'foo bar baz\y00'
971 write $[mystr.replace(/ space+ /, ' ')]
972 ## status: 3
973 ## STDOUT:
974 ## END
975
976 #### Str.replace() at top level
977 shopt --set ysh:upgrade
978
979 var s = 'mystr'
980 var pat = / 's' <capture dot> /
981 var template = ^"[$x $0 $1 $x]"
982 pp test_ (template)
983
984 var x = 'x'
985
986 var new = s.replace(pat, template)
987 echo 'replace ' $new
988
989 func myreplace(s, template) {
990 return (s.replace(pat, template))
991 }
992
993 echo myreplace $[myreplace(s, template)]
994
995 ## STDOUT:
996 <Expr>
997 replace my[x st t x]r
998 myreplace my[x st t x]r
999 ## END
1000
1001 #### Str.replace() lexical scope with ^""
1002 shopt --set ysh:upgrade
1003
1004 var s = 'mystr'
1005 var pat = / 's' <capture dot> /
1006
1007 proc p {
1008 var x = 'x'
1009 var template = ^"[$x $0 $1 $x]"
1010 pp test_ (template)
1011
1012 var new = s.replace(pat, template)
1013 echo 'replace ' $new
1014
1015 func myreplace(s, template) {
1016 return (s.replace(pat, template))
1017 }
1018
1019 echo myreplace $[myreplace(s, template)]
1020 }
1021
1022 p
1023
1024 ## STDOUT:
1025 <Expr>
1026 replace my[x st t x]r
1027 myreplace my[x st t x]r
1028 ## END
1029
1030 #### Str.replace() lexical scope with ^[]
1031 shopt --set ysh:upgrade
1032
1033 var s = 'mystr'
1034 var pat = / 's' <capture dot> /
1035
1036 proc p {
1037 var x = 'x'
1038 var template = ^['[' ++ x ++ ' ' ++ $0 ++ ' ' ++ $1 ++ ' ' ++ x ++ ']']
1039 pp test_ (template)
1040
1041 var new = s.replace(pat, template)
1042 echo 'replace ' $new
1043
1044 func myreplace(s, template) {
1045 return (s.replace(pat, template))
1046 }
1047
1048 echo myreplace $[myreplace(s, template)]
1049 }
1050
1051 p
1052
1053 ## STDOUT:
1054 <Expr>
1055 replace my[x st t x]r
1056 myreplace my[x st t x]r
1057 ## END