Skip to content

Operators

Expressions in SCTX: operator precedence with KIELER’s quirks, assignment operators, pre and val, and what each type means in C.

Expressions appear in triggers (if count > 3), in effects (do total = a + b), in initial values and in dataflow equations. They look like C, and most of the time they behave like C, because KIELER turns them into C code. The exceptions are in how expressions are grouped: KIELER’s grammar has a few rules that C does not, and they catch out experienced C programmers. This page lists them all, derived from KIELER’s expression grammar and checked with KIELER’s compiler.

The table runs from the operator that binds loosest, at the top, to the one that binds tightest: in A || B && C the && binds tighter, so it means A || (B && C).

Operators Example
|| A || B && C is A || (B && C)
&& A && B | C is A && (B | C)
| (bitwise or) a | b ^ c is a | (b ^ c)
^ (bitwise exclusive or) a ^ b & c is a ^ (b & c)
& (bitwise and) A & 3 == 2 is A & (3 == 2), as in C
== != < <= > >= x + 1 > y is (x + 1) > y; one at a time
<< >> >>> a << b + c is a << (b + c)
+ - a + b * c is a + (b * c)
* / % -a * b is (-a) * b
unary - -a, -(x + 1)
c ? a : b x > 2 ? x : 0 is x > (2 ? x : 0) (below)
-> (the level of ?:) 0 -> I + 1 is (0 -> I) + 1
fby 0 fby N + 1 is (0 fby N) + 1
sfby 5 fby 7 sfby I is 5 fby (7 sfby I)
atoms x, a[i], f(x), pre(x), (…): the operands of everything above

Operators of the same level group from the left: a - b + c is (a - b) + c, and a / b * c is (a / b) * c. The ternary and -> share a level and cannot be mixed without parentheses. fby, sfby and -> are mostly used in dataflow regions: put their operands in parentheses, 0 fby (N + 1). ! and ~ are missing from the table because they do not fit into it; they have rules of their own.

Atoms are the smallest pieces: literals (3, 2.5, true, "text", null), names (x, a[i], obj.field), calls (f(x), extern abs(x)), pre(x), val(V), random, vectors ({1, 2}), host code in backticks, and any expression in parentheses.

To check a grouping, type the expression below. The widget reads it with the same grammar rules as KIELER and shows where the parentheses go; its buttons hold the traps described on the rest of this page.

How KIELER reads it
Read it as

KIELER reads this expression as

A > (2 ? A : 0)

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

  • >greater than
    • A
    • ?:conditional
      • 2
      • A
      • 0
Type an expression, an effect or a trigger, or try one of the traps: KIELER’s grouping, fully parenthesised and as a tree.

c ? a : b binds tighter than every arithmetic, comparison and logical operator, and each of its three parts must be an atom. So the condition of a ternary is only the atom just before the ?, not the whole comparison:

GroupingSimulation

Diagram

Diagram of GroupingStates: Calc (initial). Transitions: none.Groupinginput intAinput boolBoutput intwrong, right, plus, masked, maskedRightimmediate during/ wrong = A > (2 ? A : 0)immediate during/ right = A > 2 ? A : 0immediate during/ plus = (B ? 10 : 20) + 1immediate during/ masked = A & 3 == 2immediate during/ maskedRight = (A & 3) == 2Calc

Code

scchart Grouping {
input int A
input bool B
output int wrong, right, plus, masked, maskedRight
immediate during do wrong = A > 2 ? A : 0
immediate during do right = (A > 2) ? A : 0
immediate during do plus = B ? 10 : 20 + 1
immediate during do masked = A & 3 == 2
immediate during do maskedRight = (A & 3) == 2
initial state Calc
}
Tick 0 of 3
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Grouping
Tickinput int Ainput bool Boutput int wrongoutput int rightoutput int plusoutput int maskedoutput int maskedRightActive 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. A = 6, B = falseA > 2 ? A : 0 groups as A > (2 ? A : 0): the condition 2 is not zero, so the inner part is A, and A > A is false, 0. With parentheses, right is 6. B ? 10 : 20 + 1 is (B ? 10 : 20) + 1 = 21. A & 3 == 2 is A & (3 == 2) = 6 & 0 = 0, while (A & 3) == 2 is 1.
  2. A = 1, B = trueA is 1, so right is 0; wrong is 0 as always. B is true: plus = 10 + 1 = 11.
  3. A = 77 & 3 is 3, not 2, so maskedRight is 0. wrong is still 0 although A > 2.
1 warning and 2 notes
  • Note, line 6, column 31KIELER reads this as A > (2 ? A : 0)The condition of ? : is a single value, so the comparison is not part of it. Write (A > 2) ? ... with parentheses.
  • Note, line 8, column 30KIELER reads this as (B ? 10 : 20) + 1Put parentheses around the whole conditional, or around its parts, to say what you mean.
  • WarningThe C compiler says: suggest parentheses around comparison in operand of ‘&’ [-Wparentheses]About this message
Watch wrong: it is 0 whatever A is, because KIELER reads its expression as A > (2 ? A : 0).
You write KIELER reads
A > 2 ? A : 0 A > (2 ? A : 0)
B ? 10 : 20 + 1 (B ? 10 : 20) + 1
2 * B ? 3 : 4 2 * (B ? 3 : 4)
A == 5 ? 1 : 2 A == (5 ? 1 : 2)
B ? -1 : 2 a syntax error
A ? 1 + 2 : 3 a syntax error
A ? B ? 1 : 2 : 3 a syntax error

The last three fail because -1, 1 + 2 and a ternary are not atoms. The rule of thumb: put parentheses around the condition and around every part that is not a single name or number. For C’s meaning, write (A > 2) ? A : 0, B ? 10 : (20 + 1), (2 * B) ? 3 : 4, B ? (-1) : 2 and A ? (B ? 1 : 2) : 3.

The else part may be left out: x = c ? e keeps x unchanged when c is false. That holds only for a plain assignment, though: x += c ? 1 adds x to itself when c is false. Write the else part (the cheat sheet’s Rarely used has the details).

A comparison takes at most one comparison operator: lo < I < hi and A == B == C are syntax errors (no viable alternative at input '<' and no viable alternative at input '=='), not, as in C, comparisons of a 0/1 result. Write two comparisons joined by &&, each with the variable first: I > lo && I < hi, or I > 1 && I < 3. With a number first, if 1 < I && I < 3 fails in a trigger for another reason: a number straight after if is a count delay.

If you do want to compare a comparison’s result, parenthesise it: (I > 1) == A is true when both or neither hold. SCCharts Lab then passes on the C compiler’s suggest parentheses … warning, which is harmless here: KIELER’s C code leaves out parentheses that C does not need.

! (not) and ~ (bitwise not) apply to an atom, and the result can only be a whole operand of a comparison or of a logical or bitwise operator:

You write KIELER reads
!A && B (!A) && B
!A == B (!A) == B, which is rarely what you meant (!(A == B))
!(A > 3) the negated comparison
!pre(A), !a[0] fine: pre(A) and a[0] are atoms
!A + 1, ~A + 1 a syntax error: no viable alternative at input '+'
I + !A a syntax error too, with the same message
-!A a syntax error
!0, ~5 a syntax error: a number is not an atom for ! and ~

Write (!A) + 1 or (~A) + 1 if you really mean arithmetic on the result.

With a number in front, the message misleads. In an effect, do x = 1 + !A, KIELER says no viable alternative at input '=' and adds the warning Non-signals should not be used in an emission., which has nothing to do with the mistake: having failed to read the assignment, the parser took x for an emission. (In a trigger, if 1 + !A > 1 fails earlier still, because the 1 is a count delay.)

As in C, A & 3 == 2 means A & (3 == 2), which is A & 0, always 0. Write (A & 3) == 2. The same holds for | and ^. In the example above, masked is always 0 while maskedRight is 1 exactly when the low two bits of A are 10. SCCharts Lab passes on the C compiler’s warning for this, suggest parentheses around comparison in operand of ‘&’.

In a trigger, a number straight after if is a count delay, not part of the expression: if 3 A go to B fires the third time A holds. So if 3 == level and if 1 < I && I < 3 are syntax errors, and if 3 -I > 0 means “the third time -I > 0” (KIELER warns A count delay was recognized in front of an unary operation.). Put the variable first, or wrap the expression in parentheses: if level == 3, if (3 - I) > 0.

&& and || evaluate their right operand only when it is needed, as in C: in B != 0 && A / B > 2 the division is never done when B is 0. They yield 1 or 0 (true or false). There is no and, or or not: those are ordinary names in SCTX, so if A and B is a syntax error.

Arithmetic follows C’s rules for each type; see types in C for the details. The one that surprises most people is integer division:

DivisionSimulation

Diagram

Diagram of DivisionStates: Calc (initial). Transitions: none.Divisioninput intA, Boutput intquotient, remainderoutput floatstillWhole, realimmediate during/ quotient = A / Bimmediate during/ remainder = A % Bimmediate during/ stillWhole = A / Bimmediate during/ real = A / 2.0Calc

Code

scchart Division {
input int A, B
output int quotient, remainder
output float stillWhole, real
immediate during do quotient = A / B
immediate during do remainder = A % B
immediate during do stillWhole = A / B
immediate during do real = A / 2.0
initial state Calc
}
Tick 0 of 4
Inputs for tick 1
Timing diagram of Division
Tickinput int Ainput int Boutput int quotientoutput int remainderoutput float stillWholeoutput float realActive 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 Press Next to run tick 1, or Take over to set the inputs yourself.

Guided tour, 4 ticks:

  1. A = 7, B = 2Both operands are int, so / is integer division: 7 / 2 = 3, remainder 1. stillWhole is a float, but A / B is computed in int first, so it is 3. A / 2.0 has a float operand, so it gives 3.5.
  2. A = -7Integer division rounds towards zero: −7 / 2 = −3, and the remainder has the sign of the dividend: −1.
  3. B = -2−7 / −2 = 3, remainder −1.
  4. A = 77 / −2 = −3, remainder 1.
stillWhole is a float, but A / B divides two ints, so the result is whole before it is stored.
  • int / int is integer division, rounded towards zero: 7 / 2 = 3, −7 / 2 = −3.
  • % takes the sign of the left operand: −7 % 2 = −1, 7 % −2 = 1.
  • An int divided by a float (A / 2.0) is a float division.
  • Dividing an int by zero stops KIELER’s simulation silently: the values stop changing and no error appears. The guide’s simulations stop too, but with an error that names the tick. Guard divisions: B != 0 && ….

Effects change variables with an assignment. Besides =, KIELER has C’s compound assignments, two of its own (min= and max=), and ++ and --:

Effect Means
x = e set x to e
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, as in C
x min= e keep the smaller of x and e
x max= e keep the larger of x and e
x++, x-- add or subtract 1
a = {1, 2, 3} assign the elements of an array in index order

x++ and x-- are effects, not expressions: y = x++ is a syntax error. >>>= is accepted by KIELER but fails in the C compiler, like >>>. Write min= and max= without a space.

AssignmentsSimulation

Diagram

Diagram of AssignmentsStates: Update (initial). Transitions: Update to Update: / x = I; x += 3; x *= 2; lo = 100; lo min= I; hi max= I; n++; scaled = 7; scaled *= 1.5.Assignmentsinput intIoutput intx, lo, hi, n, scaledUpdate/ x = I;x += 3;x *= 2;lo = 100;lo min= I;hi max= I;n++;scaled = 7;scaled *= 1.5

Code

scchart Assignments {
input int I
output int x, lo, hi, n, scaled
initial state Update
do x = I; x += 3; x *= 2; lo = 100; lo min= I; hi max= I; n++; scaled = 7; scaled *= 1.5 go to Update
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Assignments
Tickinput int Ioutput int xoutput int looutput int hioutput int noutput int scaledActive 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. I = 5Tick 1 enters Update; its self-loop is delayed, so nothing has run yet.
  2. The effects run left to right: x = 5, then + 3, then * 2 = 16. lo = 100, then lo min= 5 keeps the smaller: 5. hi max= 5 keeps the larger of 0 and 5. n++ counts. scaled *= 1.5 multiplies by (int) 1.5, which is 1: scaled stays 7.
  3. I = -4x = (−4 + 3) * 2 = −2; lo = −4. hi keeps 5, the larger of 5 and −4: max= compares with the current value.
The effects run left to right, and scaled *= 1.5 leaves scaled at 7.

Effects in one list run left to right, each seeing what the ones before it wrote. There is one twist: when the target is an int and the right-hand side a float, KIELER truncates the right-hand side before a compound operator applies it. scaled *= 1.5 multiplies by (int) 1.5, which is 1, while scaled = scaled * 1.5 would multiply first and then truncate, giving 10.

Prefer count += 1 (or count++) to count = count + 1. They do the same in one region, but KIELER warns about the second form, and the compound form says plainly that the effect is an update, which matters when several regions change the same variable. The KIELER VS Code extension’s warning is

This assignment looks like old relative write SC syntax and is considered deprecated.

SCCharts Lab explains 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.

followed by a line on what happens when another region writes count in the same tick. Compiler messages has the whole story.

pre(x) is the value x had at the end of the previous tick.

PreviousSimulation

Diagram

Diagram of PreviousStates: Run (initial). Transitions: none.Previousinput intIoutput intnow = 50, before, twoAgoimmediate during/ now = Iimmediate during/ before = pre(now)immediate during/ twoAgo = pre(pre(now))Run

Code

scchart Previous {
input int I
output int now = 50, before, twoAgo
immediate during do now = I
immediate during do before = pre(now)
immediate during do twoAgo = pre(pre(now))
initial state Run
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Previous
Tickinput int Ioutput int nowoutput int beforeoutput int twoAgoActive 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. I = 5now starts at 50, but pre(now) in tick 1 is 0, the default of int, not 50: there is no previous tick yet.
  2. I = 6pre(now) is the value now had at the end of tick 1: 5. pre(pre(now)) goes two ticks back, to before tick 1: 0.
  3. I = 7before = 6 and twoAgo = 5.
In tick 1, before is 0, not 50: there is no previous tick yet, so pre gives the type’s default.
  • In the first tick, pre(x) is the default of the type (0, false), not the initial value of x. The same happens each time the state that declares x is entered again.
  • pre(pre(x)) goes two ticks back.
  • pre takes a name, not an expression: pre(I + 1) is a syntax error. Write pre(I) + 1. Array elements are fine: pre(a[1]). In a trigger, if pre(I + 1) > 2, the message is mismatched input '+' expecting ')'; in an effect, do x = pre(I + 1), it is no viable alternative at input '=', with the same misleading emission warning as for 1 + !A.
  • pre(S) of a signal is “was S present in the previous tick?”.
  • pre(x) never waits for this tick’s writes of x, so it is the usual way to break a causality cycle (see Why the compiler says no).

val(V) is the value of the valued signal V. It is kept from tick to tick until V is emitted again, and in the tick of an emission it already sees the new value, in every region. The name of a signal on its own, V, is its presence (true or false, or 1 and 0 in arithmetic), so V + 1 is 1 or 2 whatever the value: use val(V) + 1. pre(val(V)) is the previous tick’s value; val(pre(V)) is not accepted (it fails in the C compiler with implicit declaration of function ‘val’). The signals and previous tick chapters teach both.

KIELER compiles a chart to C, so each type behaves like its C counterpart.

Type In C Consequences
int 32-bit int wraps round on overflow (2147483647 + 1 = −2147483648); / rounds towards zero; no literals above 2147483647; no hex literals
bool char true and false are 1 and 0, so true + 1 is 2; there is no type check between bool and int
float double but every float literal is first rounded to single precision (see below); stored into an int, it is truncated towards zero
string char* == compares where the strings are stored, not their text; there is no + for strings
pure signal its presence, a bool S && T tests presence

int as a condition. An int can be a trigger on its own and means “not zero”: if count go to B fires when count is not 0. A float cannot (Float tests must have a comparison operator!).

bool is a small number. A bool holds whatever char value it was given: b = 5 makes b true, and x = b then gives 5, not 1. Keep bools for conditions and ints for counting.

float literals are rounded.

FloatsSimulation

Diagram

Diagram of FloatsStates: Calc (initial). Transitions: none.Floatsinput floatFoutput floatliteraloutput boolsameoutput inttruncatedimmediate during/ literal = 0.1immediate during/ same = F == 0.1immediate during/ truncated = F * 10Calc

Code

scchart Floats {
input float F
output float literal
output bool same
output int truncated
immediate during do literal = 0.1
immediate during do same = F == 0.1
immediate during do truncated = F * 10
initial state Calc
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Floats
Tickinput float Foutput float literaloutput bool sameoutput int truncatedActive 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. F = 0.1The literal 0.1 is rounded to single precision when the chart is compiled, so literal holds 0.10000000149011612, which the trace rounds to 0.1. The input F keeps full double precision, so F == 0.1 is false.
  2. F = 2.75F * 10 is 27.5; stored in an int, it is truncated to 27.
  3. F = -2.75Truncation goes towards zero: −27.5 becomes −27.
F is 0.1 and the chart compares it with the literal 0.1, yet same is false.

The literal 0.1 is stored as 0.10000000149011612, while a value typed into the simulator’s input field keeps full double precision; so an input of 0.1 does not equal the literal 0.1. Never compare floats with ==; compare with a tolerance, or use integers (count tenths in an int). A float stored into an int loses its fraction, towards zero: 27.5 becomes 27, −27.5 becomes −27.

Strings are pointers. s == "go" compares where two strings are stored in memory, not their text, and a string that came from the environment never equals a literal, whatever its text. An unset string is null. Use an int code or a few const ints instead of comparing strings.

Kind Examples Not allowed
int 0, 42, 007 (leading zeros are ignored: 010 is ten, not C’s octal 8) 0x1F (no hex), 3000000000 (too large)
float 1.5, 1., 1.5e3, 2.5E-3, 1.5f, 2f .5, 1e3 (a float needs digits and a ., or an f)
bool true, false TRUE, True
string "text" escapes such as \n and \": the parser accepts them, but they break the generated C code
vector {1, 2, 3}, {0 to 3} (0, 1, 2, 3) {}
other null, random (a float from 0 to 1), `host code`

Negative numbers are the unary minus applied to a literal, which matters inside a ternary: B ? (-1) : 2. random (or random()) gives the same sequence of numbers in every run of a simulation, which keeps runs reproducible. The KIELER in the KIELER VS Code extension 0.3.0 compiles it only when the chart starts with #hostcode "#include <stdlib.h>"; SCCharts Lab does not need the line. See random numbers for more.

Report a mistake on this page