Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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
Expressiontiko groups itMany languages
2 ^ 3 ^ 2(2 ^ 3) ^ 2 = 64right, giving 512
1 shl 2 + 3(1 shl 2) + 3 = 7+ first, giving 32
not a = bnot (a = b)(not a) = b
x orelse y andalso zleft to rightandalso first

Pitfall

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 > 0 is safe.
  • if p <> 0 and p->count > 0 dereferences a null pointer.

Use andalso and orelse for conditions, and reserve and/or/xor/not for bits.

Note

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 a float64.
  • mod is a remainder and takes its left operand's sign; FloorMod in std/numeric is the mathematical one.
  • ^ groups to the left, and shl/shr bind tighter than +.
  • not is looser than the comparisons, and andalso/orelse share one level, grouping left to right — parenthesise mixed conditions.
  • andalso/orelse short-circuit; and/or/xor/not are bitwise and always evaluate both.
  • & joins text and never means anything else; iif chooses within an expression; swap exchanges two variables.

Exercises

  1. Build a permissions value from three named bit positions, then read one back. Do it twice — once with bitset/bit, once with 1 shl n masks — and check the two agree. {solution="ex05-01"}
  2. 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"}
  3. 7 mod -2 and -7 mod 2 give different signs. Predict both before running them, then work out what FloorMod would give instead.
  4. Rewrite if p <> 0 and p->count > 0 so it is safe, and explain in one sentence what the original does when p is null.