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

1044 lines, 478 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#### Hex constant with capital X
393echo $(( 0XAA ))
394## stdout: 170
395
396#### Dynamic var names - result of runtime parse/eval
397foo=5
398x=oo
399echo $(( foo + f$x + 1 ))
400## stdout: 11
401
402#### Recursive name evaluation is a result of runtime parse/eval
403foo=5
404bar=foo
405spam=bar
406eggs=spam
407echo $((foo+1)) $((bar+1)) $((spam+1)) $((eggs+1))
408## stdout: 6 6 6 6
409## N-I dash stdout-json: ""
410## N-I dash status: 2
411
412#### nounset with arithmetic
413set -o nounset
414x=$(( y + 5 ))
415echo "should not get here: x=${x:-<unset>}"
416## stdout-json: ""
417## status: 1
418## BUG dash/mksh/zsh stdout: should not get here: x=5
419## BUG dash/mksh/zsh status: 0
420
421#### 64-bit integer doesn't overflow
422
423a=$(( 1 << 31 ))
424echo $a
425
426b=$(( a + a ))
427echo $b
428
429c=$(( b + a ))
430echo $c
431
432x=$(( 1 << 62 ))
433y=$(( x - 1 ))
434echo "max positive = $(( x + y ))"
435
436#echo "overflow $(( x + x ))"
437
438## STDOUT:
4392147483648
4404294967296
4416442450944
442max positive = 9223372036854775807
443## END
444
445# mksh still uses int!
446## BUG mksh STDOUT:
447-2147483648
4480
449-2147483648
450max positive = 2147483647
451## END
452
453#### More 64-bit ops
454case $SH in dash) exit ;; esac
455
456#shopt -s strict_arith
457
458# This overflows - the extra 9 puts it above 2**31
459#echo $(( 12345678909 ))
460
461[[ 12345678909 = $(( 1 << 30 )) ]]
462echo eq=$?
463[[ 12345678909 = 12345678909 ]]
464echo eq=$?
465
466# Try both [ and [[
467[ 12345678909 -gt $(( 1 << 30 )) ]
468echo greater=$?
469[[ 12345678909 -gt $(( 1 << 30 )) ]]
470echo greater=$?
471
472[[ 12345678909 -ge $(( 1 << 30 )) ]]
473echo ge=$?
474[[ 12345678909 -ge 12345678909 ]]
475echo ge=$?
476
477[[ 12345678909 -le $(( 1 << 30 )) ]]
478echo le=$?
479[[ 12345678909 -le 12345678909 ]]
480echo le=$?
481
482## STDOUT:
483eq=1
484eq=0
485greater=0
486greater=0
487ge=0
488ge=0
489le=1
490le=0
491## END
492## N-I dash STDOUT:
493## END
494## BUG mksh STDOUT:
495eq=1
496eq=0
497greater=1
498greater=1
499ge=1
500ge=0
501le=0
502le=0
503## END
504
505# mksh still uses int!
506
507#### Invalid LValue
508a=9
509(( (a + 2) = 3 ))
510echo $a
511## status: 2
512## stdout-json: ""
513## OK bash/mksh/zsh stdout: 9
514## OK bash/mksh/zsh status: 0
515# dash doesn't implement assignment
516## N-I dash status: 2
517## N-I dash stdout-json: ""
518
519#### Invalid LValue that looks like array
520(( 1[2] = 3 ))
521echo "status=$?"
522## status: 1
523## stdout-json: ""
524
525## OK bash stdout: status=1
526## OK bash status: 0
527
528## OK mksh/zsh stdout: status=2
529## OK mksh/zsh status: 0
530
531## N-I dash stdout: status=127
532## N-I dash status: 0
533
534#### Invalid LValue: two sets of brackets
535(( a[1][2] = 3 ))
536echo "status=$?"
537# shells treat this as a NON-fatal error
538## status: 2
539## stdout-json: ""
540## OK bash stdout: status=1
541## OK mksh/zsh stdout: status=2
542## OK bash/mksh/zsh status: 0
543# dash doesn't implement assignment
544## N-I dash stdout: status=127
545## N-I dash status: 0
546
547#### Operator Precedence
548echo $(( 1 + 2*3 - 8/2 ))
549## stdout: 3
550
551#### Exponentiation with **
552echo $(( 3 ** 0 ))
553echo $(( 3 ** 1 ))
554echo $(( 3 ** 2 ))
555## STDOUT:
5561
5573
5589
559## END
560## N-I dash stdout-json: ""
561## N-I dash status: 2
562## N-I mksh stdout-json: ""
563## N-I mksh status: 1
564
565#### Exponentiation operator has buggy precedence
566# NOTE: All shells agree on this, but R and Python give -9, which is more
567# mathematically correct.
568echo $(( -3 ** 2 ))
569## stdout: 9
570## N-I dash stdout-json: ""
571## N-I dash status: 2
572## N-I mksh stdout-json: ""
573## N-I mksh status: 1
574
575#### Negative exponent
576# bash explicitly disallows negative exponents!
577echo $(( 2**-1 * 5 ))
578## stdout-json: ""
579## status: 1
580## OK zsh stdout: 2.5
581## OK zsh status: 0
582## N-I dash stdout-json: ""
583## N-I dash status: 2
584
585#### Comment not allowed in the middle of multiline arithmetic
586echo $((
5871 +
5882 + \
5893
590))
591echo $((
5921 + 2 # not a comment
593))
594(( a = 3 + 4 # comment
595))
596echo [$a]
597## status: 1
598## STDOUT:
5996
600## END
601## OK dash/osh status: 2
602## OK bash STDOUT:
6036
604[]
605## END
606## OK bash status: 0
607
608#### Add integer to indexed array (a[0] decay)
609declare -a array=(1 2 3)
610echo $((array + 5))
611## status: 0
612## STDOUT:
6136
614## END
615## N-I dash status: 2
616## N-I dash stdout-json: ""
617## N-I mksh/zsh status: 1
618## N-I mksh/zsh stdout-json: ""
619
620#### Add integer to associative array (a[0] decay)
621typeset -A assoc
622assoc[0]=42
623echo $((assoc + 5))
624## status: 0
625## stdout: 47
626## BUG dash status: 0
627## BUG dash stdout: 5
628
629#### Double subscript
630a=(1 2 3)
631echo $(( a[1] ))
632echo $(( a[1][1] ))
633## status: 1
634## OK osh status: 2
635## STDOUT:
6362
637## END
638## N-I dash status: 2
639## N-I dash stdout-json: ""
640## OK zsh STDOUT:
6411
642## END
643
644#### result of ArithSub -- array[0] decay
645a=(4 5 6)
646echo declared
647b=$(( a ))
648echo $b
649
650## status: 0
651## STDOUT:
652declared
6534
654## END
655## N-I dash status: 2
656## N-I dash stdout-json: ""
657## N-I zsh status: 1
658## N-I zsh STDOUT:
659declared
660## END
661
662#### result of ArithSub -- assoc[0] decay
663declare -A A=(['foo']=bar ['spam']=eggs)
664echo declared
665b=$(( A ))
666echo $b
667
668## status: 0
669## STDOUT:
670declared
6710
672## END
673
674## N-I mksh status: 1
675## N-I mksh stdout-json: ""
676
677
678## N-I dash status: 2
679## N-I dash stdout-json: ""
680
681#### comma operator
682a=(4 5 6)
683
684# zsh and osh can't evaluate the array like that
685# which is consistent with their behavior on $(( a ))
686
687echo $(( a, last = a[2], 42 ))
688echo last=$last
689
690## status: 0
691## STDOUT:
69242
693last=6
694## END
695## N-I dash status: 2
696## N-I dash stdout-json: ""
697## N-I zsh status: 1
698## N-I zsh stdout-json: ""
699
700
701#### assignment with dynamic var name
702foo=bar
703echo $(( x$foo = 42 ))
704echo xbar=$xbar
705## STDOUT:
70642
707xbar=42
708## END
709
710#### array assignment with dynamic array name
711foo=bar
712echo $(( x$foo[5] = 42 ))
713echo 'xbar[5]='${xbar[5]}
714## STDOUT:
71542
716xbar[5]=42
717## END
718## BUG zsh STDOUT:
71942
720xbar[5]=
721## END
722## N-I dash status: 2
723## N-I dash stdout-json: ""
724
725#### unary assignment with dynamic var name
726foo=bar
727xbar=42
728echo $(( x$foo++ ))
729echo xbar=$xbar
730## STDOUT:
73142
732xbar=43
733## END
734## BUG dash status: 2
735## BUG dash stdout-json: ""
736
737#### unary array assignment with dynamic var name
738foo=bar
739xbar[5]=42
740echo $(( x$foo[5]++ ))
741echo 'xbar[5]='${xbar[5]}
742## STDOUT:
74342
744xbar[5]=43
745## END
746## BUG zsh STDOUT:
7470
748xbar[5]=42
749## END
750## N-I dash status: 2
751## N-I dash stdout-json: ""
752
753#### Dynamic parsing of arithmetic
754e=1+2
755echo $(( e + 3 ))
756[[ e -eq 3 ]] && echo true
757[ e -eq 3 ]
758echo status=$?
759## STDOUT:
7606
761true
762status=2
763## END
764## BUG mksh STDOUT:
7656
766true
767status=0
768## END
769## N-I dash status: 2
770## N-I dash stdout-json: ""
771
772#### Dynamic parsing on empty string
773a=''
774echo $(( a ))
775
776a2=' '
777echo $(( a2 ))
778## STDOUT:
7790
7800
781## END
782
783#### nested ternary (bug fix)
784echo $((1?2?3:4:5))
785## STDOUT:
7863
787## END
788
789#### 1 ? a=1 : b=2 ( bug fix)
790echo $((1 ? a=1 : 42 ))
791echo a=$a
792
793# this does NOT work
794#echo $((1 ? a=1 : b=2 ))
795
796## STDOUT:
7971
798a=1
799## END
800## BUG zsh stdout-json: ""
801## BUG zsh status: 1
802
803#### Invalid constant
804
805echo $((a + x42))
806echo status=$?
807
808# weird asymmetry -- the above is a syntax error, but this isn't
809$SH -c 'echo $((a + 42x))'
810echo status=$?
811
812# regression
813echo $((a + 42x))
814echo status=$?
815## status: 1
816## STDOUT:
8170
818status=0
819status=1
820## END
821## OK dash status: 2
822## OK dash STDOUT:
8230
824status=0
825status=2
826## END
827## BUG bash status: 0
828## BUG bash STDOUT:
8290
830status=0
831status=1
832status=1
833## END
834
835#### Negative numbers with integer division /
836
837echo $(( 10 / 3))
838echo $((-10 / 3))
839echo $(( 10 / -3))
840echo $((-10 / -3))
841
842echo ---
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
856a=-20
857: $(( a /= -3 ))
858echo $a
859
860## STDOUT:
8613
862-3
863-3
8643
865---
8666
867-6
868-6
8696
870## END
871
872#### Negative numbers with %
873
874echo $(( 10 % 3))
875echo $((-10 % 3))
876echo $(( 10 % -3))
877echo $((-10 % -3))
878
879## STDOUT:
8801
881-1
8821
883-1
884## END
885
886#### Negative numbers with bit shift
887
888echo $(( 5 << 1 ))
889echo $(( 5 << 0 ))
890$SH -c 'echo $(( 5 << -1 ))' # implementation defined - OSH fails
891echo ---
892
893echo $(( 16 >> 1 ))
894echo $(( 16 >> 0 ))
895$SH -c 'echo $(( 16 >> -1 ))' # not sure why this is zero
896$SH -c 'echo $(( 16 >> -2 ))' # also 0
897echo ---
898
899## STDOUT:
90010
9015
902---
9038
90416
905---
906## END
907
908## OK bash/dash/zsh STDOUT:
90910
9105
911-9223372036854775808
912---
9138
91416
9150
9160
917---
918## END
919
920## BUG mksh STDOUT:
92110
9225
923-2147483648
924---
9258
92616
9270
9280
929---
930## END
931
932#### undef[0]
933case $SH in dash) exit ;; esac
934
935echo ARITH $(( undef[0] ))
936echo status=$?
937echo
938
939(( undef[0] ))
940echo status=$?
941echo
942
943echo UNDEF ${undef[0]}
944echo status=$?
945
946## STDOUT:
947ARITH 0
948status=0
949
950status=1
951
952UNDEF
953status=0
954## END
955## N-I dash STDOUT:
956## END
957
958#### undef[0] with nounset
959case $SH in dash) exit ;; esac
960
961set -o nounset
962echo UNSET $(( undef[0] ))
963echo status=$?
964
965## status: 1
966## STDOUT:
967## END
968
969## N-I dash status: 0
970
971## BUG mksh/zsh status: 0
972## BUG mksh/zsh STDOUT:
973UNSET 0
974status=0
975## END
976
977## N-I dash STDOUT:
978## END
979
980#### s[0] with string abc
981case $SH in dash) exit ;; esac
982
983s='abc'
984echo abc $(( s[0] )) $(( s[1] ))
985echo status=$?
986echo
987
988(( s[0] ))
989echo status=$?
990echo
991
992## STDOUT:
993abc 0 0
994status=0
995
996status=1
997
998## END
999## N-I dash STDOUT:
1000## END
1001
1002#### s[0] with string 42
1003case $SH in dash) exit ;; esac
1004
1005s='42'
1006echo 42 $(( s[0] )) $(( s[1] ))
1007echo status=$?
1008
1009## STDOUT:
101042 42 0
1011status=0
1012## END
1013## N-I dash STDOUT:
1014## END
1015
1016## BUG zsh STDOUT:
101742 0 4
1018status=0
1019## END
1020
1021#### s[0] with string '12 34'
1022
1023s='12 34'
1024echo '12 34' $(( s[0] )) $(( s[1] ))
1025echo status=$?
1026
1027## status: 1
1028## STDOUT:
1029## END
1030
1031## OK dash status: 2
1032
1033## BUG zsh status: 0
1034## BUG zsh STDOUT:
103512 34 0 1
1036status=0
1037## END
1038
1039# bash prints an error, but doesn't fail
1040
1041## BUG bash status: 0
1042## BUG bash STDOUT:
1043status=1
1044## END