OILS / spec / arith.test.sh View on Github | oils.pub

1040 lines, 477 significant
1## compare_shells: bash dash mksh zsh
2
3
4# Interesting interpretation of constants.
5#
6# "Constants with a leading 0 are interpreted as octal numbers. A leading ‘0x’
7# or ‘0X’ denotes hexadecimal. Otherwise, numbers take the form [base#]n, where
8# the optional base is a decimal number between 2 and 64 representing the
9# arithmetic base, and n is a number in that base. If base# is omitted, then
10# base 10 is used. When specifying n, the digits greater than 9 are represented
11# by the lowercase letters, the uppercase letters, ‘@’, and ‘_’, in that order.
12# If base is less than or equal to 36, lowercase and uppercase letters may be
13# used interchangeably to represent numbers between 10 and 35. "
14#
15# NOTE $(( 8#9 )) can fail, and this can be done at parse time...
16
17#### Side Effect in Array Indexing
18a=(4 5 6)
19echo "${a[b=2]} b=$b"
20## stdout: 6 b=2
21## OK zsh stdout: 5 b=2
22## N-I dash stdout-json: ""
23## N-I dash status: 2
24
25#### Add one to var
26i=1
27echo $(($i+1))
28## stdout: 2
29
30#### $ is optional
31i=1
32echo $((i+1))
33## stdout: 2
34
35#### SimpleVarSub within arith
36j=0
37echo $(($j + 42))
38## stdout: 42
39
40#### BracedVarSub within ArithSub
41echo $((${j:-5} + 1))
42## stdout: 6
43
44#### Arith word part
45foo=1; echo $((foo+1))bar$(($foo+1))
46## stdout: 2bar2
47
48#### Arith sub with word parts
49# Making 13 from two different kinds of sub. Geez.
50echo $((1 + $(echo 1)${undefined:-3}))
51## stdout: 14
52
53#### Constant with quotes like '1'
54# NOTE: Compare with [[. That is a COMMAND level expression, while this is a
55# WORD level expression.
56echo $(('1' + 2))
57## status: 0
58## N-I bash/zsh status: 1
59## N-I dash status: 2
60
61#### Arith sub within arith sub
62# This is unnecessary but works in all shells.
63echo $((1 + $((2 + 3)) + 4))
64## stdout: 10
65
66#### Backticks within arith sub
67# This is unnecessary but works in all shells.
68echo $((`echo 1` + 2))
69## stdout: 3
70
71#### Invalid string to int
72# bash, mksh, and zsh all treat strings that don't look like numbers as zero.
73shopt -u strict_arith || true
74s=foo
75echo $((s+5))
76## OK dash stdout-json: ""
77## OK dash status: 2
78## OK bash/mksh/zsh/osh stdout: 5
79## OK bash/mksh/zsh/osh status: 0
80
81#### Invalid string to int with strict_arith
82shopt -s strict_arith || true
83s=foo
84echo $s
85echo $((s+5))
86echo 'should not get here'
87## status: 1
88## STDOUT:
89foo
90## END
91## OK dash status: 2
92## N-I bash/mksh/zsh STDOUT:
93foo
945
95should not get here
96## END
97## N-I bash/mksh/zsh status: 0
98
99#### Integer constant parsing
100echo $(( 0x12A ))
101echo $(( 0x0A ))
102echo $(( 0777 ))
103echo $(( 0010 ))
104echo $(( 24#ag7 ))
105## STDOUT:
106298
10710
108511
1098
1106151
111## END
112
113## N-I dash status: 2
114## N-I dash STDOUT:
115298
11610
117511
1188
119## END
120
121## BUG zsh STDOUT:
122298
12310
124777
12510
1266151
127## END
128
129## BUG mksh STDOUT:
130298
13110
132777
13310
1346151
135## END
136
137#### Integer constant validation
138check() {
139 $SH -c "shopt --set strict_arith; echo $1"
140 echo status=$?
141}
142
143check '$(( 0x1X ))'
144check '$(( 09 ))'
145check '$(( 2#A ))'
146check '$(( 02#0110 ))'
147## STDOUT:
148status=1
149status=1
150status=1
151status=1
152## END
153
154## OK dash STDOUT:
155status=2
156status=2
157status=2
158status=2
159## END
160
161## BUG zsh STDOUT:
162status=1
1639
164status=0
165status=1
1666
167status=0
168## END
169
170## BUG mksh STDOUT:
171status=1
1729
173status=0
174status=1
1756
176status=0
177## END
178
179#### Newline in the middle of expression
180echo $((1
181+ 2))
182## stdout: 3
183
184#### Ternary operator
185a=1
186b=2
187echo $((a>b?5:10))
188## stdout: 10
189
190#### Preincrement
191a=4
192echo $((++a))
193echo $a
194## STDOUT:
1955
1965
197## END
198## N-I dash status: 0
199## N-I dash STDOUT:
2004
2014
202## END
203
204#### Postincrement
205a=4
206echo $((a++))
207echo $a
208## STDOUT:
2094
2105
211## END
212## N-I dash status: 2
213## N-I dash stdout-json: ""
214
215#### Increment undefined variables
216shopt -u strict_arith || true
217(( undef1++ ))
218(( ++undef2 ))
219echo "[$undef1][$undef2]"
220## stdout: [1][1]
221## N-I dash stdout: [][]
222
223#### Increment and decrement array elements
224shopt -u strict_arith || true
225a=(5 6 7 8)
226(( a[0]++, ++a[1], a[2]--, --a[3] ))
227(( undef[0]++, ++undef[1], undef[2]--, --undef[3] ))
228echo "${a[@]}" - "${undef[@]}"
229## stdout: 6 7 6 7 - 1 1 -1 -1
230## N-I dash stdout-json: ""
231## N-I dash status: 2
232## BUG zsh stdout: 5 6 7 8 -
233
234#### Increment undefined variables with nounset
235set -o nounset
236(( undef1++ ))
237(( ++undef2 ))
238echo "[$undef1][$undef2]"
239## stdout-json: ""
240## status: 1
241## OK dash status: 2
242## BUG mksh/zsh status: 0
243## BUG mksh/zsh STDOUT:
244[1][1]
245## END
246
247#### Comma operator (borrowed from C)
248a=1
249b=2
250echo $((a,(b+1)))
251## stdout: 3
252## N-I dash status: 2
253## N-I dash stdout-json: ""
254
255#### Augmented assignment
256a=4
257echo $((a+=1))
258echo $a
259## STDOUT:
2605
2615
262## END
263
264#### Comparison Ops
265echo $(( 1 == 1 ))
266echo $(( 1 != 1 ))
267echo $(( 1 < 1 ))
268echo $(( 1 <= 1 ))
269echo $(( 1 > 1 ))
270echo $(( 1 >= 1 ))
271## STDOUT:
2721
2730
2740
2751
2760
2771
278## END
279
280#### Logical Ops
281echo $((1 || 2))
282echo $((1 && 2))
283echo $((!(1 || 2)))
284## STDOUT:
2851
2861
2870
288## END
289
290#### Logical Ops Short Circuit
291x=11
292(( 1 || (x = 22) ))
293echo $x
294(( 0 || (x = 33) ))
295echo $x
296(( 0 && (x = 44) ))
297echo $x
298(( 1 && (x = 55) ))
299echo $x
300## STDOUT:
30111
30233
30333
30455
305## END
306## N-I dash STDOUT:
30711
30811
30911
31011
311## END
312
313#### Bitwise ops
314echo $((1|2))
315echo $((1&2))
316echo $((1^2))
317echo $((~(1|2)))
318## STDOUT:
3193
3200
3213
322-4
323## END
324
325#### Unary minus and plus
326a=1
327b=3
328echo $((- a + + b))
329## STDOUT:
3302
331## END
332
333#### No floating point
334echo $((1 + 2.3))
335## status: 2
336## OK bash/mksh status: 1
337## BUG zsh status: 0
338
339#### Array indexing in arith
340# zsh does 1-based indexing!
341array=(1 2 3 4)
342echo $((array[1] + array[2]*3))
343## stdout: 11
344## OK zsh stdout: 7
345## N-I dash status: 2
346## N-I dash stdout-json: ""
347
348#### Constants in base 36
349echo $((36#a))-$((36#z))
350## stdout: 10-35
351## N-I dash stdout-json: ""
352## N-I dash status: 2
353
354#### Constants in bases 2 to 64
355# This is a truly bizarre syntax. Oh it comes from zsh... which allows 36.
356echo $((64#a))-$((64#z)), $((64#A))-$((64#Z)), $((64#@)), $(( 64#_ ))
357## stdout: 10-35, 36-61, 62, 63
358## N-I dash stdout-json: ""
359## N-I dash status: 2
360## N-I mksh/zsh stdout-json: ""
361## N-I mksh/zsh status: 1
362
363#### Multiple digit constants with base N
364echo $((10#0123)), $((16#1b))
365## stdout: 123, 27
366## N-I dash stdout-json: ""
367## N-I dash status: 2
368
369#### Dynamic base constants
370base=16
371echo $(( ${base}#a ))
372## stdout: 10
373## N-I dash stdout-json: ""
374## N-I dash status: 2
375
376#### Octal constant
377echo $(( 011 ))
378## stdout: 9
379## N-I mksh/zsh stdout: 11
380
381#### Dynamic octal constant
382zero=0
383echo $(( ${zero}11 ))
384## stdout: 9
385## N-I mksh/zsh stdout: 11
386
387#### Dynamic hex constants
388zero=0
389echo $(( ${zero}xAB ))
390## stdout: 171
391
392#### Dynamic var names - result of runtime parse/eval
393foo=5
394x=oo
395echo $(( foo + f$x + 1 ))
396## stdout: 11
397
398#### Recursive name evaluation is a result of runtime parse/eval
399foo=5
400bar=foo
401spam=bar
402eggs=spam
403echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
404## stdout: 6 6 6 6
405## N-I dash stdout-json: ""
406## N-I dash status: 2
407
408#### nounset with arithmetic
409set -o nounset
410x=$(( y + 5 ))
411echo "should not get here: x=${x:-<unset>}"
412## stdout-json: ""
413## status: 1
414## BUG dash/mksh/zsh stdout: should not get here: x=5
415## BUG dash/mksh/zsh status: 0
416
417#### 64-bit integer doesn't overflow
418
419a=$(( 1 << 31 ))
420echo $a
421
422b=$(( a + a ))
423echo $b
424
425c=$(( b + a ))
426echo $c
427
428x=$(( 1 << 62 ))
429y=$(( x - 1 ))
430echo "max positive = $(( x + y ))"
431
432#echo "overflow $(( x + x ))"
433
434## STDOUT:
4352147483648
4364294967296
4376442450944
438max positive = 9223372036854775807
439## END
440
441# mksh still uses int!
442## BUG mksh STDOUT:
443-2147483648
4440
445-2147483648
446max positive = 2147483647
447## END
448
449#### More 64-bit ops
450case $SH in dash) exit ;; esac
451
452#shopt -s strict_arith
453
454# This overflows - the extra 9 puts it above 2**31
455#echo $(( 12345678909 ))
456
457[[ 12345678909 = $(( 1 << 30 )) ]]
458echo eq=$?
459[[ 12345678909 = 12345678909 ]]
460echo eq=$?
461
462# Try both [ and [[
463[ 12345678909 -gt $(( 1 << 30 )) ]
464echo greater=$?
465[[ 12345678909 -gt $(( 1 << 30 )) ]]
466echo greater=$?
467
468[[ 12345678909 -ge $(( 1 << 30 )) ]]
469echo ge=$?
470[[ 12345678909 -ge 12345678909 ]]
471echo ge=$?
472
473[[ 12345678909 -le $(( 1 << 30 )) ]]
474echo le=$?
475[[ 12345678909 -le 12345678909 ]]
476echo le=$?
477
478## STDOUT:
479eq=1
480eq=0
481greater=0
482greater=0
483ge=0
484ge=0
485le=1
486le=0
487## END
488## N-I dash STDOUT:
489## END
490## BUG mksh STDOUT:
491eq=1
492eq=0
493greater=1
494greater=1
495ge=1
496ge=0
497le=0
498le=0
499## END
500
501# mksh still uses int!
502
503#### Invalid LValue
504a=9
505(( (a + 2) = 3 ))
506echo $a
507## status: 2
508## stdout-json: ""
509## OK bash/mksh/zsh stdout: 9
510## OK bash/mksh/zsh status: 0
511# dash doesn't implement assignment
512## N-I dash status: 2
513## N-I dash stdout-json: ""
514
515#### Invalid LValue that looks like array
516(( 1[2] = 3 ))
517echo "status=$?"
518## status: 1
519## stdout-json: ""
520
521## OK bash stdout: status=1
522## OK bash status: 0
523
524## OK mksh/zsh stdout: status=2
525## OK mksh/zsh status: 0
526
527## N-I dash stdout: status=127
528## N-I dash status: 0
529
530#### Invalid LValue: two sets of brackets
531(( a[1][2] = 3 ))
532echo "status=$?"
533# shells treat this as a NON-fatal error
534## status: 2
535## stdout-json: ""
536## OK bash stdout: status=1
537## OK mksh/zsh stdout: status=2
538## OK bash/mksh/zsh status: 0
539# dash doesn't implement assignment
540## N-I dash stdout: status=127
541## N-I dash status: 0
542
543#### Operator Precedence
544echo $(( 1 + 2*3 - 8/2 ))
545## stdout: 3
546
547#### Exponentiation with **
548echo $(( 3 ** 0 ))
549echo $(( 3 ** 1 ))
550echo $(( 3 ** 2 ))
551## STDOUT:
5521
5533
5549
555## END
556## N-I dash stdout-json: ""
557## N-I dash status: 2
558## N-I mksh stdout-json: ""
559## N-I mksh status: 1
560
561#### Exponentiation operator has buggy precedence
562# NOTE: All shells agree on this, but R and Python give -9, which is more
563# mathematically correct.
564echo $(( -3 ** 2 ))
565## stdout: 9
566## N-I dash stdout-json: ""
567## N-I dash status: 2
568## N-I mksh stdout-json: ""
569## N-I mksh status: 1
570
571#### Negative exponent
572# bash explicitly disallows negative exponents!
573echo $(( 2**-1 * 5 ))
574## stdout-json: ""
575## status: 1
576## OK zsh stdout: 2.5
577## OK zsh status: 0
578## N-I dash stdout-json: ""
579## N-I dash status: 2
580
581#### Comment not allowed in the middle of multiline arithmetic
582echo $((
5831 +
5842 + \
5853
586))
587echo $((
5881 + 2 # not a comment
589))
590(( a = 3 + 4 # comment
591))
592echo [$a]
593## status: 1
594## STDOUT:
5956
596## END
597## OK dash/osh status: 2
598## OK bash STDOUT:
5996
600[]
601## END
602## OK bash status: 0
603
604#### Add integer to indexed array (a[0] decay)
605declare -a array=(1 2 3)
606echo $((array + 5))
607## status: 0
608## STDOUT:
6096
610## END
611## N-I dash status: 2
612## N-I dash stdout-json: ""
613## N-I mksh/zsh status: 1
614## N-I mksh/zsh stdout-json: ""
615
616#### Add integer to associative array (a[0] decay)
617typeset -A assoc
618assoc[0]=42
619echo $((assoc + 5))
620## status: 0
621## stdout: 47
622## BUG dash status: 0
623## BUG dash stdout: 5
624
625#### Double subscript
626a=(1 2 3)
627echo $(( a[1] ))
628echo $(( a[1][1] ))
629## status: 1
630## OK osh status: 2
631## STDOUT:
6322
633## END
634## N-I dash status: 2
635## N-I dash stdout-json: ""
636## OK zsh STDOUT:
6371
638## END
639
640#### result of ArithSub -- array[0] decay
641a=(4 5 6)
642echo declared
643b=$(( a ))
644echo $b
645
646## status: 0
647## STDOUT:
648declared
6494
650## END
651## N-I dash status: 2
652## N-I dash stdout-json: ""
653## N-I zsh status: 1
654## N-I zsh STDOUT:
655declared
656## END
657
658#### result of ArithSub -- assoc[0] decay
659declare -A A=(['foo']=bar ['spam']=eggs)
660echo declared
661b=$(( A ))
662echo $b
663
664## status: 0
665## STDOUT:
666declared
6670
668## END
669
670## N-I mksh status: 1
671## N-I mksh stdout-json: ""
672
673
674## N-I dash status: 2
675## N-I dash stdout-json: ""
676
677#### comma operator
678a=(4 5 6)
679
680# zsh and osh can't evaluate the array like that
681# which is consistent with their behavior on $(( a ))
682
683echo $(( a, last = a[2], 42 ))
684echo last=$last
685
686## status: 0
687## STDOUT:
68842
689last=6
690## END
691## N-I dash status: 2
692## N-I dash stdout-json: ""
693## N-I zsh status: 1
694## N-I zsh stdout-json: ""
695
696
697#### assignment with dynamic var name
698foo=bar
699echo $(( x$foo = 42 ))
700echo xbar=$xbar
701## STDOUT:
70242
703xbar=42
704## END
705
706#### array assignment with dynamic array name
707foo=bar
708echo $(( x$foo[5] = 42 ))
709echo 'xbar[5]='${xbar[5]}
710## STDOUT:
71142
712xbar[5]=42
713## END
714## BUG zsh STDOUT:
71542
716xbar[5]=
717## END
718## N-I dash status: 2
719## N-I dash stdout-json: ""
720
721#### unary assignment with dynamic var name
722foo=bar
723xbar=42
724echo $(( x$foo++ ))
725echo xbar=$xbar
726## STDOUT:
72742
728xbar=43
729## END
730## BUG dash status: 2
731## BUG dash stdout-json: ""
732
733#### unary array assignment with dynamic var name
734foo=bar
735xbar[5]=42
736echo $(( x$foo[5]++ ))
737echo 'xbar[5]='${xbar[5]}
738## STDOUT:
73942
740xbar[5]=43
741## END
742## BUG zsh STDOUT:
7430
744xbar[5]=42
745## END
746## N-I dash status: 2
747## N-I dash stdout-json: ""
748
749#### Dynamic parsing of arithmetic
750e=1+2
751echo $(( e + 3 ))
752[[ e -eq 3 ]] && echo true
753[ e -eq 3 ]
754echo status=$?
755## STDOUT:
7566
757true
758status=2
759## END
760## BUG mksh STDOUT:
7616
762true
763status=0
764## END
765## N-I dash status: 2
766## N-I dash stdout-json: ""
767
768#### Dynamic parsing on empty string
769a=''
770echo $(( a ))
771
772a2=' '
773echo $(( a2 ))
774## STDOUT:
7750
7760
777## END
778
779#### nested ternary (bug fix)
780echo $((1?2?3:4:5))
781## STDOUT:
7823
783## END
784
785#### 1 ? a=1 : b=2 ( bug fix)
786echo $((1 ? a=1 : 42 ))
787echo a=$a
788
789# this does NOT work
790#echo $((1 ? a=1 : b=2 ))
791
792## STDOUT:
7931
794a=1
795## END
796## BUG zsh stdout-json: ""
797## BUG zsh status: 1
798
799#### Invalid constant
800
801echo $((a + x42))
802echo status=$?
803
804# weird asymmetry -- the above is a syntax error, but this isn't
805$SH -c 'echo $((a + 42x))'
806echo status=$?
807
808# regression
809echo $((a + 42x))
810echo status=$?
811## status: 1
812## STDOUT:
8130
814status=0
815status=1
816## END
817## OK dash status: 2
818## OK dash STDOUT:
8190
820status=0
821status=2
822## END
823## BUG bash status: 0
824## BUG bash STDOUT:
8250
826status=0
827status=1
828status=1
829## END
830
831#### Negative numbers with integer division /
832
833echo $(( 10 / 3))
834echo $((-10 / 3))
835echo $(( 10 / -3))
836echo $((-10 / -3))
837
838echo ---
839
840a=20
841: $(( a /= 3 ))
842echo $a
843
844a=-20
845: $(( a /= 3 ))
846echo $a
847
848a=20
849: $(( a /= -3 ))
850echo $a
851
852a=-20
853: $(( a /= -3 ))
854echo $a
855
856## STDOUT:
8573
858-3
859-3
8603
861---
8626
863-6
864-6
8656
866## END
867
868#### Negative numbers with %
869
870echo $(( 10 % 3))
871echo $((-10 % 3))
872echo $(( 10 % -3))
873echo $((-10 % -3))
874
875## STDOUT:
8761
877-1
8781
879-1
880## END
881
882#### Negative numbers with bit shift
883
884echo $(( 5 << 1 ))
885echo $(( 5 << 0 ))
886$SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
887echo ---
888
889echo $(( 16 >> 1 ))
890echo $(( 16 >> 0 ))
891$SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
892$SH -c 'echo $(( 16 >> -2 ))' # also 0
893echo ---
894
895## STDOUT:
89610
8975
898---
8998
90016
901---
902## END
903
904## OK bash/dash/zsh STDOUT:
90510
9065
907-9223372036854775808
908---
9098
91016
9110
9120
913---
914## END
915
916## BUG mksh STDOUT:
91710
9185
919-2147483648
920---
9218
92216
9230
9240
925---
926## END
927
928#### undef[0]
929case $SH in dash) exit ;; esac
930
931echo ARITH $(( undef[0] ))
932echo status=$?
933echo
934
935(( undef[0] ))
936echo status=$?
937echo
938
939echo UNDEF ${undef[0]}
940echo status=$?
941
942## STDOUT:
943ARITH 0
944status=0
945
946status=1
947
948UNDEF
949status=0
950## END
951## N-I dash STDOUT:
952## END
953
954#### undef[0] with nounset
955case $SH in dash) exit ;; esac
956
957set -o nounset
958echo UNSET $(( undef[0] ))
959echo status=$?
960
961## status: 1
962## STDOUT:
963## END
964
965## N-I dash status: 0
966
967## BUG mksh/zsh status: 0
968## BUG mksh/zsh STDOUT:
969UNSET 0
970status=0
971## END
972
973## N-I dash STDOUT:
974## END
975
976#### s[0] with string abc
977case $SH in dash) exit ;; esac
978
979s='abc'
980echo abc $(( s[0] )) $(( s[1] ))
981echo status=$?
982echo
983
984(( s[0] ))
985echo status=$?
986echo
987
988## STDOUT:
989abc 0 0
990status=0
991
992status=1
993
994## END
995## N-I dash STDOUT:
996## END
997
998#### s[0] with string 42
999case $SH in dash) exit ;; esac
1000
1001s='42'
1002echo 42 $(( s[0] )) $(( s[1] ))
1003echo status=$?
1004
1005## STDOUT:
100642 42 0
1007status=0
1008## END
1009## N-I dash STDOUT:
1010## END
1011
1012## BUG zsh STDOUT:
101342 0 4
1014status=0
1015## END
1016
1017#### s[0] with string '12 34'
1018
1019s='12 34'
1020echo '12 34' $(( s[0] )) $(( s[1] ))
1021echo status=$?
1022
1023## status: 1
1024## STDOUT:
1025## END
1026
1027## OK dash status: 2
1028
1029## BUG zsh status: 0
1030## BUG zsh STDOUT:
103112 34 0 1
1032status=0
1033## END
1034
1035# bash prints an error, but doesn't fail
1036
1037## BUG bash status: 0
1038## BUG bash STDOUT:
1039status=1
1040## END