Skip to content

Behavior specification

The Composition operators also specify behavior.

do

Purpose

Define the behavior of a scenario

Category

Directive

Syntax

do <scenario-invocation>
Syntax parameters

See Scenario invocation for a description of <scenario-invocation>.

Description

do is required within a scenario declaration or extension in order to define scenario behavior. Invoking a scenario causes its do member to be activated.

You define a scenario's behavior by invoking Foretify's built-in Composition operators, behavioral directives, library scenarios, and user-defined scenarios:

  • Foretify's composition operators, actions and behavioral directives perform tasks common to all scenarios, such as implementing serial or parallel execution mode, adding information to a log file, or implementing time-related actions such as wait.
  • Library scenarios describe relatively complex behavior, such as the vehicle.drive scenario and scenario modifiers, that let you control speed, distance between other vehicles and so on.
  • By calling these scenarios in a user-defined scenario, you can describe more complex behavior, such as a vehicle approaching a yield sign or another vehicle moving at a specified speed.

In this manner, complex behavior is described by a hierarchy of scenario invocations.

Two operator scenarios commonly used to define scenario behavior are serial and parallel. See Composition operators for a description of these and other operators.

Within a scenario declaration or extension, use do once and only once in a scenario declaration or extension. Do not use do when invoking any nested scenario. For example, do is used below to invoke serial, but omitted when invoking turn and yield:

OSC2 code: behavior specification with do
scenario vehicle.zip:
    do serial():
        t: turn()
        y: yield()

See complete example.

To execute scenario zip, you need to extend top.main, again with do:

OSC2 code: extend top.main with do
extend top.main:
    car1: vehicle

    do z: car1.zip()

See complete example.

However, if you extend vehicle.zip with do as in the following example, the earlier do statement in the vehicle.zip scenario is overridden. So, in this example, only intercept will execute.

OSC2 code: extend scenario with do
extend vehicle.zip:
    do top.intercept()

See complete example.

To execute a previously defined do block, you can use previous_do().

To make your code easily readable, create an explicit, meaningful label for the scenario invoked by do as well as all nested scenario invocations. Invocations without an explicit label are labeled automatically.

In the following example, there is an explicit label for the serial invocation at the root of the tree. The remaining invocations are labeled automatically. See Automatic label computation for how automatic labels (implicit labels) are computed.

OSC2 code: explicit label for do
extend top.main:
    car1: vehicle

    do a: serial():                # explicit label "a"
        car1.drive() with:
            s1: speed(0kph, at: start)
            s2: speed(10kph, at: end)

See complete example.

previous_do()

Purpose

Execute (and extend) a scenario's previously defined behavior

Category

Scenario invocation

Syntax

previous_do()
Description

In the do block of a scenario extension, you can execute the previously defined do block using previous_do(). The result is that the do block of the previous extension is executed as if it were written in place.

If multiple extensions are loaded, the do block in the most recently loaded extension is the one executed by previous_do().

Any user defined labels in the previous do block are accessible inside the current do block. Therefore, labels defined in the current do block should not use the same name as labels in the previous one.

The following uses of previous_do() result in an error:

  • Invoking previous_do() multiple times in a single do block.
  • Invoking previous_do() outside of a do block.
  • Invoking previous_do() when no previous behavior has been defined.

Note

If you want to replace the previously defined behavior, simply define the new behavior in a scenario extension without using previous_do().

Example: add to previously defined behavior

In this example, the previous behavior of scenario3 is executed first. Then new behavior is added.

You can of course define new behavior first and then call previous_do() or you can use parallel to execute new behavior in parallel with the previous behavior.

OSC2 code: behavior specification with previous_do()
scenario top.scenario1:
  flag: bool

  do log("Scenario 1 is executing")

extend top.scenario1:

  do serial:
    previous_do()
    log("Extending scenario 1 behavior")

extend top.main:

  do serial:
    scenario1()

Here are the results of executing this example:

Foretify> run
Checking license...

Running the test ...
Loading configuration ... done.
[0.000] [MAIN] Executing plan for top.all@1 (top__all)
[0.000] [MAIN] Scenario 1 is executing
[0.000] [MAIN] Extending scenario 1 behavior
[0.020] [MAIN] Run finished
[0.020] [MAIN] Ending the run
[0.020] [SIMULATOR] stop_run()
Run completed
See complete example.

Scenario invocation

A scenario can only be invoked within an operator scenario or within a do clause of a scenario declaration.

Purpose

Invoke a scenario

Category

Scenario invocation

Syntax

[<label-name>:] <scenario-name>(<param>*) [<with-block>]
Syntax parameters

<label-name>
(Optional) Is an identifier that has to be unique within the scenario declaration. If a label is not specified, an automatic label is created. See Automatic labels for an explanation of how automatic labels are computed.
<scenario-name>

(Required) Is the name of the scenario you want to invoke, optionally including the path to the scenario. See Path operator and name resolution for an explanation of how names without explicit paths are resolved.

Note

Invoking the generic form of the scenario, such as vehicle.drive() is not allowed.

Invocation parameters

For more information on invocation parameters, see Invocation parameters.

<with-block>

(Optional) <with-block> is a list of one or more keep() constraints or scenario modifiers, where the members are listed on separate lines as a block or on the same line as with: and separated by semi-colons. For example, the following two scenario invocations are the same:

OSC2 code: 'with' block syntax example 1
ss2: some_scenario(z: 4) with:
  keep(x==3)
  keep(y==5)

OSC2 code: 'with' block syntax example 2
ss2: some_scenario(z: 4) with: keep(x==3);  keep(y==5)

Note

cover() definitions are not allowed in scenario invocations.

Example

This example shows the declaration of a scenario called traffic.two_phases. do is required to define the behavior of two_phases, and it invokes the serial operator scenario. The nested scenario, car1.drive, whose behavior is defined as a library scenario, is invoked without do.

OSC2 code: behavior specification with scenario invocation
scenario traffic.two_phases:   # Scenario name
    # Define the cars with specific attributes
    car1: vehicle with:
        keep(it.color == green)
        keep(it.vehicle_category == box_truck)

    # Define the behavior
    do serial():
        phase1: car1.drive() with:
            spd1: speed(0kph, at: start)
            spd2: speed(10kph, at: end)
        phase2: car1.drive() with:
            speed([10..15]kph)

See complete example.

Invocation parameters

The Scenario invocation, Scenario modifier application, and Watcher invocation receive a list parameters.

The parameters are received in the form ([<param> [, <param>]*]). Here, each <param> is in the form [<field-name|event-name>:] [default] [<value>|<qualified-event>]. The descriptions of the fields within the <param> are provided below:

  • <value> is an expression representing a single value, range, or a path to an event.
  • <qualified-event> is a qualified event.
  • The optional default keyword indicates that the field assignment can be overridden later using a constraint.

The list must be separated by a comma and enclosed in parentheses. It can be any of the following:

  • Name-based (<field-name>: [default] ,…)
  • Order-based ([default] <value>,…)
  • Mixed name-based and order-based

In order-based lists, the first value is assigned to the first field in the scenario, and so on.

In the list, a name-based parameter can follow an order-based parameter, or vice-versa. Thus, the following are valid and assign the same values:

  • turn(x: 3, y: 5)
  • turn(3, y: 5)
  • turn(3, 5)
  • turn(x: 3, 5)

When invoking an operator scenario, parentheses are allowed but not required. When invoking all other scenarios, parentheses are required.

Note

Passing an argument to a var field is not allowed and will result in a compilation error.

You can pass a qualified event as an argument to an event using the syntax <event-name>: <qualified-event>. The event parameters must be name-based. The default is not applicable to event parameters. <label>: <scenario-path>(<event-name>: <qualified-event>) is equivalent to on <qualified-event>: emit <label>.<event-name>.

call

Purpose

Call a method

Category

Directive

Syntax

call <method>(<param>*)

Syntax parameters

<method>
(Required) Is the name of a declared method. If the method is not in the current context, the name must be specified as <path-name>.<method-name>
<param>*
(Required) Is a comma-separated list of method parameters.

Example

OSC2 code: behavior specification with call
extend top.main:
    on @c.phase_essence_slow.start:
        call sut.car.calculate_dist_to_other_car(c.cut_in_vehicle)

    do c: sut.vehicle_cut_in_and_slow()

See complete example.

See also Call (invoke) a method.

emit

Purpose

Emit an event in zero-time

Category

Directive

Syntax

emit <event-path>[(<param>+)]

Syntax parameters

<event-path>
(Required) Has the format [<path-expression>.]<event-name>.

Scenario arguments

<param>+
(Optional) Is a comma-separated list of one or more parameters defined in the event declaration in the form <param-name>: <value>. Passing parameters by position is not allowed.

Example

OSC2 code: behavior specification with emit
extend top.main:
    event second_after_pass_by
    do parallel(start_to_start:[0..0]s, overlap:any):
        onc: sut.oncoming()
        smp: serial():
            wait @onc.oncoming_vehicle.car_passing_by
            wait elapsed(1s)
            log_info("Sampling dut speed: $(sut.car.state.speed)")
            emit second_after_pass_by
    var dut_speed:=sample(sut.car.state.speed,
     @second_after_pass_by) with:
        cover(dut_speed_second_after_pass_by, expression: it,
        unit:kph,range:[0..200], every:10)

See complete example.

on

Purpose

Execute actions when an event occurs. on blocks can be used as scenario or modifier members.

Category

Directive

Syntax

on <qualified-event>[ with]:
    <procedural-code>

Syntax parameters

<qualified-event>
(Required) See Qualified event for a description.
<procedural-code>
(Optional) Any procedural code that can be defined in a native method is also legal here. See Native methods for more information. When the on is defined with a with clause, the parameter it is available to the procedural code. That it parameter represents the event data.

Example 1

OSC2 code: behavior specification with 'on'
scenario top.scenario1:
    car1: vehicle

    event near_collision

    var near_collisions_count := 0

    on @near_collision:
        near_collisions_count = near_collisions_count + 1
        logger.log_info("Near collision occurred.")

    do serial:
        car1.drive()

See complete example.

watcher and checker declarations

watcher

Purpose

Instantiate a watcher that will emit intervals.

Category

Watcher

Syntax

watcher <name>[(<watcher-data>)] is <watcher-type>(<param>*)

Syntax parameters

<name>

(Required) The instance name of the watcher.

<watcher-data>

(Optional) The watcher instance will have a data field of the type watcher-data. The watcher-data has to be a legitimate watcher data type (derived from any_watcher_data). If the watcher-type already has watcher data defined for it, the expressed watcher-data has to be derived from it.

<watcher-type>

(Required) The type of the watcher, defined by the watcher modifier statement. See watcher type for details.

<param>*

(Optional) A list of zero or more parameters of the form [<field-name>:] <value> that pass values to the watcher fields. See the scenario-invocation parameters for more information.

Invocation parameters

For more information on invocation parameters, see Invocation parameters.

Description

Instantiate a watcher of type <watcher-type> with the provided parameters. This watcher will emit intervals according to the <watcher-type>. If <watcher-data> is provided, the intervals will be of that type.

For more details, see Defining watchers and checkers.

Example: watcher that creates intervals that record maximal speed

The following example defines a watcher that creates intervals that record the maximal speed when the speed of the SUT vehicle exceeds some speed limit during the scenario.

OSC2 code: watcher that creates intervals that record maximal speed
# Define custom watcher data that records the maximum speed during
# an interval
struct speed_above_data inherits any_watcher_data:
    max_speed: speed
    record(max_speed, unit: kph)

# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit,
# and records the maximal exceeding speed
watcher modifier speed_above(speed_above_data):
    v: vehicle
    limit: speed

    on @top.w_clk:
        if(data == null):
            if(v.state.speed > limit):
                start_interval()
        else:
            if(v.state.speed <= limit):
                end_interval()
    on @i_clock:
        if(data.max_speed < v.state.speed):
            data.max_speed = v.state.speed

global modifier vehicle.speed_watchers:
    # Instantiate the watcher for all the vehicle actors
    watcher speed_above_w is speed_above(actor, 30kph)

See complete example.

Example: On-the-fly watcher that creates intervals that record maximal speed

The following example defines an on-the-fly watcher that creates intervals that record the maximal speed when the speed of the SUT vehicle exceeds some speed limit during the scenario.

OSC2 code: on-the-fly watcher that creates intervals that record maximal speed
# Define watcher data that records a speed
struct speed_watcher_data inherits any_watcher_data:
    speed: speed
    record(speed, unit: kph)

extend top.main:
    watcher above_speed(speed_watcher_data) is any_watcher_behaviour() # Instantiate the watcher

    on @top.clk:
        if(above_speed.data == null):
            if(sut.car.state.speed > 30kph):     # when the speed of the vehicle exceeds 30kph start the interval
                above_speed.start_interval()
        else:
            if(sut.car.state.speed <= 30kph):    # when the speed of the vehicle is less than 30kph end the interval
                above_speed.end_interval()

    on @above_speed.i_clock:               # during the interval record the maximal speed
        if(above_speed.data.speed < sut.car.state.speed): above_speed.data.speed = sut.car.state.speed

See complete example.

checker

Purpose

Instantiate a checker that will emit violation intervals

Category

Checker

Syntax

checker <name>[(<watcher-data>)] is <watcher-type>(<param>*)

Syntax parameters

<name>

(Required) The instance name of the checker.

<watcher-data>

(Optional) The checker instance will have a data field of the type watcher-data. The watcher-data has to be a legitimate watcher data type (derived from any_watcher_data). If the watcher-type already has watcher data defined for it, the expressed watcher-data has to be derived from it.

<watcher-type>

(Required) The type of the checker, defined by the watcher modifier statement. See watcher type for details.

<param>*

(Optional) A list of zero or more parameters of the form [<field-name>:] that pass values to the checker fields. See the scenario-invocation parameters for more information.

Description

Instantiate a checker of type <watcher-type> with the provided parameters. This checker will emit intervals according to the <watcher-type>. If <watcher-data> is provided, the intervals will be of that type. The checker is used to validate the correct behavior of a part of the system. If that part does not behave as expected, the checker can emit an issue that will be captured by the error mechanisms of the system. The checker declaration exposes issue-related methods that are not available for watchers.

Checkers follow the same logic as watchers. You need to start the interval with start_interval() and end it with end_interval(). While the interval is active, you can call the checker methods to create an issue associated with that interval. When the interval is active, you can override the issue by calling subsequent calls to the checker methods. The issue will be released to the system only after the end_interval() is called. Once the interval ends, the checker issue goes through the checker verdict analysis followed by the general verdict analysis where the issue may be altered according to the analysis. The checker verdict analysis is utilized by the set_issue() modifier.

The checker API

The checker follows the same API as the watcher. In addition, there are specific methods to create and manipulate issues for the checker.

Checker API method common parameters

The checker methods use common parameters as follows:

severity: issue_severity
The severity of the issue.
`kind: issue_kind
The kind of the issue.
category: issue_category
The category of the issue.
details: string
The message of the issue.
normalized_details: string
Provide a canonical string for clustering issues. If not provided, normalized_details is deduced automatically from the issue message.

Checker API methods

sut_issue()

sut_issue(severity: issue_severity, kind: issue_kind, details: string, normalized_details: string = "")

Issue an SUT issue with the provided severity.

sut_error()

sut_error(kind: issue_kind, details: string, normalized_details: string = "")

Issue an SUT error.

sut_warning()

sut_warning(kind: issue_kind, details: string, normalized_details: string = "")

Issue an SUT warning.

scenario_completion_error()

scenario_completion_error(kind: issue_kind, details: string, normalized_details: string = "")

Issue a scenario completion error.

scenario_completion_warning()

scenario_completion_warning(kind: issue_kind, details: string, normalized_details: string = "")

Issue a scenario completion warning.

other_issue()

other_issue(severity: issue_severity, kind: issue_kind, details: string, normalized_details: string = "")

Issue an issue of category other.

other_error()

other_error(kind: issue_kind, details: string, normalized_details: string = "")

Issue an error of category other.

other_warning()

other_warning(kind: issue_kind, details: string, normalized_details: string = "")

Issue a warning of category other.

issue()

issue(category: issue_category, severity: issue_severity, kind: issue_kind, details: string, normalized_details: string = "")

Issue a generalized issue.

message()

message(details: string, normalized_details: string = "")

Set the message of the issue to a specific string.

Example: checker that creates issues when speed limit exceeded

The following example defines a checker that creates issues when the speed of an SUT vehicle exceeds some speed limit. The issues describes the maximal speed reached.

OSC2 code: checker that creates issues when speed limit exceeds
extend issue_kind: [too_fast] #define a new issue kind

# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit
watcher modifier speed_above:
    v: vehicle
    limit: speed
    var max_speed: speed

    on @top.w_clk:
        if(data == null):
            if(v.state.speed > limit):
                start_interval()
        else:
            if(v.state.speed <= limit):
                end_interval()
    on @i_clock:
        if(max_speed < v.state.speed):
            max_speed = v.state.speed

global modifier sut_vehicle.speed_checkers:
    checker check_speed_above is speed_above(actor, 30kph) with:
        it.sut_error(kind: too_fast,
            details: "Vehicle $(it.v) exceeded maximal speed $(it.limit) reaching up to $(it.max_speed)")

See complete example.

set_issue

Purpose

Perform verdict analysis on intervals

Category

Modifier

Syntax

set_issue(target_checker, condition, category, severity, kind, message)

Syntax parameters

target_checker

(Required) The checker path you want to perform verdict analysis on. When no target_checker is provided, the change is performed on all checker issues that meet the condition.

condition

(Required) A condition to be checked when the issue is handled. When the condition is not met, the set_issue() is ignored.

category: issue_category

(Required) Change the issue category to this one. When no category is provided, the issue category is not changed.

severity: issue_severity

(Required) Change the issue severity to this one. When no severity is provided, the issue severity is not changed.

kind: issue_kind

(Required) Change the issue kind to this one. When no kind is provided, the issue kind is not changed.

message

(Required) Change the issue message to this one. When no message is provided, the issue message is not changed.

Description

The set_issue() modifier lets you modify issues coming from checkers. If target_checker is provided, only the issues coming from that checker are modified. If target_checker is not provided, the modification is performed on all checker-associated issues.

The modification is activated for issues only if the condition is met.

Example: checker that uses set_issue to issue an informative interval

The following example changes a checker called s to issue an informative interval rather than an error.

OSC2 code: checker with set_issue to issue an informative interval
extend top.main:
    set_issue(target_checker: sut.car.speed_checkers.check_speed_above, severity: info)

See complete example.

synchronize

Purpose

Synchronize the timing of two sub-invocations.

Category

Directive

Syntax

synchronize (slave: <inv-event1>
    , master: <inv-event2> 
    [, offset: <time-exp>])

Scenario arguments

slave: <inv-event1>, master: <inv-event2>

(Required) <inv-event1> is a scenario invocation event that you want to synchronize with <inv-event2> (another scenario invocation event).

An invocation event has the form <invocation-label>.<event>, where <invocation-label> is the label of some scenario invocation in the current scope.

offset: <time-exp>
(Optional) Is an expression of type time. It signifies how much time should pass from the master event to the slave event. If negative, the slave event should happen before the master event.

Description

The synchronize() modifier accepts two events. You can synchronize more events by using multiple statements.

The synchronize() modifier is mainly used under parallel() to sync between scenarios within the top-level scenarios, and not for the top-level scenarios themselves.

until

Purpose

End an invoked scenario when an event occurs.

Category

Directive

Syntax

until(<qualified-event>)

Syntax parameters

<qualified-event>
(Required) See Qualified event for a description.

Example

OSC2 code: behavior specification with until
    # Example 1

    do serial:
        phase1: car1.drive() with:
            speed(40kph)
            until(@e1)
        phase2: car1.drive() with:
            speed(80kph)
            until(@e1)
See complete example.

wait

Purpose

Delay action until the qualified event occurs

Category

Directive

Syntax

wait <qualified-event>

Syntax parameters

<qualified-event>
(Required) See Qualified event for a description.

Description

Note

Any scenario has these predefined events: start, end, fail.

Examples

OSC2 code: behavior specification with wait
    do serial:
        w1: wait @top.my_event
        w2: wait @top.my_event if a > b
        w3: wait (a > b)

See complete example.

OSC2 code: behavior specification with wait elapsed
    do serial:
        w1: wait elapsed([3..5]second)

        # max is a field defined with type time and constrained to a value
        w2: wait elapsed(max)

See complete example.

For additional guidelines on how to use wait, see Tips for the proper use of wait as an event-based constructs.

Methods

Foretify supports three types of methods:

Purpose

Describe and implement the behavior of an OSC2 object

Category

Struct, actor, or scenario member

Syntax

def <osc-method-name> (<param>*) [-> <return-type>] <method-implementation>

Syntax parameters

<osc-method-name>
(Required) Is an identifier that is unique in the current OSC2 context.
<param>*
(Optional) Is a list composed of zero or more arguments separated by commas of the following form. The parentheses are required even if the parameter list is empty.
<param-name>: <param-type> [= <default-exp>]
<return-type>
(Optional) Specifies the type of the return parameter. Methods without a declared return type can only be invoked with the call directive in behavior specifications. They cannot be invoked in other expressions.
<method-implementation>

(Required) Is one of:

  • is [<qualifier>] undefined
  • is [<qualifier>] expression <expression>
  • is [<qualifier>] <procedural-code>
  • is [<qualifier>] <bind-exp>

<qualifier> specifies an extension to a previously declared method. The method signature must be the same as the previous definition. <qualifier> is one of the following:

  • also appends the specified method to the previously declared method(s).
  • first prepends the specified method to the previously declared method(s).
  • only replaces the previously declared method(s) with the specified method.

Undefined methods

The syntax for undefined methods is:

OSC2 code: undefined method syntax
def <osc-method-name> (<param>*) [-> <return-type>] is [<qualifier>] undefined

undefined declares but does not implement a method. If an undefined method is called, it causes a runtime error.

Example undefined method declaration

OSC2 code: undefined method example
def calculate_distance() is undefined

Expression methods

The syntax for methods that evaluate an expression is :

OSC2 code: expression method syntax
def <osc-method-name> (<param>*) [-> <return-type>] is [<qualifier>] expression <expression>

The <expression> to be evaluated can reference the named arguments of the method as well as all other fields that are in scope.

Example expression method declaration

This expression method returns true if an integer is even.

OSC2 code: expression method example
def is_even(i: int) -> bool is expression (i % 2 == 0)

External methods

Foretify provides an interface for invoking OSC2 methods defined in C++.

The syntax for external methods is :

OSC2 code: external method syntax
def <osc-method-name> (<param>*) [-> <return-type>] is [<qualifier>] external cpp([[name:] <c++-method-name>,] <shared-object-name>)
<c++-method-name>
(Required) Specifies the name for the C++ method. The name must be unique within the current context. When not set, the C++ method name is the same as <osc-method-name>.
<shared-object-name>
(Required) Specifies the shared object that contains the implementation of the C++ method. The name should include only the file name; the full library path is detected according to the operating system conventions. For example, in Linux, libraries are searched for in paths defined by the environment variable LD_LIBRARY_PATH.

Example

This example shows how to declare an external method that accesses data stored in an external (not OSC2) entity, in this case, a C++ class. Fields of the external_data type cannot be generated, so they must be declared as variables with the keyword var.

OSC2 code: external method example
extend vehicle:
    var states: external_data

    def set_states_on_lane_change(states: external_data)-> external_data is \
      external cpp("set_states_on_lane_change", "car_states.so")

Call (invoke) a method

You can call methods using the call directive. If a method returns a value, you can use it in any place where an expression can be used, such as in constraint expressions, in qualified event expressions, in coverage computations and so on.

When called, these methods execute immediately in zero simulated time.

Example method call

This example calls the external sut.car.calculate_dist_to_other_car() method at the start of the slow phase of cut_in_and_slow().

OSC2 code: call method example
extend top.main:
    on @c.phase_essence_slow.start:
        call sut.car.calculate_dist_to_other_car(c.cut_in_vehicle)

    do c: sut.vehicle_cut_in_and_slow()

See complete example.

Native methods

Native methods are implemented in OSC2 and do not refer to an external language implementation.

The syntax for defining a native method is:

OSC2 code: native method syntax
def <name>(<param>*)[-> <return-type>] is [also|first|only]:
   <procedural-code>

Note

The following language constructs can be used in any procedural code such as native methods or within an on blocks, within the init() method or the post_plan() method.

Declaring local variables

You can declare a typed variable that can hold values. There are two variants for variable declaration:

OSC2 code: var declaration syntax

  • For an explicit type, use this syntax:

    OSC2 code: explicit var declaration syntax
    var name: type[ = value]
    

  • For an implicit type, use this syntax:

    OSC2 code: implicit var declaration syntax
    var name := value
    

Local variables can be defined anywhere in the procedural code:

  • A variable is known from the declaration until the end of that block.
  • Only one variable with the same name can be defined in each block.
  • Variables defined in an internal block hide other variables with the same name defined in external blocks.
  • The top-most block of a method holds the parameters of the method as local variables. The body of the method opens an internal block, possibly containing local variables.

Using set to assign variables or fields

You can set a value for a local variable, for a global field or for a field in a struct instance that was passed to the method.

OSC2 code: assign value syntax
[set] <pathname> = <value>

Allocating objects with new

You can allocate structs or actors using the new operator in a variable declaration or when setting a value for a field.

OSC2 code: allocate object syntax

  • In a variable declaration

    OSC2 code: allocate object syntax in a variable declaration
    var x: type = new
    

  • Assigning a value to a field:

    OSC2 code: allocate object syntax for assigning a value to a field
    [set] <pathname> = new
    

OSC2 code: allocate object example

OSC2 code: allocate object
struct my_ints:
    x: int
    y: int

extend top.main:

    def new_ints() -> my_ints is:
        var w:= new my_ints
        return w

Calling a method that does not return a value

OSC2 code: method call syntax
[call]<method-pathname>
OSC2 code: method call example
call logger.log_info("It's too late!")

Adding conditional flow control

You can add conditional flow control using this syntax:

OSC2 code: conditional flow syntax
if(expression):
    ...
[elif:
    ...]
[else:
    ...]

Note

Note that conditional flow can be used for triggering events, call functions and non-generatable field assignments. It cannot be used for generatable field assignments, since these are assigned only once at the beginning of a simulation cycle.

OSC2 code: conditional flow example
if(speed < 10kph):
    slow_speed(speed)
if(speed > 50kph):
    fast_speed(speed)
else:
    car1.slow_speed = speed
if(t < 10s): call fast_time()

Return from the method

OSC2 code: return syntax
return [<exp>]

Result field

If the method has a return value, a field named result is defined, and its type is the same as the return value. At any point in the method, you can access the variable like any other variable (set it, include it in an expression, and more). If a method does not reach a return action, the value of result is returned. The result gets a default value according to its type.

Note

The method extension remembers the returned value from previously executed extensions and uses it as the initial value of the result field in the current method extension.

OSC2 code: using the 'result' field
def simple() -> int is:
    if(result > 10):
        result = 16

def simple() -> int is first:
    result = 12    

Repeat loops

You can use a repeat to repeat a block of one or more actions using this syntax:

OSC2 code: repeat action syntax
repeat:
    <block>

To stop the repeat use keyword break.

OSC2 code: repeat action example
def simple_repeat(iterations: uint) -> uint is:
    var current := 0
    var total := 0
    repeat:
        if current == iterations:
            break
        set current = current + 1
        set total = total + 1
    return total

For loops

You can use a for loop to set the values of items in a list using this syntax:

OSC2 code: for item in list syntax
for <item-var>[, <index-var>] in <list-exp>
    <block>

<item-var> is a variable defined inside the loop representing the current list item.

<index-var> is a variable defined inside the loop representing the current index of the item in the list.

The block is executed for each item in the list. The <item-var> and <index-var> are updated with the corresponding values.

OSC2 code: for item in list example
var l: list of int = [1,2,3]
for curr_item in l:
    if curr_item % 2 == 0:
            logger.log_info("$(curr_item)")

Emitting events

You can emit events using the same syntax as the emit directive:

OSC2 code: emit syntax
emit <event-path>[(<param>+)

See emit for details and an example.