Skip to content

Variables, types and expressions

Booleans, integers, floats and strings; inputs, outputs and locals; initial values, operators, assignments and arrays.

The inputs and outputs of the charts so far have been variables all along. This chapter covers the whole set: the four types, the three places a variable can live, how variables get their first values, and the operators and assignments you can use, up to arrays. SCCharts expressions look like C, and KIELER runs your chart as C code, so they mostly behave like C too. Where that gives an answer you might not expect, this chapter says so; the strangest cases are collected at the end, in When the C code shows through.

Tank adds the input flow to volume in every tick until the tank is full, then stops. It uses every kind of declaration in this chapter: an input, two outputs, a constant and a local variable.

TankSimulation

Diagram

Diagram of TankStates: Filling (initial), Full (final). Transitions: Filling to Full: 1: volume + flow >= CAPACITY / volume = CAPACITY; full = true; Filling to Filling: 2: / volume += flow; fills += 1.Tankinput intflowoutput intvolume = 0output boolfull = falseconst intCAPACITY = 500intfillsFillingFull1: volume + flow >= CAPACITY/ volume = CAPACITY;full = true2: / volume += flow;fills += 1

Code

scchart Tank {
input int flow
output int volume = 0
output bool full = false
const int CAPACITY = 500
int fills
initial state Filling
if volume + flow >= CAPACITY do volume = CAPACITY; full = true go to Full
do volume += flow; fills += 1 go to Filling
final state Full
}
Tick 0 of 6
Inputs for tick 1
Timing diagram of Tank
Tickinput int flowoutput int volumeoutput bool fulllocal int fillsActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 6 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 6 ticks:

  1. flow = 100Tick 1 enters the chart: volume and full get their initial values, and the local fills, which has none, starts at 0. CAPACITY is a constant, so it is not in the list of values at all. Both transitions are delayed, so nothing else happens.
  2. flow is still 100. volume + flow is 100, below CAPACITY, so priority 1 fails and the self-loop adds flow: volume is 100 and fills is 1.
  3. flow = 150flow is 150 now: volume is 250, fills 2.
  4. flow keeps its value, 150: volume is 400, fills 3.
  5. volume + flow would be 550, which reaches CAPACITY, so priority 1 fires: volume is set to 500, full to true, and the chart enters the final state Full and terminates.
  6. The chart has terminated: nothing changes any more.
flow keeps its value until it is changed; fills is a local that counts how often flow was added.

Each declaration names a type and one or more variables, optionally with an initial value:

input int flow // set by the environment
output int volume = 0 // written by the chart, visible outside
output bool full = false
const int CAPACITY = 500 // a fixed value, see Constants below
int fills // a local variable: no input or output

In every tick until it is full, Tank first checks whether this tick’s flow would reach CAPACITY (priority 1). If not, the self-loop adds flow to volume. In tick 5, 400 + 150 would be 550, so the chart sets volume to exactly 500, sets full, enters its final state and terminates.

An input is written by the environment: the device, the test bench, you in the simulator. An output is written by the chart and can be read from outside. A variable declared with neither keyword is a local: the chart’s own working memory. All three keep their value from tick to tick until something assigns them. In KIELER’s simulator the locals declared at the top of a chart are listed along with the inputs and outputs, so you can watch them; locals declared inside states come in Superstates.

You may declare several variables of one type at once, each with its own initial value if you like: input int a, b or output int low = 0, high = 100.

A real environment supplies every input in every tick. KIELER’s simulator, SCCharts Lab and this guide make that easier: an input you set keeps its value until you change it. That is why flow stays 150 in ticks 4 and 5 of Tank, and why the guided tours list an input only in the ticks in which it changes. Think of inputs as switches, not buttons. If you want something to be true for one tick only, you have to set it back yourself.

A chart may even assign its own inputs; the value lasts until the environment supplies a new one. It is legal, but it makes charts hard to follow. Copy the input into a local instead.

Type Holds Literals Default
bool true or false true, false false
int a 32-bit whole number 0, 42, 2147483647 0
float a number with a fraction (a C double) 1.5, 2., 1.5e3, 1.5f 0.0
string text "pump" null (no value)

A few details of the literals. int literals are decimal only (no hex), and one above 2147483647 is rejected (For input string: "3000000000"). There are no negative literals: -7 is the minus operator applied to 7, which matters in the conditional. An exponent needs a decimal point before it: 1e3 is a syntax error (no viable alternative at input 'e3'); write 1.0e3 or 1000.0.

SCCharts also has enums, structs and classes, for named values and for data that belongs together. They come later, in Enums, structs and classes.

bool and int mix freely, as in C. true + 1 is 2, a comparison gives 0 or 1, and an int can stand on its own as a trigger, meaning “not zero”: if n go to … fires for −3 as well as for 1. Keep bools for conditions and do arithmetic in ints, though: a bool is really a small number, which can surprise you.

A variable without an initial value starts with its type’s default from the table above: 0, false, 0.0. An unset string is null, no value at all, and KIELER leaves it out of the simulation’s values altogether.

An initial value is assigned when the chart is entered, in tick 1, and never again. (A variable declared inside a state gets its initial value each time that state is entered; see Superstates.) The initial values run in the order you declared them, and they can read the inputs of tick 1. Calibrate uses that to remember its first reading as the zero point:

CalibrateSimulation

Diagram

Diagram of CalibrateStates: Measure (initial). Transitions: Measure to Measure: / offset = reading - zero.Calibrateinput intreadingoutput intzero = readingoutput intoffset = 0Measure/ offset = reading - zero

Code

scchart Calibrate {
input int reading
output int zero = reading
output int offset = 0
initial state Measure
do offset = reading - zero go to Measure
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of Calibrate
Tickinput int readingoutput int zerooutput int offsetActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. reading = 20Tick 1 runs the initial values in declaration order: zero = reading reads this tick’s input, so zero is 20. The self-loop is delayed, so offset keeps its initial value.
  2. reading is still 20: offset is 0.
  3. reading = 23offset is 23 − 20 = 3. zero is still 20: an initial value is assigned once, when its scope is entered, and does not follow reading.
  4. reading = 18offset is −2.
  5. reading keeps its value, 18, so offset stays −2.
zero takes reading from tick 1 and keeps it; offset follows reading from tick 2.
Tick reading zero after offset after
1 20 20 0
2 20 (held) 20 0
3 23 20 3
4 18 20 −2
5 18 (held) 20 −2

So output int zero = reading is not a definition that holds for ever, as it would be in a spreadsheet: it is an assignment that runs once. Because the initial values run in order, one may use another declared above it, but not one declared below it. Here early is 1, not 6, because later still has its default 0 when early is computed:

output int early = later + 1
output int later = 5

There is one exception in KIELER, when an initial value builds on another one that reads an input: see Tick 1 runs input-free initial values first.

const makes a name for a fixed value. A constant is replaced by its value when the chart is compiled, so it never appears among the simulator’s values: the Tank simulation lists no CAPACITY. A constant needs an initial value; without one, KIELER says:

A const declaration must have an initialization part!

A constant cannot be assigned either: do CAPACITY = 600 gives You cannot assign a value to a const object. If a value has to change while the chart runs, it is a variable. Constants are also the only names allowed as array sizes, which come up at the end of this chapter.

From the loosest binding to the tightest:

Operators Meaning
|| or (stops at the first true operand)
&& and (stops at the first false operand)
|, ^, & bitwise or, exclusive or, and (in that order)
== != < <= > >= comparisons, at most one in a row
<< >> shifts
+ - addition, subtraction
* / % multiplication, division, remainder
-x, !x, ~x negation, not, bitwise not
c ? a : b conditional: a if c holds, otherwise b

Two things in this table differ from what you may know from other languages. The conditional is at the bottom, binding tighter than everything else; that is a trap, and it gets its own section next. And, as in C, & binds more loosely than ==, so a & 3 == 2 means a & (3 == 2). Write (a & 3) == 2. Operators has the complete table, with every quirk of KIELER’s grammar.

If you use SCCharts Lab, its Watch expressions (the top line of the trace) are a quick way to try an operator: each one is evaluated after every tick, in the chart’s own syntax.

The conditional operator binds tighter than every other operator, and its three parts must each be a single value: a name, a number, a call, or something in parentheses. So Clamp below does not cap speed at 10. It reads as speed > (10 ? 10 : speed); 10 ? 10 : speed is 10, because 10 is not zero, so the whole thing is speed > 10, which is 0 or 1. ClampFixed adds the parentheses.

Clamp and ClampFixedComparison

Diagrams

Clamp

Diagram of ClampStates: Show (initial). Transitions: Show to Show: / shown = speed > (10 ? 10 : speed).Clampinput intspeedoutput intshown = 0Show/ shown = speed > (10 ? 10 : speed)

ClampFixed

Diagram of ClampFixedStates: Show (initial). Transitions: Show to Show: / shown = speed > 10 ? 10 : speed.ClampFixedinput intspeedoutput intshown = 0Show/ shown = speed > 10 ? 10 : speed

Code

Clamp

scchart Clamp {
input int speed
output int shown = 0
initial state Show
do shown = speed > 10 ? 10 : speed go to Show
}

ClampFixed

scchart ClampFixed {
input int speed
output int shown = 0
initial state Show
do shown = (speed > 10) ? 10 : speed go to Show
}
Tick 0 of 5
Inputs for tick 1, for both charts
Timing diagram of Clamp and ClampFixed, no ticks yet
Tickinput int speedClamp: output int shownClamp: Active state of the chartClampFixed: output int shownClampFixed: Active state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Both charts get the same inputs. Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks, the same inputs for both charts:

  1. speed = 4Tick 1 enters Show in both charts; their self-loops are delayed, so shown keeps its initial 0.
  2. speed is 4. ClampFixed shows 4, as intended. Clamp reads speed > 10 ? 10 : speed as speed > (10 ? 10 : speed), which is speed > 10: false, stored as 0.
  3. speed = 25speed is 25. ClampFixed caps it at 10. In Clamp, speed > 10 is true, stored as 1.
  4. speed = 1010 is not above 10: ClampFixed shows 10, Clamp 0.
  5. speed = 11ClampFixed caps 11 at 10; Clamp shows 1. It only ever shows 0 or 1.
Note: KIELER reads this as speed > (10 ? 10 : speed) (Clamp)
  • Note in Clamp, line 6, column 16The condition of ? : is a single value, so the comparison is not part of it. Write (speed > 10) ? ... with parentheses.
One pair of parentheses apart: Clamp shows 0 or 1, ClampFixed caps speed at 10.

Don’t trust the diagrams here: KIELER prints a label with its own choice of parentheses, not yours. Clamp’s label shows how KIELER reads it, speed > (10 ? 10 : speed), but ClampFixed’s label reads speed > 10 ? 10 : speed, which is Clamp’s code. When precedence matters, read the code.

More traps of the same kind:

  • b ? 10 : 20 + 1 means (b ? 10 : 20) + 1, and 2 * b ? 3 : 4 means 2 * (b ? 3 : 4).
  • b ? -1 : 2 and !b ? 1 : 2 are rejected with no viable alternative at input …. Write b ? (-1) : 2 and (!b) ? 1 : 2.
  • Comparisons don’t chain: 1 < x < 3 fails with no viable alternative at input '<'. Write x > 1 && x < 3, with the name first: in a trigger, if 1 < x … would read the 1 as a count delay and fail too.
  • ! and ~ take a single value: !b + 1 fails with no viable alternative at input '+'. Write (!b) + 1.
  • ++ is an effect of its own, not part of an expression: x = i++ fails with no viable alternative at input '++'. Write x = i; i++.

Each of them is a button here. Type your own expression or effect to see how KIELER groups it:

How KIELER reads it
Read it as

KIELER reads this expression as

speed > (10 ? 10 : speed)

As a tree, each operation with its operands under it:

  • >greater than
    • speed
    • ?:conditional
      • 10
      • 10
      • speed
KIELER’s reading of an expression or an effect, fully parenthesised and as a tree, or the syntax error it reports.

The habit that avoids all of it: put the condition, and every part that is more than a single name or number, in parentheses.

Divide divides a by b in every tick from tick 2 on, but only when b is not zero.

DivideSimulation

Diagram

Diagram of DivideStates: Calc (initial). Transitions: Calc to Calc: b != 0 / quotient = a / b; remainder = a % b.Divideinput inta, boutput intquotient = 0, remainder = 0Calcb != 0/ quotient = a / b;remainder = a % b

Code

scchart Divide {
input int a, b
output int quotient = 0, remainder = 0
initial state Calc
if b != 0 do quotient = a / b; remainder = a % b go to Calc
}
Tick 0 of 6
Inputs for tick 1
Timing diagram of Divide
Tickinput int ainput int boutput int quotientoutput int remainderActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 6 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 6 ticks:

  1. a = 7, b = 2Tick 1 enters Calc; the self-loop is delayed, so nothing is computed yet.
  2. 7 / 2 is 3 and 7 % 2 is 1, as in C: integer division throws away the fraction.
  3. a = -7−7 / 2 is −3, not −4: division truncates towards zero. The remainder takes the sign of the dividend: −7 % 2 is −1.
  4. b = -2−7 / −2 is 3, and −7 % −2 is −1: the remainder’s sign follows a, never b.
  5. a = 77 / −2 is −3 and 7 % −2 is 1.
  6. b = 0b is 0, so the trigger b != 0 is false and the outputs keep their values. Without that guard this tick would divide by zero.
Integer division truncates towards zero, and the remainder takes the sign of a.
Tick a b quotient after remainder after
2 7 (held) 2 (held) 3 1
3 −7 2 (held) −3 −1
4 −7 (held) −2 3 −1
5 7 −2 (held) −3 1
6 7 (held) 0 −3 (kept) 1 (kept)

int division throws the fraction away, rounding towards zero, so −7 / 2 is −3, not −4. The remainder has the sign of the dividend. An int is 32 bits and wraps round on overflow: 2147483647 + 1 is −2147483648. And int / int is integer division even when the result is stored in a float: write a / 2.0 when you want the fraction.

A float is a C double, with one twist: KIELER rounds every float literal in your chart to single precision before it reaches the C code. 0.1 in a chart is 0.10000000149011612, while a value you type into the simulator keeps full precision.

FloatsSimulation

Diagram

Diagram of FloatsStates: Calc (initial). Transitions: Calc to Calc: / equal = f == tenth; whole = f * 10.Floatsinput floatfoutput floattenth = 0.1output boolequal = falseoutput intwhole = 0Calc/ equal = f == tenth;whole = f * 10

Code

scchart Floats {
input float f
output float tenth = 0.1
output bool equal = false
output int whole = 0
initial state Calc
do equal = f == tenth; whole = f * 10 go to Calc
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of Floats
Tickinput float foutput float tenthoutput bool equaloutput int wholeActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. f = 0.1Tick 1: tenth is initialised from the literal 0.1, which KIELER rounds to single precision: 0.10000000149011612.
  2. The input f is exactly the double 0.1, so f == tenth is false although both were written as 0.1. whole = f * 10 stores 1.0 in an int: 1.
  3. f = 0.79f * 10 is 7.9; storing it in an int cuts off the fraction: 7.
  4. f = -0.79−7.9 becomes −7: the conversion truncates towards zero, it does not round down.
  5. f = 0.50.5 is exact in single precision too, but tenth is still the rounded 0.1, so equal stays false. whole is 5.
f and tenth were both written as 0.1, yet equal stays false.

tenth is 0.10000000149011612 in every tick:

Tick f equal after whole after
2 0.1 (held) false 1
3 0.79 false 7
4 −0.79 false −7

Two lessons. Never compare floats with ==; compare with a tolerance, or keep the quantity in an int (tenths of a degree, milliseconds). And storing a float in an int truncates towards zero: 7.9 becomes 7, and −7.9 becomes −7. Literals that single precision holds exactly, such as 0.5, 2.5 or 1500.0, are not rounded.

A float on its own is not a condition: if f go to … is rejected with Float tests must have a comparison operator! Write if f > 0.0 go to ….

Besides =, effects can use the compound assignments of C and two of KIELER’s own:

Effect Means
x += e, x -= e, x *= e, x /= e, x %= e x = x + e, and so on
x &= e, x |= e, x ^= e, x <<= e, x >>= e bitwise versions
x min= e keep the smaller: x becomes e if e is less than x
x max= e keep the larger
x++, x-- add or subtract 1 (also on floats)
OpsSimulation

Diagram

Diagram of OpsStates: Watch (initial). Transitions: Watch to Watch: / lowest min= reading; highest max= reading; count++; doubling *= 2.Opsinput intreadingoutput intlowest = 1000output inthighest = 0output intcount = 0output intdoubling = 10Watch/ lowest min= reading;highest max= reading;count++;doubling *= 2

Code

scchart Ops {
input int reading
output int lowest = 1000
output int highest = 0
output int count = 0
output int doubling = 10
initial state Watch
do lowest min= reading; highest max= reading; count++; doubling *= 2
go to Watch
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of Ops
Tickinput int readingoutput int lowestoutput int highestoutput int countoutput int doublingActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 5 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 5 ticks:

  1. reading = 40Tick 1 sets the initial values; the self-loop is delayed.
  2. lowest min= 40 keeps the smaller of 1000 and 40; highest max= 40 the larger of 0 and 40. count++ adds 1 and doubling *= 2 doubles.
  3. reading = 7575 is not below 40, so lowest stays 40; highest becomes 75.
  4. reading = 1212 is the new lowest; 75 stays the highest.
  5. reading keeps its value, 12. Nothing new for lowest and highest, but count and doubling change in every tick.
lowest and highest track the extremes of reading; count and doubling change in every tick.

Prefer these to writing the variable twice. For count = count + 1, official KIELER warns:

This assignment looks like old relative write SC syntax and is considered deprecated.
If you want to create a relative write in new syntax, please use an infix assignment operator.

SCCharts Lab says what it makes of it instead:

This assignment reads count, so the compiler treats it as an update of count (a relative write, as in the deprecated old SC syntax).
Write it as count += 1 to say so. In one region it means the same.
It only matters when another region also writes count in the same tick: this assignment then runs after that write instead of conflicting with it.

This site’s playground says the same in fewer words. The infix assignment operator is count += 1, or count++. In the charts you have met so far both forms mean the same thing; the difference only matters with concurrent regions, as Why the compiler says no explains.

Add a size in brackets to declare an array: int ring[3], or int grid[2][3] for two dimensions. The size must be a number, or a constant whose initial value is a plain number. Elements start at the type’s default, or take their values from a vector: int ring[3] = {1, 2, 3}. A vector may be shorter than the array (the rest stay 0) but not longer (Cardinalities do not match.). Elements are numbered from 0: ring[0], ring[1], ring[2].

A vector can also be assigned in an effect, ring = {ring[1], ring[2], ring[0]}. That looks like a rotation, but KIELER runs it as one assignment per element, in order, each seeing the ones before: ring[0] = ring[1]; ring[1] = ring[2]; ring[2] = ring[0]. By the time ring[2] is assigned, ring[0] has already been overwritten. Saving the element first fixes it:

Rotate and RotateTempComparison

Diagrams

Rotate

Diagram of RotateStates: Turn (initial). Transitions: Turn to Turn: / ring = {ring[1], ring[2], ring[0]}.Rotateoutput intring[3] = {1, 2, 3}Turn/ ring = {ring[1], ring[2], ring[0]}

RotateTemp

Diagram of RotateTempStates: Turn (initial). Transitions: Turn to Turn: / first = ring[0]; ring = {ring[1], ring[2], first}.RotateTempoutput intring[3] = {1, 2, 3}intfirstTurn/ first = ring[0];ring = {ring[1], ring[2], first}

Code

Rotate

scchart Rotate {
output int ring[3] = {1, 2, 3}
initial state Turn
do ring = {ring[1], ring[2], ring[0]} go to Turn
}

RotateTemp

scchart RotateTemp {
output int ring[3] = {1, 2, 3}
int first
initial state Turn
do first = ring[0]; ring = {ring[1], ring[2], first} go to Turn
}
Tick 0 of 4
Timing diagram of Rotate and RotateTemp, no ticks yet
TickRotate: output int ringRotate: Active state of the chartRotateTemp: output int ringRotateTemp: Active state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 4 ticks Both charts get the same inputs. Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 4 ticks, the same inputs for both charts:

  1. Tick 1: both charts start with ring = {1, 2, 3}. Their self-loops are delayed.
  2. Rotate runs ring[0] = ring[1]; ring[1] = ring[2]; ring[2] = ring[0], in that order. By the time ring[2] is assigned, ring[0] is already 2, so it gets {2, 3, 2}: the 1 is lost. RotateTemp saved ring[0] in first before the vector assignment, so its last element gets the 1 back: {2, 3, 1}.
  3. Rotate: {3, 2, 3}. RotateTemp: {3, 1, 2}.
  4. Rotate is back to {2, 3, 2} and never rotates. RotateTemp is back to {1, 2, 3} after three rotations.
Rotate loses its 1 in tick 2; RotateTemp saves ring[0] first and really rotates.
Tick Rotate’s ring RotateTemp’s ring
1 {1, 2, 3} {1, 2, 3}
2 {2, 3, 2} {2, 3, 1}
3 {3, 2, 3} {3, 1, 2}
4 {2, 3, 2} {1, 2, 3}

There is no whole-array assignment: b = a between two arrays is rejected by the C compiler (assignment to expression with array type). Copy element by element, and mind the order, as above.

KIELER compiles a chart to C and runs the C program, and in a few places the C shows through. You won’t need any of this for the charts in this guide. Skim it now, and come back when a chart of your own behaves strangely.

In the C code a bool is a char. Assign it 5 and it shows as true but reads back as 5; assign it 256 and it is false, because 256 does not fit in a char and becomes 0. Keep bools for conditions and do arithmetic in ints.

In KIELER’s C simulation a string is a pointer (char*), and == compares pointers, not text. Greeting compares the input name and the output reply with the literal "hi":

GreetingSimulation

Diagram

Diagram of GreetingStates: Compare (initial). Transitions: Compare to Compare: / nameIsHi = name == "hi"; replyIsHi = reply == "hi".Greetinginput stringnameoutput stringreply = "hi"output boolnameIsHi = falseoutput boolreplyIsHi = falseCompare/ nameIsHi = name == "hi";replyIsHi = reply == "hi"

Code

scchart Greeting {
input string name
output string reply = "hi"
output bool nameIsHi = false
output bool replyIsHi = false
initial state Compare
do nameIsHi = name == "hi"; replyIsHi = reply == "hi" go to Compare
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Greeting
Tickinput string nameoutput string replyoutput bool nameIsHioutput bool replyIsHiActive state of the chart
Click a tick to show it, or use the Left and Right arrow keys to go back and step.
Guided tour, 3 ticks Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 3 ticks:

  1. name = "hi"Tick 1: reply gets the literal "hi". The self-loop is delayed, so nothing is compared yet.
  2. reply == "hi" is true, but name == "hi" is false although name is “hi” too. In the C code strings are pointers: reply points at the same literal "hi", while the input’s text lives somewhere else.
  3. name = "bye"nameIsHi is false whatever name holds: an input string never compares equal to a literal.
Warning: comparison with string literal results in unspecified behavior [-Waddress]
  • WarningThe C compiler says: comparison with string literal results in unspecified behavior [-Waddress]About this message
With name set to hi, nameIsHi is still false.

reply was assigned the literal "hi", so it points at that very literal and compares equal. The text you type into the simulator lives somewhere else in memory, so name == "hi" is false whatever you type. So a string comparison runs and quietly gives the wrong answer. Official KIELER compiles it without a word; SCCharts Lab passes on the C compiler’s warning for each one (C compiler warnings):

comparison with string literal results in unspecified behavior

Strings cannot be joined either. The C compiler rejects s + "b":

invalid operands to binary + (have ‘char *’ and ‘char *’)

Don’t base decisions on strings: encode choices as ints, bools or an enum (mode == 2, isManual), and use strings only for text you pass through. In the KIELER VS Code extension (0.3.0), string inputs are broken: its simulation of Greeting shows name as nameIsHi in every tick, whatever you send.

A compound assignment converts its right-hand side first

Section titled “A compound assignment converts its right-hand side first”

For an int target and a float right-hand side, KIELER converts the right-hand side to int before applying a compound operator. So x *= 1.5 multiplies by 1. In this chart, which runs in every tick from tick 2 on,

initial state Calc
do compound = n; compound *= 1.5; plain = n * 1.5 go to Calc

compound *= 1.5 never changes anything, while plain = n * 1.5 computes the product and then truncates it:

Tick n compound after plain after
2 10 (held) 10 15
3 3 3 4
4 −3 −3 −4

Tick 1 runs input-free initial values first

Section titled “Tick 1 runs input-free initial values first”

KIELER’s default compiler has a bug in tick 1: assignments that do not read an input are moved ahead of those that do. With these declarations and start = 4 in tick 1, KIELER gives twice the value 0, not 8, because it computes twice before first has its value:

input int start
output int first = start
output int twice = first * 2

This site’s simulator shows the intended 8, with a warning that KIELER computes something else; SCCharts Lab and KIELER show 0. Until the bug is fixed, don’t build one initial value on another that reads an input. Compute it on a transition instead.

In KIELER’s simulation an index outside the array is not caught: ring[3] in a three-element array silently reads or overwrites whatever variable lies next to it in memory. This site’s simulator does the same, and says so under What happened in that tick; a constant index outside its array also gets a warning. Only where the C code would reach past the chart’s own variables does the simulator stop with an error instead. Make sure your indices stay in range.

The KIELER wiki also describes _ as a placeholder that leaves an element unchanged in a vector assignment. It does not work in current KIELER (the C compiler reports ‘_’ undeclared), so assign the elements you mean one by one.

Keep the tank in range

Limits adds flow and takes away drain in every tick, and volume happily goes above CAPACITY and below zero. Keep volume between 0 and CAPACITY without adding a state or a transition: two more effects on the self-loop are enough.

Show the starter chart
scchart Limits {
input int flow, drain
output int volume = 0
const int CAPACITY = 500
initial state Filling
do volume += flow - drain go to Filling
}
Show a solution
scchart Limits {
input int flow, drain
output int volume = 0
const int CAPACITY = 500
initial state Filling
do volume += flow - drain; volume max= 0; volume min= CAPACITY
go to Filling
}

Remember the last three readings

History should keep the last three values of reading in last (newest first) and their average in average. With readings 6, 9 and 12 in ticks 2 to 4, last should end up {12, 9, 6} and average 9. As written, every element of last holds the newest reading. Rewrite the vector assignment as three element assignments, in an order that never overwrites an element you still need.

Show the starter chart
scchart History {
input int reading
output int last[3]
output int average = 0
initial state Sample
do last = {reading, last[0], last[1]};
average = (last[0] + last[1] + last[2]) / 3
go to Sample
}
Show a solution
scchart History {
input int reading
output int last[3]
output int average = 0
initial state Sample
do last[2] = last[1]; last[1] = last[0]; last[0] = reading;
average = (last[0] + last[1] + last[2]) / 3
go to Sample
}
a is −7 and b is 2, both int. What are a / b and a % b in KIELER?
Show the answer

Answer B: −3 and −1

Integer division truncates towards zero, so −7 / 2 is −3, and the remainder has the sign of the dividend: −7 % 2 is −1. This is C’s rule (and Java’s), not Python’s.

What does the effect y = x > 5 ? 1 : 0 store in y when x is 3?
Show the answer

Answer B: 1, because it means x > (5 ? 1 : 0), that is x > 1

The conditional binds tighter than >, so the expression is x > (5 ? 1 : 0). 5 ? 1 : 0 is 1 (5 is not zero), and 3 > 1 holds, so y is 1. Write (x > 5) ? 1 : 0 to get 0.

A chart declares input int level and output int start = level. level is 5 in tick 1 and 8 from tick 2 on. What is start after tick 3?
Show the answer

Answer A: 5

An initial value is assigned once, when the chart is entered in tick 1, and it can read tick 1’s inputs. After that, nothing assigns start again, so it stays 5.

Key ideas

  • Inputs are written by the environment, outputs and locals by the chart. In the simulator an input keeps its value until you change it.
  • Variables start at their type’s default (0, false, 0.0) or their initial value, assigned once in tick 1, in declaration order, with tick 1’s inputs available.
  • Arithmetic is C’s: integer division truncates towards zero, ints wrap at 32 bits, and storing a float in an int truncates. Float literals are rounded to single precision, so never compare floats with ==.
  • The conditional c ? a : b binds tighter than everything else. Parenthesise its parts.
  • Prefer x += e and x++ to x = x + e. A vector assignment runs element by element, in order.
  • Some C details show through: strings compare as pointers, and nothing checks for division by zero or for indices out of range.

Next, Signals: values that are present for a single tick, for the events a controller reacts to.

Report a mistake on this page