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

1062 lines, 627 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
311shopt --set parse_ysh_expr_sub
312
313var s = '= Hi5- Bye6-'
314
315var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /)
316echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
317echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
318echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
319
320echo ---
321
322var pos = m.end(0) # search from end position
323var m = s.search(/ <capture [a-z]+ > <capture d+> '-' ; i /, pos=pos)
324echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
325echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
326echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
327
328## STDOUT:
329g0 2 6 Hi5-
330g1 2 4 Hi
331g2 4 5 5
332---
333g0 7 12 Bye6-
334g1 7 10 Bye
335g2 10 11 6
336## END
337
338#### Str->search() only matches %start ^ when pos == 0
339
340shopt -s ysh:upgrade
341
342var anchored = / %start <capture d+> '-' /
343var free = / <capture d+> '-' /
344
345var s = '12-34-'
346
347for pat in ([anchored, free]) {
348 echo "pat=$pat"
349
350 var pos = 0
351 while (true) {
352 var m = s.search(pat, pos=pos)
353 if (not m) {
354 break
355 }
356 echo $[m.group(0)]
357 setvar pos = m.end(0)
358 }
359
360}
361
362## STDOUT:
363pat=^([[:digit:]]+)-
36412-
365pat=([[:digit:]]+)-
36612-
36734-
368## END
369
370
371#### search() and leftMatch() accept ERE string
372shopt --set parse_ysh_expr_sub
373
374var s = '= hi5- bye6-'
375
376var m = s.search('([[:alpha:]]+)([[:digit:]]+)-')
377echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
378echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
379echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
380echo ---
381
382var m = s[2:].leftMatch('([[:alpha:]]+)([[:digit:]]+)-')
383echo "g0 $[m.start(0)] $[m.end(0)] $[m.group(0)]"
384echo "g1 $[m.start(1)] $[m.end(1)] $[m.group(1)]"
385echo "g2 $[m.start(2)] $[m.end(2)] $[m.group(2)]"
386
387## STDOUT:
388g0 2 6 hi5-
389g1 2 4 hi
390g2 4 5 5
391---
392g0 0 4 hi5-
393g1 0 2 hi
394g2 2 3 5
395## END
396
397#### Str.leftMatch() can implement lexer pattern
398
399shopt -s ysh:upgrade
400
401var lexer = / <capture d+> | <capture [a-z]+> | <capture s+> /
402#echo $lexer
403
404proc show-tokens (s) {
405 var pos = 0
406
407 while (true) {
408 echo "pos=$pos"
409
410 var m = s.leftMatch(lexer, pos=pos)
411 if (not m) {
412 break
413 }
414 # TODO: add groups()
415 #var groups = [m.group(1), m.group(2), m.group(3)]
416 echo "$[m.group(1)]/$[m.group(2)]/$[m.group(3)]/"
417
418 echo
419
420 setvar pos = m.end(0)
421 }
422}
423
424show-tokens 'ab 12'
425
426echo '==='
427
428# There's a token here that doesn't leftMatch()
429show-tokens 'ab+12'
430
431## STDOUT:
432pos=0
433null/ab/null/
434
435pos=2
436null/null/ /
437
438pos=3
43912/null/null/
440
441pos=5
442===
443pos=0
444null/ab/null/
445
446pos=2
447## END
448
449#### Named captures with m.group()
450shopt -s ysh:all
451
452var s = 'zz 2020-08-20'
453var pat = /<capture d+ as year> '-' <capture d+ as month>/
454
455var m = s.search(pat)
456argv.py $[m.group('year')] $[m.group('month')]
457echo $[m.start('year')] $[m.end('year')]
458echo $[m.start('month')] $[m.end('month')]
459
460argv.py $[m.group('oops')]
461echo 'error'
462
463## status: 3
464## STDOUT:
465['2020', '08']
4663 7
4678 10
468## END
469
470#### Named captures with _group() _start() _end()
471shopt -s ysh:all
472
473var x = 'zz 2020-08-20'
474
475if (x ~ /<capture d+ as year> '-' <capture d+ as month>/) {
476 argv.py $[_group('year')] $[_group('month')]
477 echo $[_start('year')] $[_end('year')]
478 echo $[_start('month')] $[_end('month')]
479}
480
481argv.py $[_group('oops')]
482
483## status: 3
484## STDOUT:
485['2020', '08']
4863 7
4878 10
488## END
489
490#### Named Capture Decays Without Name
491shopt -s ysh:all
492var pat = /<capture d+ as month>/
493echo $pat
494
495if ('123' ~ pat) {
496 echo yes
497}
498
499## STDOUT:
500([[:digit:]]+)
501yes
502## END
503
504#### Nested Named Capture Uses ( ordering
505
506shopt -s ysh:upgrade
507
508var Date = /<capture d+ as year> '-' <capture d+ as month>/
509var Time = /<capture d+ as hour> ':' <capture d+ as minute> (':' <capture d+ as secs>)? /
510
511var pat = / 'when: ' (<capture Date> | <capture Time as two>) /
512#echo $pat
513
514proc show-groups (; m) {
515 echo 0 $[m.group(0)]
516 echo 1 $[m.group(1)] # this is everything except when
517 echo 2 $[m.group(2)]
518 echo
519 echo $[m.group('two')]
520 echo $[m.group('year')] $[m.group('month')]
521 echo $[m.group('hour')] $[m.group('minute')] $[m.group('secs')]
522}
523
524var m = 'when: 2023-10'.leftMatch(pat)
525
526show-groups (m)
527
528var m = 'when: 23:30'.leftMatch(pat)
529
530echo ---
531show-groups (m)
532
533var m = 'when: 23:30:59'.leftMatch(pat)
534
535echo ---
536show-groups (m)
537
538## STDOUT:
5390 when: 2023-10
5401 2023-10
5412 2023-10
542
543null
5442023 10
545null null null
546---
5470 when: 23:30
5481 23:30
5492 null
550
55123:30
552null null
55323 30 null
554---
5550 when: 23:30:59
5561 23:30:59
5572 null
558
55923:30:59
560null null
56123 30 59
562## END
563
564#### Capture with Type Conversion Func
565shopt -s ysh:upgrade
566
567var s = 'hi 42-3.14'
568var pat = / <capture d+: int> '-' <capture d+ '.' d+ : float> /
569
570if (s ~ pat) {
571 var g1 = _group(1) # Int
572 var g2 = _group(2) # Float
573 echo $[type(g1)] $[type(g2)]
574}
575
576var m = s.search(pat)
577if (m) {
578 echo $[m.group(1) => type()] $[m.group(2) => type()]
579}
580
581## STDOUT:
582Int Float
583Int Float
584## END
585
586
587#### Named Capture with Type Conversion Func
588shopt -s ysh:upgrade
589
590func floatNegate(x) {
591 return (-float(x))
592}
593
594var s = 'hi 42-3.14'
595var pat = / <capture d+ as left: int> '-' <capture d+ '.' d+ as right: floatNegate> /
596
597if (s ~ pat) {
598 var g1 = _group('left') # Int
599 var g2 = _group('right') # Float
600 echo $g2
601 echo $[type(g1)] $[type(g2)]
602}
603
604var m = s.search(pat)
605if (m) {
606 echo $[m.group('right')]
607 echo $[m.group('left') => type()] $[m.group('right') => type()]
608}
609
610## STDOUT:
611-3.14
612Int Float
613-3.14
614Int Float
615## END
616
617#### Can't splice eggex with different flags
618shopt -s ysh:upgrade
619
620var pat = / 'abc' ; i /
621var pat2 = / @pat 'def' ; reg_icase / # this is allowed
622
623var pat3 = / @pat 'def' /
624= pat3
625
626## status: 1
627## STDOUT:
628## END
629
630#### Eggex with translation preference has arbitrary flags
631shopt -s ysh:upgrade
632
633# TODO: can provide introspection so users can translate it?
634# This is kind of a speculative corner of the language.
635
636var pat = / d+ ; ignorecase ; PCRE /
637
638# This uses ERE, as a test
639if ('ab 12' ~ pat) {
640 echo yes
641}
642
643## STDOUT:
644yes
645## END
646
647
648#### Invalid sh operation on eggex
649var pat = / d+ /
650#pat[invalid]=1
651pat[invalid]+=1
652## status: 1
653## stdout-json: ""
654
655#### Long Python Example
656
657# https://docs.python.org/3/reference/lexical_analysis.html#integer-literals
658
659# integer ::= decinteger | bininteger | octinteger | hexinteger
660# decinteger ::= nonzerodigit (["_"] digit)* | "0"+ (["_"] "0")*
661# bininteger ::= "0" ("b" | "B") (["_"] bindigit)+
662# octinteger ::= "0" ("o" | "O") (["_"] octdigit)+
663# hexinteger ::= "0" ("x" | "X") (["_"] hexdigit)+
664# nonzerodigit ::= "1"..."9"
665# digit ::= "0"..."9"
666# bindigit ::= "0" | "1"
667# octdigit ::= "0"..."7"
668# hexdigit ::= digit | "a"..."f" | "A"..."F"
669
670shopt -s ysh:all
671
672const DecDigit = / [0-9] /
673const BinDigit = / [0-1] /
674const OctDigit = / [0-7] /
675const HexDigit = / [0-9 a-f A-F] / # note: not splicing Digit into character class
676
677const DecInt = / [1-9] ('_'? DecDigit)* | '0'+ ('_'? '0')* /
678const BinInt = / '0' [b B] ('_'? BinDigit)+ /
679const OctInt = / '0' [o O] ('_'? OctDigit)+ /
680const HexInt = / '0' [x X] ('_'? HexDigit)+ /
681
682const Integer = / %start (DecInt | BinInt | OctInt | HexInt) %end /
683
684#echo $Integer
685
686if ( '123' ~ Integer) { echo 'Y' }
687if ( 'zzz' !~ Integer) { echo 'N' }
688
689if ('123_000' ~ Integer) { echo 'Y decimal' }
690if ('000_123' !~ Integer) { echo 'N decimal' }
691
692if ( '0b100' ~ Integer) { echo 'Y binary' }
693if ( '0b102' !~ Integer) { echo 'N binary' }
694
695if ( '0o755' ~ Integer) { echo 'Y octal' }
696if ( '0o778' !~ Integer) { echo 'N octal' }
697
698if ( '0xFF' ~ Integer) { echo 'Y hex' }
699if ( '0xFG' !~ Integer) { echo 'N hex' }
700
701## STDOUT:
702Y
703N
704Y decimal
705N decimal
706Y binary
707N binary
708Y octal
709N octal
710Y hex
711N hex
712## END
713
714#### Regex in a loop (bug regression)
715
716shopt --set ysh:all
717
718var content = [ 1, 2 ]
719var i = 0
720while (i < len(content)) {
721 var line = content[i]
722 write $[content[i]]
723 if (str(line) ~ / s* 'imports' s* '=' s* .* /) {
724 exit
725 }
726 setvar i += 1
727}
728
729## STDOUT:
7301
7312
732## END
733
734
735#### Regex in a loop depending on var
736
737shopt --set ysh:all
738
739var lines = ['foo', 'bar']
740for line in (lines) {
741 write "line $line"
742
743 # = / $line /
744
745if ("x$line" ~ / dot @line /) {
746 #if (line ~ / $line /) {
747 write "matched $line"
748 }
749}
750
751## STDOUT:
752line foo
753matched foo
754line bar
755matched bar
756## END
757
758
759#### Regex with [ (bug regression)
760shopt --set ysh:all
761
762if ('[' ~ / '[' /) {
763 echo 'sq'
764}
765
766if ('[' ~ / [ '[' ] /) {
767 echo 'char class'
768}
769
770# User-reported string
771if ("a" ~ / s* 'imports' s* '=' s* '[' /) {
772 echo "yes"
773}
774
775## STDOUT:
776sq
777char class
778## END
779
780#### Str.replace(Str, Str)
781shopt --set ysh:all
782
783var mystr = 'abca'
784write $[mystr.replace('a', 'A')] # Two matches
785write $[mystr.replace('b', 'B')] # One match
786write $[mystr.replace('x', 'y')] # No matches
787
788write $[mystr.replace('abc', '')] # Empty substitution
789write $[mystr.replace('', 'new')] # Empty substring
790## STDOUT:
791AbcA
792aBca
793abca
794a
795newanewbnewcnewanew
796## END
797
798#### Str.replace(Eggex, Str)
799shopt --set ysh:all
800
801var mystr = 'mangled----kebab--case'
802write $[mystr.replace(/ '-'+ /, '-')]
803
804setvar mystr = 'smaller-to-bigger'
805write $[mystr.replace(/ '-'+ /, '---')]
806## STDOUT:
807mangled-kebab-case
808smaller---to---bigger
809## END
810
811#### Str.replace(Eggex, Expr)
812shopt --set ysh:all
813
814var mystr = 'name: Bob'
815write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1")]
816write $[mystr.replace(/ 'name: ' <capture dot+> /, ^"Hello $1 (extracted from '$0')")]
817## STDOUT:
818Hello Bob
819Hello Bob (extracted from 'name: Bob')
820## END
821
822#### Str.replace(*, Expr), $0
823shopt --set ysh:all
824
825# Functionality
826var mystr = 'class Foo: # this class is called Foo'
827write $[mystr.replace("Foo", ^"$0Bar")]
828write $[mystr.replace(/ 'Foo' /, ^"$0Bar")]
829
830# Edge-cases
831var dollar0 = "$0"
832#echo dollar0=$dollar0
833#echo "0 = $0"
834
835var expected = "f($dollar0)($dollar0)"
836#echo "expected = $expected"
837
838# Eager replacement
839assert [expected === "foo".replace("o", "($0)")]
840
841assert ['f(o)(o)' === "foo".replace("o", ^"($0)")]
842
843func f() { return ( "<$0>" ) }
844assert ["<$dollar0>" === f()]
845
846assert ['f<o><o>' === "foo".replace("o", ^[f()])]
847
848## STDOUT:
849class FooBar: # this class is called FooBar
850class FooBar: # this class is called FooBar
851## END
852
853#### Str.replace(Eggex, Expr), scopes
854shopt --set ysh:all
855
856var mystr = '123'
857
858var anotherVar = 'surprise!'
859write $[mystr.replace(/ <capture d+> /, ^"Hello $1 ($anotherVar)")]
860
861var globalName = '456'
862write $[mystr.replace(/ <capture d+ as globalName> /, ^"Hello $globalName")]
863
864write $[mystr.replace(/ <capture d+ as localName> /, ^"Hello $localName, $globalName")]
865## STDOUT:
866Hello 123 (surprise!)
867Hello 123
868Hello 123, 456
869## END
870
871#### Str.replace(Eggex, *, count)
872shopt --set ysh:all
873
874var mystr = '1abc2abc3abc'
875
876for count in (-2..<4) {
877 write $[mystr.replace('abc', "-", count=count)]
878 write $[mystr.replace('abc', ^"-", count=count)]
879 write $[mystr.replace(/ [a-z]+ /, "-", count=count)]
880 write $[mystr.replace(/ [a-z]+ /, "-", count=count)]
881}
882## STDOUT:
8831-2-3-
8841-2-3-
8851-2-3-
8861-2-3-
8871-2-3-
8881-2-3-
8891-2-3-
8901-2-3-
8911abc2abc3abc
8921abc2abc3abc
8931abc2abc3abc
8941abc2abc3abc
8951-2abc3abc
8961-2abc3abc
8971-2abc3abc
8981-2abc3abc
8991-2-3abc
9001-2-3abc
9011-2-3abc
9021-2-3abc
9031-2-3-
9041-2-3-
9051-2-3-
9061-2-3-
907## END
908
909#### Str.replace(Str, Str), empty new/old strings
910shopt --set parse_ysh_expr_sub
911
912var mystr = 'abca'
913write $[mystr.replace('abc', '')] # Empty substitution
914write $[mystr.replace('', 'new')] # Empty substring
915write $[mystr.replace('', 'new', count=1)] # Empty substring, count != -1
916write $[mystr.replace('', 'new', count=10)] # Empty substring, count too large
917## STDOUT:
918a
919newanewbnewcnewanew
920newabca
921newanewbnewcnewanew
922## END
923
924#### Str.replace(Eggex, Lazy), convert_func
925shopt --set ysh:all
926
927var mystr = '123'
928
929write $[mystr.replace(/ <capture d+ as n : int> /, ^"$[n + 1]")]
930
931# values automatically get stringified
932write $[mystr.replace(/ <capture d+ as n : int> /, ^"$1")]
933
934func not_str(inp) {
935 return ({ "value": inp })
936}
937
938# should fail to stringify $1
939try { call mystr.replace(/ <capture d+ : not_str> /, ^"$1") }
940write status=$_status
941## STDOUT:
942124
943123
944status=3
945## END
946
947#### Str.replace(Eggex, *), eflags
948shopt --set ysh:all
949
950var mystr = b'1-2-3\n4-5'
951write $[mystr.replace(/ d+ /, ^"[$0]")]
952write $[mystr.replace(/ ^ d+ /, ^"[$0]")]
953write $[mystr.replace(/ ^ d+ ; reg_newline /, ^"[$0]")]
954## STDOUT:
955[1]-[2]-[3]
956[4]-[5]
957[1]-2-3
9584-5
959[1]-2-3
960[4]-5
961## END
962
963#### Str.replace(Eggex, *), guard against infinite loop
964shopt --set ysh:all
965
966var mystr = 'foo bar baz'
967write $[mystr.replace(/ space* /, ' ')]
968## status: 3
969## STDOUT:
970## END
971
972#### Str.replace(Eggex, *), str cannot contain NUL bytes
973shopt --set ysh:all
974
975var mystr = b'foo bar baz\y00'
976write $[mystr.replace(/ space+ /, ' ')]
977## status: 3
978## STDOUT:
979## END
980
981#### Str.replace() at top level
982shopt --set ysh:upgrade
983
984var s = 'mystr'
985var pat = / 's' <capture dot> /
986var template = ^"[$x $0 $1 $x]"
987pp test_ (template)
988
989var x = 'x'
990
991var new = s.replace(pat, template)
992echo 'replace ' $new
993
994func myreplace(s, template) {
995 return (s.replace(pat, template))
996}
997
998echo myreplace $[myreplace(s, template)]
999
1000## STDOUT:
1001<Expr>
1002replace my[x st t x]r
1003myreplace my[x st t x]r
1004## END
1005
1006#### Str.replace() lexical scope with ^""
1007shopt --set ysh:upgrade
1008
1009var s = 'mystr'
1010var pat = / 's' <capture dot> /
1011
1012proc p {
1013 var x = 'x'
1014 var template = ^"[$x $0 $1 $x]"
1015 pp test_ (template)
1016
1017 var new = s.replace(pat, template)
1018 echo 'replace ' $new
1019
1020 func myreplace(s, template) {
1021 return (s.replace(pat, template))
1022 }
1023
1024 echo myreplace $[myreplace(s, template)]
1025}
1026
1027p
1028
1029## STDOUT:
1030<Expr>
1031replace my[x st t x]r
1032myreplace my[x st t x]r
1033## END
1034
1035#### Str.replace() lexical scope with ^[]
1036shopt --set ysh:upgrade
1037
1038var s = 'mystr'
1039var pat = / 's' <capture dot> /
1040
1041proc p {
1042 var x = 'x'
1043 var template = ^['[' ++ x ++ ' ' ++ $0 ++ ' ' ++ $1 ++ ' ' ++ x ++ ']']
1044 pp test_ (template)
1045
1046 var new = s.replace(pat, template)
1047 echo 'replace ' $new
1048
1049 func myreplace(s, template) {
1050 return (s.replace(pat, template))
1051 }
1052
1053 echo myreplace $[myreplace(s, template)]
1054}
1055
1056p
1057
1058## STDOUT:
1059<Expr>
1060replace my[x st t x]r
1061myreplace my[x st t x]r
1062## END