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.
Precedence
Section titled “Precedence”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.
KIELER reads this expression as
A > (2 ? A : 0)
As a tree, each operation with its operands under it:
>greater thanA?:conditional2A0
The ternary takes atoms
Section titled “The ternary takes atoms”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:
Diagram
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 | input int A | input bool B | output int wrong | output int right | output int plus | output int masked | output int maskedRight | Active state of the chart |
|---|
Guided tour, 3 ticks:
- A = 6, B = false
A > 2 ? A : 0groups asA > (2 ? A : 0): the condition2is not zero, so the inner part isA, andA > Ais false, 0. With parentheses,rightis 6.B ? 10 : 20 + 1is(B ? 10 : 20) + 1= 21.A & 3 == 2isA & (3 == 2)=6 & 0= 0, while(A & 3) == 2is 1. - A = 1, B = true
Ais 1, sorightis 0;wrongis 0 as always.Bis true:plus= 10 + 1 = 11. - A = 7
7 & 3is 3, not 2, somaskedRightis 0.wrongis still 0 althoughA > 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
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).
Comparisons do not chain
Section titled “Comparisons do not chain”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.
! and ~ apply only to atoms
Section titled “! and ~ apply only to atoms”! (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.)
&, | and ^ bind weaker than ==
Section titled “&, | and ^ bind weaker than ==”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 ‘&’.
Count delays look like numbers
Section titled “Count delays look like numbers”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.
Logical operators
Section titled “Logical operators”&& 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
Section titled “Arithmetic”Arithmetic follows C’s rules for each type; see types in C for the details. The one that surprises most people is integer division:
Diagram
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 | input int A | input int B | output int quotient | output int remainder | output float stillWhole | output float real | Active state of the chart |
|---|
Guided tour, 4 ticks:
- A = 7, B = 2Both operands are
int, so/is integer division: 7 / 2 = 3, remainder 1.stillWholeis afloat, butA / Bis computed inintfirst, so it is 3.A / 2.0has afloatoperand, so it gives 3.5. - A = -7Integer division rounds towards zero: −7 / 2 = −3, and the remainder has the sign of the dividend: −1.
- B = -2−7 / −2 = 3, remainder −1.
- 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 / intis 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
intdivided by afloat(A / 2.0) is afloatdivision. - Dividing an
intby 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 && ….
Assignment operators
Section titled “Assignment operators”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.
Diagram
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 | input int I | output int x | output int lo | output int hi | output int n | output int scaled | Active state of the chart |
|---|
Guided tour, 3 ticks:
- I = 5Tick 1 enters
Update; its self-loop is delayed, so nothing has run yet. - The effects run left to right:
x= 5, then + 3, then * 2 = 16.lo= 100, thenlo min= 5keeps the smaller: 5.hi max= 5keeps the larger of 0 and 5.n++counts.scaled *= 1.5multiplies by(int) 1.5, which is 1:scaledstays 7. - I = -4
x= (−4 + 3) * 2 = −2;lo= −4.hikeeps 5, the larger of 5 and −4:max=compares with the current value.
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 and val
Section titled “pre and val”pre(x) is the value x had at the end of the previous tick.
Diagram
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 | input int I | output int now | output int before | output int twoAgo | Active state of the chart |
|---|
Guided tour, 3 ticks:
- I = 5
nowstarts at 50, butpre(now)in tick 1 is 0, the default ofint, not 50: there is no previous tick yet. - I = 6
pre(now)is the valuenowhad at the end of tick 1: 5.pre(pre(now))goes two ticks back, to before tick 1: 0. - I = 7
before= 6 andtwoAgo= 5.
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 ofx. The same happens each time the state that declaresxis entered again. pre(pre(x))goes two ticks back.pretakes a name, not an expression:pre(I + 1)is a syntax error. Writepre(I) + 1. Array elements are fine:pre(a[1]). In a trigger,if pre(I + 1) > 2, the message ismismatched input '+' expecting ')'; in an effect,do x = pre(I + 1), it isno viable alternative at input '=', with the same misleading emission warning as for1 + !A.pre(S)of a signal is “wasSpresent in the previous tick?”.pre(x)never waits for this tick’s writes ofx, 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.
What each type means in C
Section titled “What each type means in C”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.
Diagram
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 | input float F | output float literal | output bool same | output int truncated | Active state of the chart |
|---|
Guided tour, 3 ticks:
- F = 0.1The literal
0.1is rounded to single precision when the chart is compiled, soliteralholds 0.10000000149011612, which the trace rounds to 0.1. The inputFkeeps full double precision, soF == 0.1is false. - F = 2.75
F * 10is 27.5; stored in anint, it is truncated to 27. - 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.
Literals
Section titled “Literals”| 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.