Operators and Expressions
Operators are the part of a language people assume they already know, which is exactly why they are worth a chapter. Most of tiko's behave as you expect. A handful do not, and each of those is a bug waiting in code that was written by assumption rather than by reading.
Everything in this chapter was measured against the compiler. Where tiko's grouping differs from the language you came from, the listing prints both readings so you can see which one won.
Arithmetic
'' arithmetic.tiko -- the operators, and the three that surprise people.
print "7 / 2 = " & ( 7 / 2 ) '' always a float
print "7 \ 2 = " & ( 7 \ 2 ) '' integer division
print "7 mod 2 = " & ( 7 mod 2 )
print
'' mod takes the sign of the LEFT operand: a remainder, not a modulus.
print "-7 mod 2 = " & ( -7 mod 2 )
print " 7 mod -2 = " & ( 7 mod -2 )
print
'' '^' is floating point even when both operands are whole numbers.
print "2 ^ 10 = " & ( 2 ^ 10 ) & _
" and it is " & sizeof( typeof( 2 ^ 10 ) ) & " bytes wide"
7 / 2 = 3.5
7 \ 2 = 3
7 mod 2 = 1
-7 mod 2 = -1
7 mod -2 = 1
2 ^ 10 = 1024 and it is 8 bytes wide
Three of those need naming.
/ is always floating point. 7 / 2 is 3.5 even though both operands are whole numbers.
Integer division is a separate operator, \, and it truncates: 7 \ 2 is 3.
mod is a remainder, not a modulus. It takes the sign of its left operand, so -7 mod 2 is
−1 rather than 1. When you want the mathematical modulus — always non-negative for a positive
divisor — std/numeric's FloorMod is the one to reach for. This matters the moment you use
mod to wrap an index that might be negative.
^ produces a float. 2 ^ 10 is 1024, but it is a float64 that happens to hold a whole
number, and it was computed in floating point. For integer powers where exactness matters,
std/numeric has IPow.
Precedence, and its four surprises
tiko's precedence table was established by compiling 484 operator pairs and reading what the compiler did with each. Four of the answers differ from what a C, Python or Java programmer expects, and this listing prints each one beside both possible groupings:
'' precedence.tiko -- four places the table is not what people expect.
dim as int32 a = 3, b = 0
'' 1. '^' groups to the LEFT. Most languages group it to the right.
print "2 ^ 3 ^ 2 = " & ( 2 ^ 3 ^ 2 )
print " (2^3)^2 = " & ( ( 2 ^ 3 ) ^ 2 )
print " 2^(3^2) = " & ( 2 ^ ( 3 ^ 2 ) )
print
'' 2. 'shl' and 'shr' bind TIGHTER than '+'.
print "1 shl 2 + 3 = " & ( 1 shl 2 + 3 )
print " (1 shl 2) + 3 = " & ( ( 1 shl 2 ) + 3 )
print " 1 shl (2 + 3) = " & ( 1 shl ( 2 + 3 ) )
print
print "not a = b = " & ( not a = b )
print " not (a = b) = " & ( not ( a = b ) )
print "not a = b = " & ( not a = b )
print " not (a = b) = " & ( not ( a = b ) )
print " (not a) = b = " & ( ( not a ) = b )
print
'' 4. 'andalso' and 'orelse' share one level: they group left to right.
print "true orelse false andalso false = " & _
( true orelse false andalso false )
print " (true orelse false) andalso false = " & _
( ( true orelse false ) andalso false )
2 ^ 3 ^ 2 = 64
(2^3)^2 = 64
2^(3^2) = 512
1 shl 2 + 3 = 7
(1 shl 2) + 3 = 7
1 shl (2 + 3) = 32
not a = b = -1
not (a = b) = -1
not a = b = -1
not (a = b) = -1
(not a) = b = 0
true orelse false andalso false = false
(true orelse false) andalso false = false
| Expression | tiko groups it | Many languages |
|---|---|---|
2 ^ 3 ^ 2 | (2 ^ 3) ^ 2 = 64 | right, giving 512 |
1 shl 2 + 3 | (1 shl 2) + 3 = 7 | + first, giving 32 |
not a = b | not (a = b) | (not a) = b |
x orelse y andalso z | left to right | andalso first |
not is looser than the comparisons. not a = b asks "is it not the case that a equals b",
which is usually what was meant — but it is the opposite of C, where !a == b compares !a
against b.
andalso and orelse share one precedence level, so they group strictly left to right. In C,
Python and nearly everything else, and binds tighter than or, so x or y and z means
x or (y and z). Here it means (x orelse y) andalso z, which is a different answer.
Both are cases where code that looks right and compiles clean can be wrong. Parenthesise any
mixed andalso/orelse expression, every time.
Two families of logical operator
'' logic.tiko -- two families of operator that look alike and are not.
declare function Loud( byval v as boolean ) as boolean
function Loud( byval v as boolean ) as boolean
print " (Loud was called)"
return v
end function
print "andalso stops once the answer is known:"
if false andalso Loud( true ) then print "unreachable"
print "and always evaluates both:"
if false and Loud( true ) then print "unreachable"
print
'' On integers the bitwise family works bit by bit.
print "12 and 10 = " & ( 12 and 10 )
print "12 or 10 = " & ( 12 or 10 )
print "12 xor 10 = " & ( 12 xor 10 )
print "not 12 = " & ( not 12 )
andalso stops once the answer is known:
and always evaluates both:
(Loud was called)
12 and 10 = 8
12 or 10 = 14
12 xor 10 = 6
not 12 = -13
andalso and orelse short-circuit: once the answer is known the right-hand side is never
evaluated. and, or, xor and not do not — they are bitwise operators that also serve for
logic, and they always evaluate both sides.
The difference matters whenever the right-hand side has an effect, or would fail:
if p <> 0 andalso p->count > 0is safe.if p <> 0 and p->count > 0dereferences a null pointer.
Use andalso and orelse for conditions, and reserve and/or/xor/not for bits.
tiko refuses to mix the two families silently. Combining a boolean and a non-boolean in an
andalso chain draws a warning, because the result is almost never what was meant — hoist the
value into a boolean first.
Working with bits
'' bits.tiko -- shifting, masking, and the single-bit helpers.
dim as uint32 flags = 0
flags = bitset( flags, 0 ) '' turn bit 0 on
flags = bitset( flags, 3 )
print "flags = " & flags & " (binary " & bin( flags ) & ")"
print "bit 3 set? = " & cbool( bit( flags, 3 ) <> 0 )
flags = bitreset( flags, 3 )
print "after reset = " & bin( flags )
print
print "1 shl 8 = " & ( 1 shl 8 )
print "256 shr 4 = " & ( 256 shr 4 )
print
'' Picking halves out of a wider value.
dim as uint32 packed = &hDEADBEEF
print "packed = " & hex( packed )
print "low word = " & hex( loword( packed ) )
print "high word = " & hex( hiword( packed ) )
print "low byte = " & hex( lobyte( packed ) )
flags = 9 (binary 1001)
bit 3 set? = true
after reset = 1
1 shl 8 = 256
256 shr 4 = 16
packed = DEADBEEF
low word = BEEF
high word = DEAD
low byte = EF
shl and shr shift; bitset, bitreset and bit turn one bit on, off, or read it; lobyte,
hibyte, loword and hiword pull a piece out of a wider value. bin( ) and hex( ) print a
value in a base where the bits are visible, which is most of the battle when debugging a mask.
Remember that shifts bind tighter than arithmetic. 1 shl n + 1 shifts by n and then adds — not
what the spacing suggests.
Joining, choosing, and shorthand
'' expressions.tiko -- joining text, choosing a value, and shorthand.
dim as ustring who = "world"
'' '&' joins text. '+' also adds numbers, so '&' always means join.
print "hello, " & who
print "1 & 2 gives the text " & ( 1 & 2 )
print "1 + 2 gives the number " & ( 1 + 2 )
print
'' iif picks between two values inside an expression.
dim as int32 stock = 0
print "in stock: " & iif( stock > 0, "yes", "no" )
print
'' Compound assignment, and swapping two variables.
dim as int32 total = 10
total += 5 : total *= 2
print "total = " & total
dim as ustring left_ = "first", right_ = "second"
swap left_, right_
print left_ & " " & right_
hello, world
1 & 2 gives the text 12
1 + 2 gives the number 3
in stock: no
total = 30
second first
& joins text; + adds numbers. + will also join two strings, which is exactly why the
book always uses &: 1 & 2 is the text 12, while 1 + 2 is the number 3, and & never has
to be guessed at.
iif( condition, a, b ) chooses between two values inside an expression, where an if
statement would need several lines. Both arms are evaluated, so do not use it where one of them
has an effect.
Compound assignment — +=, -=, *=, /=, \=, &= — and swap a, b, which exchanges
two variables of the same type without a temporary.
Summary
/is floating point,\is integer division, and^produces afloat64.modis a remainder and takes its left operand's sign;FloorModinstd/numericis the mathematical one.^groups to the left, andshl/shrbind tighter than+.notis looser than the comparisons, andandalso/orelseshare one level, grouping left to right — parenthesise mixed conditions.andalso/orelseshort-circuit;and/or/xor/notare bitwise and always evaluate both.&joins text and never means anything else;iifchooses within an expression;swapexchanges two variables.
Exercises
- Build a permissions value from three named bit positions, then read one back. Do it twice —
once with
bitset/bit, once with1 shl nmasks — and check the two agree. {solution="ex05-01"} - Write expressions where parentheses change the answer, and one where they only make the existing grouping obvious. Which of the four surprises above would have bitten you? {solution="ex05-02"}
7 mod -2and-7 mod 2give different signs. Predict both before running them, then work out whatFloorModwould give instead.- Rewrite
if p <> 0 and p->count > 0so it is safe, and explain in one sentence what the original does whenpis null.