OILS / spec / ysh-regex-api.test.sh View on Github | oils.pub

1057 lines, 624 significant
1## oils_failures_allowed: 0
2
3#### s ~ regex and s !~ regex
4shopt -s ysh:upgrade
5
6var s = 'foo'
7if (s ~ '.([[:alpha:]]+)') { # ERE syntax
8 echo matches
9 argv.py $[_group(0)] $[_group(1)]
10}
11if (s !~ '[[:digit:]]+') {
12 echo "does not match"
13 argv.py $[_group(0)] $[_group(1)]
14}
15
16if (s ~ '[[:digit:]]+') {
17 echo "matches"
18}
19# Should be cleared now
20# should this be Undef rather than ''?
21try {
22 var x = _group(0)
23}
24if (_status === 3) {
25 echo 'got expected status 3'
26}
27
28try {
29 var y = _group(1)
30}
31if (_status === 3) {
32 echo 'got expected status 3'
33}
34
35## STDOUT:
36matches
37['foo', 'oo']
38does not match
39['foo', 'oo']
40got expected status 3
41got expected status 3
42## END
43
44#### Invalid regex has libc error message
45
46shopt -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
55if ('abc' ~ '+') {
56 echo 'bad'
57}
58
59## status: 2
60## STDOUT:
61## END
62
63#### Eggex flags to ignore case are respected
64shopt -s ysh:upgrade
65
66# based on Python's spelling
67var pat = / 'abc' ; i /
68var pat2 = / @pat 'def' ; reg_icase / # this is allowed
69
70if ('-abcdef-' ~ pat2) {
71 echo 'yes'
72}
73
74if ('-ABCDEF-' ~ pat2) {
75 echo 'yes'
76}
77
78if ('ABCDE' ~ pat2) {
79 echo 'BUG'
80}
81
82## STDOUT:
83yes
84yes
85## END
86
87#### Eggex flags to treat newlines as special are respected
88shopt -s ysh:upgrade
89
90if (u'abc123\n' ~ / digit %end /) {
91 echo 'BUG'
92}
93if (u'abc\n123' ~ / %start digit /) {
94 echo 'BUG'
95}
96
97if (u'abc123\n' ~ / digit %end ; reg_newline /) {
98 echo 'yes'
99}
100if (u'abc\n123' ~ / %start digit ; reg_newline /) {
101 echo 'yes'
102}
103
104if (u'\n' ~ / . /) {
105 echo 'yes'
106}
107if (u'\n' ~ / !digit /) {
108 echo 'yes'
109}
110
111if (u'\n' ~ / . ; reg_newline /) {
112 echo 'BUG'
113}
114if (u'\n' ~ / !digit ; reg_newline /) {
115 echo 'BUG'
116}
117
118## STDOUT:
119yes
120yes
121yes
122yes
123## END
124
125#### leftMatch() combined with REG_NEWLINE - interaction with ^
126shopt --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
136var lines = '''
137 one
138 2
139 three
140 '''
141
142if (0) {
143 = lines
144 = ${p}
145 echo ---
146}
147
148proc 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
165var p1 = / <capture [a-z] as letter> /
166var p2 = / <capture [a-z] as letter> ; ; reg_newline /
167
168show-matches (p1)
169echo
170show-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)
183shopt --set ysh:upgrade
184
185var lines = '''
186 one
187 2
188 three
189 '''
190
191proc 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
209var p3 = / <capture [0-9] as number> | <capture [a-z] as letter> /
210var p4 = / <capture [0-9] as number> | <capture [a-z] as letter> ; ; reg_newline /
211
212var m = lines.leftMatch(p3, pos=3)
213assert [m is null]
214
215var m = lines.leftMatch(p3, pos=4)
216pp test_ ([m.group(0), m.group(1), m.group(2)])
217
218var m = lines.leftMatch(p3, pos=5)
219assert [m is null]
220
221var m = lines.leftMatch(p3, pos=6)
222pp test_ ([m.group(0), m.group(1), m.group(2)])
223
224echo
225show-matches (p3)
226
227echo
228show-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
244shopt -s ysh:upgrade
245
246var x = 'zz 2020-08-20'
247
248if [[ $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.
254setvar BASH_REMATCH = :| reset |
255
256if (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
269shopt -s ysh:upgrade
270
271var pat = / <capture 'a'> | <capture 'b'> /
272if ('b' ~ pat) {
273 echo "$[_group(1)] $[_group(2)]"
274}
275## STDOUT:
276null b
277## END
278
279#### _start() and _end()
280shopt -s ysh:upgrade
281
282var s = 'foo123bar'
283if (s ~ /digit+/) {
284 echo start=$[_start(0)] end=$[_end(0)]
285}
286echo ---
287
288if (s ~ / <capture [a-z]+> <capture digit+> /) {
289 echo start=$[_start(1)] end=$[_end(1)]
290 echo start=$[_start(2)] end=$[_end(2)]
291}
292echo ---
293
294if (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:
300start=3 end=6
301---
302start=0 end=3
303start=3 end=6
304---
305start=0 end=3
306start=-1 end=-1
307## END
308
309#### Str->search() method returns value.Match object
310
311var s = '= Hi5- Bye6-'
312
313var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /)
314echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
315echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
316echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
317
318echo ---
319
320var pos = m.end(0) # search from end position
321var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /, pos=pos)
322echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
323echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
324echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
325
326## STDOUT:
327g0 2 6 Hi5-
328g1 2 4 Hi
329g2 4 5 5
330---
331g0 7 12 Bye6-
332g1 7 10 Bye
333g2 10 11 6
334## END
335
336#### Str->search() only matches %start ^ when pos == 0
337
338shopt -s ysh:upgrade
339
340var anchored = / %start <capture d+> '-' /
341var free = / <capture d+> '-' /
342
343var s = '12-34-'
344
345for 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:
361pat=^([[:digit:]]+)-
36212-
363pat=([[:digit:]]+)-
36412-
36534-
366## END
367
368
369#### search() and leftMatch() accept ERE string
370
371var s = '= hi5- bye6-'
372
373var m = s.search('([[:alpha:]]+)([[:digit:]]+)-')
374echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
375echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
376echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
377echo ---
378
379var m = s[2:].leftMatch('([[:alpha:]]+)([[:digit:]]+)-')
380echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
381echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
382echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
383
384## STDOUT:
385g0 2 6 hi5-
386g1 2 4 hi
387g2 4 5 5
388---
389g0 0 4 hi5-
390g1 0 2 hi
391g2 2 3 5
392## END
393
394#### Str.leftMatch() can implement lexer pattern
395
396shopt -s ysh:upgrade
397
398var lexer = / <capture d+> | <capture [a-z]+> | <capture s+> /
399#echo $lexer
400
401proc 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
421show-tokens 'ab 12'
422
423echo '==='
424
425# There's a token here that doesn't leftMatch()
426show-tokens 'ab+12'
427
428## STDOUT:
429pos=0
430null/ab/null/
431
432pos=2
433null/null/ /
434
435pos=3
43612/null/null/
437
438pos=5
439===
440pos=0
441null/ab/null/
442
443pos=2
444## END
445
446#### Named captures with m.group()
447shopt -s ysh:all
448
449var s = 'zz 2020-08-20'
450var pat = /<capture d+ as year> '-' <capture d+ as month>/
451
452var m = s.search(pat)
453argv.py $[m.group('year')] $[m.group('month')]
454echo $[m.start('year')] $[m.end('year')]
455echo $[m.start('month')] $[m.end('month')]
456
457argv.py $[m.group('oops')]
458echo 'error'
459
460## status: 3
461## STDOUT:
462['2020', '08']
4633 7
4648 10
465## END
466
467#### Named captures with _group() _start() _end()
468shopt -s ysh:all
469
470var x = 'zz 2020-08-20'
471
472if (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
478argv.py $[_group('oops')]
479
480## status: 3
481## STDOUT:
482['2020', '08']
4833 7
4848 10
485## END
486
487#### Named Capture Decays Without Name
488shopt -s ysh:all
489var pat = /<capture d+ as month>/
490echo $pat
491
492if ('123' ~ pat) {
493 echo yes
494}
495
496## STDOUT:
497([[:digit:]]+)
498yes
499## END
500
501#### Nested Named Capture Uses ( ordering
502
503shopt -s ysh:upgrade
504
505var Date = /<capture d+ as year> '-' <capture d+ as month>/
506var Time = /<capture d+ as hour> ':' <capture d+ as minute> (':' <capture d+ as secs>)? /
507
508var pat = / 'when: ' (<capture Date> | <capture Time as two>) /
509#echo $pat
510
511proc 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
521var m = 'when: 2023-10'.leftMatch(pat)
522
523show-groups (m)
524
525var m = 'when: 23:30'.leftMatch(pat)
526
527echo ---
528show-groups (m)
529
530var m = 'when: 23:30:59'.leftMatch(pat)
531
532echo ---
533show-groups (m)
534
535## STDOUT:
5360 when: 2023-10
5371 2023-10
5382 2023-10
539
540null
5412023 10
542null null null
543---
5440 when: 23:30
5451 23:30
5462 null
547
54823:30
549null null
55023 30 null
551---
5520 when: 23:30:59
5531 23:30:59
5542 null
555
55623:30:59
557null null
55823 30 59
559## END
560
561#### Capture with Type Conversion Func
562shopt -s ysh:upgrade
563
564var s = 'hi 42-3.14'
565var pat = / <capture d+: int> '-' <capture d+ '.' d+ : float> /
566
567if (s ~ pat) {
568 var g1 = _group(1) # Int
569 var g2 = _group(2) # Float
570 echo $[type(g1)] $[type(g2)]
571}
572
573var m = s.search(pat)
574if (m) {
575 echo $[m.group(1) => type()] $[m.group(2) => type()]
576}
577
578## STDOUT:
579Int Float
580Int Float
581## END
582
583
584#### Named Capture with Type Conversion Func
585shopt -s ysh:upgrade
586
587func floatNegate(x) {
588 return (-float(x))
589}
590
591var s = 'hi 42-3.14'
592var pat = / <capture d+ as left: int> '-' <capture d+ '.' d+ as right: floatNegate> /
593
594if (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
601var m = s.search(pat)
602if (m) {
603 echo $[m.group('right')]
604 echo $[m.group('left') => type()] $[m.group('right') => type()]
605}
606
607## STDOUT:
608-3.14
609Int Float
610-3.14
611Int Float
612## END
613
614#### Can't splice eggex with different flags
615shopt -s ysh:upgrade
616
617var pat = / 'abc' ; i /
618var pat2 = / @pat 'def' ; reg_icase / # this is allowed
619
620var pat3 = / @pat 'def' /
621= pat3
622
623## status: 1
624## STDOUT:
625## END
626
627#### Eggex with translation preference has arbitrary flags
628shopt -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
633var pat = / d+ ; ignorecase ; PCRE /
634
635# This uses ERE, as a test
636if ('ab 12' ~ pat) {
637 echo yes
638}
639
640## STDOUT:
641yes
642## END
643
644
645#### Invalid sh operation on eggex
646var pat = / d+ /
647#pat[invalid]=1
648pat[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
667shopt -s ysh:all
668
669const DecDigit = / [0-9] /
670const BinDigit = / [0-1] /
671const OctDigit = / [0-7] /
672const HexDigit = / [0-9 a-f A-F] / # note: not splicing Digit into character class
673
674const DecInt = / [1-9] ('_'? DecDigit)* | '0'+ ('_'? '0')* /
675const BinInt = / '0' [b B] ('_'? BinDigit)+ /
676const OctInt = / '0' [o O] ('_'? OctDigit)+ /
677const HexInt = / '0' [x X] ('_'? HexDigit)+ /
678
679const Integer = / %start (DecInt | BinInt | OctInt | HexInt) %end /
680
681#echo $Integer
682
683if ( '123' ~ Integer) { echo 'Y' }
684if ( 'zzz' !~ Integer) { echo 'N' }
685
686if ('123_000' ~ Integer) { echo 'Y decimal' }
687if ('000_123' !~ Integer) { echo 'N decimal' }
688
689if ( '0b100' ~ Integer) { echo 'Y binary' }
690if ( '0b102' !~ Integer) { echo 'N binary' }
691
692if ( '0o755' ~ Integer) { echo 'Y octal' }
693if ( '0o778' !~ Integer) { echo 'N octal' }
694
695if ( '0xFF' ~ Integer) { echo 'Y hex' }
696if ( '0xFG' !~ Integer) { echo 'N hex' }
697
698## STDOUT:
699Y
700N
701Y decimal
702N decimal
703Y binary
704N binary
705Y octal
706N octal
707Y hex
708N hex
709## END
710
711#### Regex in a loop (bug regression)
712
713shopt --set ysh:all
714
715var content = [ 1, 2 ]
716var i = 0
717while (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:
7271
7282
729## END
730
731
732#### Regex in a loop depending on var
733
734shopt --set ysh:all
735
736var lines = ['foo', 'bar']
737for line in (lines) {
738 write "line $line"
739
740 # = / $line /
741
742if ("x$line" ~ / dot @line /) {
743 #if (line ~ / $line /) {
744 write "matched $line"
745 }
746}
747
748## STDOUT:
749line foo
750matched foo
751line bar
752matched bar
753## END
754
755
756#### Regex with [ (bug regression)
757shopt --set ysh:all
758
759if ('[' ~ / '[' /) {
760 echo 'sq'
761}
762
763if ('[' ~ / [ '[' ] /) {
764 echo 'char class'
765}
766
767# User-reported string
768if ("a" ~ / s* 'imports' s* '=' s* '[' /) {
769 echo "yes"
770}
771
772## STDOUT:
773sq
774char class
775## END
776
777#### Str.replace(Str, Str)
778shopt --set ysh:all
779
780var mystr = 'abca'
781write $[mystr.replace('a', 'A')] # Two matches
782write $[mystr.replace('b', 'B')] # One match
783write $[mystr.replace('x', 'y')] # No matches
784
785write $[mystr.replace('abc', '')] # Empty substitution
786write $[mystr.replace('', 'new')] # Empty substring
787## STDOUT:
788AbcA
789aBca
790abca
791a
792newanewbnewcnewanew
793## END
794
795#### Str.replace(Eggex, Str)
796shopt --set ysh:all
797
798var mystr = 'mangled----kebab--case'
799write $[mystr.replace(/ '-'+ /, '-')]
800
801setvar mystr = 'smaller-to-bigger'
802write $[mystr.replace(/ '-'+ /, '---')]
803## STDOUT:
804mangled-kebab-case
805smaller---to---bigger
806## END
807
808#### Str.replace(Eggex, Expr)
809shopt --set ysh:all
810
811var mystr = 'name: Bob'
812write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1")]
813write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1 (extracted from '$0')")]
814## STDOUT:
815Hello Bob
816Hello Bob (extracted from 'name: Bob')
817## END
818
819#### Str.replace(*, Expr), $0
820shopt --set ysh:all
821
822# Functionality
823var mystr = 'class Foo: # this class is called Foo'
824write $[mystr.replace("Foo", ^"$0Bar")]
825write $[mystr.replace(/ 'Foo' /, ^"$0Bar")]
826
827# Edge-cases
828var dollar0 = "$0"
829#echo dollar0=$dollar0
830#echo "0 = $0"
831
832var expected = "f($dollar0)($dollar0)"
833#echo "expected = $expected"
834
835# Eager replacement
836assert [expected === "foo".replace("o", "($0)")]
837
838assert ['f(o)(o)' === "foo".replace("o", ^"($0)")]
839
840func f() { return ( "<$0>" ) }
841assert ["<$dollar0>" === f()]
842
843assert ['f<o><o>' === "foo".replace("o", ^[f()])]
844
845## STDOUT:
846class FooBar: # this class is called FooBar
847class FooBar: # this class is called FooBar
848## END
849
850#### Str.replace(Eggex, Expr), scopes
851shopt --set ysh:all
852
853var mystr = '123'
854
855var anotherVar = 'surprise!'
856write $[mystr.replace(/ <capture d+> /, ^"Hello $1 ($anotherVar)")]
857
858var globalName = '456'
859write $[mystr.replace(/ <capture d+ as globalName> /, ^"Hello $globalName")]
860
861write $[mystr.replace(/ <capture d+ as localName> /, ^"Hello $localName, $globalName")]
862## STDOUT:
863Hello 123 (surprise!)
864Hello 123
865Hello 123, 456
866## END
867
868#### Str.replace(Eggex, *, count)
869shopt --set ysh:all
870
871var mystr = '1abc2abc3abc'
872
873for 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:
8801-2-3-
8811-2-3-
8821-2-3-
8831-2-3-
8841-2-3-
8851-2-3-
8861-2-3-
8871-2-3-
8881abc2abc3abc
8891abc2abc3abc
8901abc2abc3abc
8911abc2abc3abc
8921-2abc3abc
8931-2abc3abc
8941-2abc3abc
8951-2abc3abc
8961-2-3abc
8971-2-3abc
8981-2-3abc
8991-2-3abc
9001-2-3-
9011-2-3-
9021-2-3-
9031-2-3-
904## END
905
906#### Str.replace(Str, Str), empty new/old strings
907var mystr = 'abca'
908write $[mystr.replace('abc', '')] # Empty substitution
909write $[mystr.replace('', 'new')] # Empty substring
910write $[mystr.replace('', 'new', count=1)] # Empty substring, count != -1
911write $[mystr.replace('', 'new', count=10)] # Empty substring, count too large
912## STDOUT:
913a
914newanewbnewcnewanew
915newabca
916newanewbnewcnewanew
917## END
918
919#### Str.replace(Eggex, Lazy), convert_func
920shopt --set ysh:all
921
922var mystr = '123'
923
924write $[mystr.replace(/ <capture d+ as n : int> /, ^"$[n + 1]")]
925
926# values automatically get stringified
927write $[mystr.replace(/ <capture d+ as n : int> /, ^"$1")]
928
929func not_str(inp) {
930 return ({ "value": inp })
931}
932
933# should fail to stringify $1
934try { call mystr.replace(/ <capture d+ : not_str> /, ^"$1") }
935write status=$_status
936## STDOUT:
937124
938123
939status=3
940## END
941
942#### Str.replace(Eggex, *), eflags
943shopt --set ysh:all
944
945var mystr = b'1-2-3\n4-5'
946write $[mystr.replace(/ d+ /, ^"[$0]")]
947write $[mystr.replace(/ ^ d+ /, ^"[$0]")]
948write $[mystr.replace(/ ^ d+ ; reg_newline /, ^"[$0]")]
949## STDOUT:
950[1]-[2]-[3]
951[4]-[5]
952[1]-2-3
9534-5
954[1]-2-3
955[4]-5
956## END
957
958#### Str.replace(Eggex, *), guard against infinite loop
959shopt --set ysh:all
960
961var mystr = 'foo bar baz'
962write $[mystr.replace(/ space* /, ' ')]
963## status: 3
964## STDOUT:
965## END
966
967#### Str.replace(Eggex, *), str cannot contain NUL bytes
968shopt --set ysh:all
969
970var mystr = b'foo bar baz\y00'
971write $[mystr.replace(/ space+ /, ' ')]
972## status: 3
973## STDOUT:
974## END
975
976#### Str.replace() at top level
977shopt --set ysh:upgrade
978
979var s = 'mystr'
980var pat = / 's' <capture dot> /
981var template = ^"[$x $0 $1 $x]"
982pp test_ (template)
983
984var x = 'x'
985
986var new = s.replace(pat, template)
987echo 'replace ' $new
988
989func myreplace(s, template) {
990 return (s.replace(pat, template))
991}
992
993echo myreplace $[myreplace(s, template)]
994
995## STDOUT:
996<Expr>
997replace my[x st t x]r
998myreplace my[x st t x]r
999## END
1000
1001#### Str.replace() lexical scope with ^""
1002shopt --set ysh:upgrade
1003
1004var s = 'mystr'
1005var pat = / 's' <capture dot> /
1006
1007proc 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
1022p
1023
1024## STDOUT:
1025<Expr>
1026replace my[x st t x]r
1027myreplace my[x st t x]r
1028## END
1029
1030#### Str.replace() lexical scope with ^[]
1031shopt --set ysh:upgrade
1032
1033var s = 'mystr'
1034var pat = / 's' <capture dot> /
1035
1036proc 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
1051p
1052
1053## STDOUT:
1054<Expr>
1055replace my[x st t x]r
1056myreplace my[x st t x]r
1057## END