Skip to content

Methods and code effects

Methods and code effects in SCCharts' own statement language: if, loops, local variables and its rule for semicolons, then print and random numbers.

Transitions and actions say when something happens. Some computations are easier to write as a few lines of code: a formula with several cases, a loop over an array. For these SCCharts has methods and code effects. Both are written in SCL, the Sequentially Constructive Language, SCCharts’ own small imperative language. It looks like C, KIELER compiles it together with the chart, and it runs wherever the chart runs, this site included.

This chapter covers methods and code effects, the statements they are made of and what KIELER compiles them into, then print and random numbers, which belong to SCCharts too. Code written in C or Java itself, host code, has a chapter of its own: Host code.

A method is a small function that belongs to the chart. It can return a value, take parameters, and read and write the chart’s variables:

ThermometerSimulation

Diagram

Diagram of ThermometerStates: Display (initial). Transitions: Display to Display: / shown = percent(reading); hot = tooHot(reading).Thermometerinput intreadingoutput intshown = 0output boolhot = falseintpercent(intv )booltooHot(intv )Display/ shown = percent(reading);hot = tooHot(reading)

Code

scchart Thermometer {
input int reading
output int shown = 0
output bool hot = false
method int percent(int v) {
if (v < 0) {
return 0
}
if (v > 100) {
return 100
}
return v
}
method bool tooHot(int v) {
return v > 80
}
initial state Display
do shown = percent(reading); hot = tooHot(reading) go to Display
}
Tick 0 of 4
Inputs for tick 1
Timing diagram of Thermometer
Tickinput int readingoutput int shownoutput bool hotActive 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. reading = 42Tick 1 enters Display; the self-loop is delayed: shown = 0.
  2. reading = 250percent(250) returns 100 and tooHot(250) returns true, both within this tick: shown = 100, hot = true.
  3. reading = -7shown = 0, hot = false.
  4. reading = 90shown = 90, hot = true.
percent and tooHot run within the tick, as if their bodies were written into the transition.
  • method int percent(int v) { ... } declares a method that takes an int and returns one. A method can take several parameters (method int clampTo(int v, int lo, int hi)); leave the return type out (or write void) for a method that returns nothing. Declare methods with the chart’s variables, before its states.

  • A body holds local declarations, assignments, calls, if / else, for and while loops and return; Statements below shows each of them. A call runs the whole body within the tick.

  • Every path through a method with a return type must end in a return. Without any, KIELER reports The method does not return any value!; a method that returns on some paths only compiles, but breaks KIELER’s simulation when it ends without one. A return in a method without a return type gives

    Return statement is used but no return type is declared in method signature.
  • Call methods in triggers (if tooHot(reading) ...), in effects, in actions and in dataflow equations.

  • Treat parameters as read-only. When a method needs to change a value it was given, copy the parameter into a local variable and change that; Don’t assign to a parameter below shows why.

  • Methods inside a class are written without the keyword method.

For a few statements that are used only once, a code effect saves declaring a method: a block in braces in place of an effect, do { a++; b = a * 2 } go to S, or in an action, entry do { ... }. Its statements run one after another, in the same tick, like the body of a method without parameters (only return is not allowed: Return statements can only be used in method bodies.):

ThermostatSimulation

Diagram

Diagram of ThermostatStates: Control (initial). Transitions: Control to Control: / {if (temperature < 18) {heater = true;} else {if (temperature > 22) {heater = false;}}}.Thermostatinput inttemperatureoutput boolheater = falseControl/ {if (temperature < 18) {heater =true;} else {if (temperature > 22){heater = false;}}}

Code

scchart Thermostat {
input int temperature
output bool heater = false
initial state Control
do {
if (temperature < 18) {
heater = true
} else {
if (temperature > 22) {
heater = false
}
}
} go to Control
}
Tick 0 of 6
Inputs for tick 1
Timing diagram of Thermostat
Tickinput int temperatureoutput bool heaterActive 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. temperature = 20Tick 1 enters Control; the self-loop is delayed, so the code effect has not run yet: heater is false.
  2. temperature = 1717 is below 18: the if branch switches heater on.
  3. temperature = 2020 is neither below 18 nor above 22: the else branch runs, its inner if does not, and heater keeps its value, true.
  4. temperature = 23Above 22: heater goes off.
  5. temperature = 2020 again, and heater stays off: between 18 and 22 the thermostat keeps what it last did.
  6. temperature = 15On again.
Between 18 and 22 degrees neither branch changes heater, so the thermostat keeps what it did last (ticks 3 and 5).

An if takes braces on both branches, and the condition is usually written in parentheses, as in C. There is no else if: } else if (...) { gives missing '{' at 'if'. Nest the second if inside else { ... }, as Thermostat does.

Method bodies and code effects use the same statements. Besides assignments, calls and if, they have for and while loops and local variables, and a rule for semicolons that is not C’s.

AverageSimulation

Diagram

Diagram of AverageStates: Measure (initial). Transitions: Measure to Measure: / total = sum(); mean = total / 4.Averageinput intsamples[4]output inttotal = 0, mean = 0intsum( )Measure/ total = sum();mean = total / 4

Code

scchart Average {
input int samples[4]
output int total = 0, mean = 0
method int sum() {
int s = 0
for (int i = 0; i < 4; i++) {
s += samples[i]
}
return s
}
initial state Measure
do total = sum(); mean = total / 4 go to Measure
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Average
Tickinput int samplesoutput int totaloutput int meanActive 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. samples = 1,2,3,4Tick 1 enters Measure; the self-loop is delayed, so sum() has not been called yet.
  2. samples = 10,20,30,41sum() adds the four elements in one call: total is 101. mean is 101 / 4 = 25: integer division drops the fraction.
  3. samples = 0,0,0,-8total is −8, mean −2.
sum() adds up the four elements of samples in one call: four passes of the loop, all within the tick.

for (int i = 0; i < 4; i++) { ... } is C’s for loop: a counter declared in the loop’s head, a condition tested before each pass, and a step. The braces are required, even for a single statement: for (...) a++ gives missing '{' at 'a'. Declare the counter in the loop’s head: for (i = 0; ...) with a variable i of the chart passes KIELER’s checks, but its C code does not compile (‘i’ undeclared (first use in this function)). A loop is a way of computing a value within one tick, not a way of waiting: all its passes happen in the tick the method is called. To spread work over ticks, use states.

DigitsSimulation

Diagram

Diagram of DigitsStates: Count (initial). Transitions: Count to Count: / digits = countDigits(value).Digitsinput intvalueoutput intdigits = 1intcountDigits(intv )Count/ digits = countDigits(value)

Code

scchart Digits {
input int value
output int digits = 1
method int countDigits(int v) {
int rest
int n = 1
rest = v;
while (rest >= 10) {
rest = rest / 10;
n++
}
return n
}
initial state Count
do digits = countDigits(value) go to Count
}
Tick 0 of 5
Inputs for tick 1
Timing diagram of Digits
Tickinput int valueoutput int digitsActive 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. value = 7Tick 1 enters Count; the self-loop is delayed. digits keeps its initial value, 1.
  2. value = 42The loop runs once (rest goes from 42 to 4): digits is 2.
  3. value = 12345Four passes round the loop, all within this one tick: 5 digits.
  4. value = 99 is below 10, so the loop body never runs: 1.
  5. value = 1003 digits.
countDigits divides by 10 until one digit is left. 12345 takes four passes in tick 3.

A while loop runs as long as its condition holds, again within the one tick, so make sure it ends: the tick cannot finish before the loop does. countDigits copies its parameter v into the local variable rest and divides that, since a method should not change its parameters (see Don’t assign to a parameter below).

A declaration at the start of a block declares a variable that exists only in that block. A block is a method body, a code effect, a branch of an if, or the body of a loop:

SwapSimulation

Diagram

Diagram of SwapStates: Hold (initial). Transitions: Hold to Hold: swap / {int t = aa = b; b = t;}.Swapinput boolswapoutput inta = 1, b = 2Holdswap/ {int t = aa = b; b = t;}

Code

scchart Swap {
input bool swap
output int a = 1, b = 2
initial state Hold
if swap do {
int t = a
a = b;
b = t
} go to Hold
}
Tick 0 of 4
Inputs for tick 1 Boolean input: true while on. It keeps its value until you change it.
Timing diagram of Swap
Tickinput bool swapoutput int aoutput int bActive 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. swap = falseTick 1 enters Hold: a is 1, b is 2.
  2. swap = trueThe code effect declares t, keeps a in it, and swaps: a is 2, b is 1. t exists only while the block runs, so it is not in the trace.
  3. swap = falseNo swap.
  4. swap = trueSwapped back.
The code effect keeps a in its own variable t while it swaps a and b.

The diagram’s label reads int t = aa = b: KIELER’s diagrams print a declaration in a code effect without anything between it and the next statement. The code is int t = a, then a = b;.

  • Declarations come first. A declaration after a statement of the same block is a syntax error: a = b; int t = a gives mismatched input 'int' expecting '}' in a code effect and missing '}' at 'int' in a method body. If you need a variable halfway through, open a new block there: a block in braces is a statement too, with its own declarations at its top.
  • A local variable is not visible after its block: a tmp declared in a branch of an if is unknown after the branch (Couldn't resolve reference to ValuedObject 'tmp'.).
  • A local may have the name of one of the chart’s variables. Inside the block it hides that variable: the chart’s variable keeps its value.
  • Local variables are not part of the data pool, so they never appear in a trace.

KIELER’s rule is short: an assignment that is followed by another statement in the same block ends with ;. That includes x++ and x += 2, and an assignment just before return. A line break is not enough:

SwapChart

Diagram

Diagram of SwapStates: Hold (initial). Transitions: Hold to Hold: swap / {int t = aa = b; b = t;}.Swapinput boolswapoutput inta = 1, b = 2Holdswap/ {int t = aa = b; b = t;}

Code

scchart Swap {
input bool swap
output int a = 1, b = 2
initial state Hold
if swap do {
int t = a
a = b
b = t
} go to Hold
}

KIELER rejects this chart

  • Line 8, column 7Missing semicolonKIELER says: Missing SemicolonIn a code block, an assignment that another statement follows needs a ; after it: { x = 1; y = 2 }. Calls, declarations and blocks need none.About this message
a = b on line 8 is followed by another statement, so it needs a semicolon.

Nothing else needs one:

  • declarations (int t = a), calls (inc(), print("a")) and if, for and while blocks may be followed by another statement without ;;
  • the last statement of a block needs none, though a ; there is allowed;
  • return takes none at all: return v; gives extraneous input ';' expecting '}'.

Swap above follows the rule exactly: none after the declaration, one after each assignment but the last. If in doubt, end every assignment with ;; that is always accepted. Effects on a transition follow their own rule: they are separated by ;, with none after the last one (see Missing Semicolon).

KIELER’s netlist compiler inlines a method that is short, has no loop and calls no other method: shown = percent(reading) becomes the statements of percent, with reading in place of v, as if you had written them into the transition. Any other method, and a code effect with a loop, it keeps as a C function. For sum() in for loops above, the generated C code has

int Average_sum(TickData* d) {
int s;
s = 0;
for (int i = 0; i < 4; i++) {
s += d->samples[i];
}
return s;
}

and the transition calls d->total = Average_sum(d);. Mostly you see the difference only in the generated code, and in warnings such as

ValuedObject with name _fn0_percent_v is contained in the model but not in VariableStore.

which KIELER lists for the parameters and local variables of methods, and for the names of extern functions. They are harmless. Two things, though, depend on how a method is compiled.

Inside such a C function, d is the pointer to the chart’s TickData, through which the generated code reaches the chart’s variables (see From chart to C). So in a method or code effect that becomes a C function, no parameter or local variable may be called d. gcc rejects the generated code: a parameter gives conflicting types for ‘d’, a local variable ‘d’ redeclared as different kind of symbol (the KIELER VS Code extension shows only Compiler did not return success (exit value != 0)). An inlined method is not affected, but a method stops being inlined as soon as it gets a loop, so it is simplest never to call a variable d.

The second concerns the parameters of inlined methods. Zap passes its output o to a method that sets its parameter to 0 before it returns. Predict o and r after tick 2, check, then open the explanation.

ZapPrediction

Diagram

Diagram of ZapStates: Run (initial). Transitions: Run to Run: / r = zap(o).Zapoutput into = 5output intr = 0intzap(intv )Run/ r = zap(o)

Code

scchart Zap {
output int o = 5
output int r = 0
method int zap(int v) {
v = 0;
return v + 1
}
initial state Run
do r = zap(o) go to Run
}

In tick 2 the self-loop calls zap(o), and zap sets its parameter v to 0 before it returns.

Plays the scenario on the diagram, then marks each answer.
Explanation

r is 1, and o is 0, not 5. KIELER inlines zap: the call becomes the method’s statements, with the argument in place of the parameter. So v = 0 is carried out as o = 0, and the method has changed the variable it was given. KIELER says nothing about it beyond the harmless VariableStore warning.

With a number or an expression as the argument, zap(3) or zap(o + 1), there is no variable to put in place of v, and the compiler fails: SCCharts Lab reports The compiler failed during compilation setup., and in the KIELER VS Code extension the compilation never finishes. A method with a loop is a C function and gets a copy of its argument, so there the same assignment changes nothing outside.

So whether an assignment to a parameter changes the caller’s variable, breaks the compiler or does nothing depends on how KIELER compiles the method. Don’t depend on any of it: when a method needs to change a value it was given, copy the parameter into a local variable first, as countDigits does.

print writes text to the simulation’s console. It is part of SCCharts, not host code, so it works in every simulation:

ReportSimulation

Diagram

Diagram of ReportStates: Watch (initial). Transitions: Watch to Watch: / print("level is", level); reports++.Reportinput intleveloutput intreports = 0Watch/ print("level is", level);reports++

Code

scchart Report {
input int level
output int reports = 0
initial state Watch
do print("level is", level); reports++ go to Watch
}
Tick 0 of 3
Inputs for tick 1
Timing diagram of Report
Tickinput int leveloutput int reportsActive 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. level = 3Tick 1 enters Watch. The self-loop is delayed: nothing is printed yet, reports = 0.
  2. level = 4The self-loop prints level is 4 and counts: reports = 1. The printed text is not a variable, so it is not in the trace.
  3. level = 9Prints level is 9; reports = 2.
Each tick from tick 2 prints level is and the current level; the printed text is not in the trace.

KIELER turns print("level is", level) into a C printf with one conversion per argument:

  • the arguments are joined with single spaces, and no newline is added, so two prints in one tick run together: print("a", 1); print("b") prints a 1b;
  • an int prints in decimal, a bool as 1 or 0, a float with six decimals (1.500000), a string as it is;
  • a \n inside a print string breaks the generated C (missing terminating " character).

print changes no variable, so its text never appears in the trace. This site’s simulator lists it in its description of each tick. KIELER prints it to the console of the simulation process; the language-server tools, KIELER’s VS Code extension and SCCharts Lab among them, do not show it.

random() returns a float between 0 and 1:

DiceSimulation

Diagram

Diagram of DiceStates: Roll (initial). Transitions: Roll to Roll: / r = random; face = r * 6 + 1.Diceoutput floatr = 0output intface = 0Roll/ r = random;face = r * 6 + 1

Code

scchart Dice {
output float r = 0
output int face = 0
initial state Roll
do r = random(); face = r * 6 + 1 go to Roll
}
Tick 0 of 4
Timing diagram of Dice
Tickoutput float routput int faceActive 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. Tick 1 enters Roll; the self-loop is delayed, so random() has not been called: r = 0.
  2. The first number of C’s rand(): r ≈ 0.840, so face = 6.
  3. r ≈ 0.394, face = 3.
  4. r ≈ 0.783, face = 5. Run it again and you get exactly the same numbers: the generator is never seeded.
face = r * 6 + 1 turns the number into a die roll; assigning a float to an int cuts off the fraction.

KIELER’s C simulation computes random() with C’s rand(), which it never seeds. So every run produces the same numbers, one for each call, in the order the calls happen. (A random() in a trigger is called in every tick, even while its state is not active: KIELER’s C code computes every trigger in every tick.) With the GNU C library, which Linux uses, they are 0.840, 0.394, 0.783, …, and this site’s simulator produces the same sequence. Other systems’ C libraries have their own rand(), so in SCCharts Lab on Windows or a Mac the numbers may well differ (How this guide is checked). Either way, the sequence makes simulations reproducible, which is useful for testing, but it is not randomness. In the KIELER VS Code extension (0.3.0) the chart above also needs #hostcode "#include <stdlib.h>", or gcc fails; current KIELER and SCCharts Lab add that include themselves when a chart calls random().

randomize() is meant to seed the generator from the time of day, so that runs differ. In what KIELER runs unconditionally in tick 1 (an entry action of the chart or of an initial state, or an immediate transition without a trigger taken then) it does not even compile: KIELER’s netlist compiler moves it into the reset code and writes it out as null, and gcc reports ‘null’ undeclared (first use in this function). Anywhere else, even on a triggered transition in tick 1 (immediate if start do randomize()), it seeds the generator, and the runs are no longer reproducible, provided gcc knows srand: the automatic <stdlib.h> comes only with random(), and not at all in the KIELER VS Code extension. Without it, gcc 14 and later reject the generated code with implicit declaration of function ‘srand’; older versions only warn, and the chart runs. Either way, #hostcode "#include <stdlib.h>" fixes it.

A sign method

Write a method int sign(int v) that returns −1 for a negative v, 1 for a positive one and 0 for zero, and use it to set s from value in every tick after the first. Remember KIELER’s rules for return and else.

Show the starter chart
scchart Sign {
input int value
output int s = 0
initial state Watch
do s = value go to Watch
}
Show a solution
scchart Sign {
input int value
output int s = 0
method int sign(int v) {
if (v < 0) {
return -1
}
if (v > 0) {
return 1
}
return 0
}
initial state Watch
do s = sign(value) go to Watch
}

Count the high samples

Write a method int countAbove(int t) that counts how many of the four samples are greater than t, with a for loop, and use it to set high from threshold in every tick after the first.

Show the starter chart
scchart Above {
input int samples[4]
input int threshold
output int high = 0
initial state Measure
do high = 0 go to Measure
}
Show a solution
scchart Above {
input int samples[4]
input int threshold
output int high = 0
method int countAbove(int t) {
int n = 0
for (int i = 0; i < 4; i++) {
if (samples[i] > t) {
n++
}
}
return n
}
initial state Measure
do high = countAbove(threshold) go to Measure
}
A transition has the effect print("ready", ready, n), with ready true and n equal to 3. What does it print?
Show the answer

Answer A: ready 1 3

print joins its arguments with single spaces and adds no newline. A bool is printed as a number, 1 for true, so the text is ready 1 3.

Which of these code effects does KIELER accept?
Show the answer

Answer B: { a = 1; b = 2 }

An assignment followed by another statement needs a semicolon, so the first is rejected with Missing Semicolon. The third declares t after a statement; declarations must come first in a block. The fourth uses else if, which SCL does not have: nest the second if inside else { ... }.

Key ideas

  • Methods and code effects are written in SCL, SCCharts’ own statement language. They run within the tick, wherever the chart runs, this site included.
  • SCL is close to C, but an assignment followed by another statement needs ;, return takes none, there is no else if, and declarations come first in each block.
  • for and while loops run all their passes within one tick: they compute, they never wait.
  • KIELER inlines short methods into the transitions that call them, so don’t assign to a parameter: copy it into a local variable.
  • print joins its arguments with spaces and adds no newline; random() gives the same sequence in every run.

Next, Enums, structs and classes: names for the values of a mode, records of related variables, and data that comes with its own methods.

Report a mistake on this page