7.7 Library mechanisms
7.7.1 Introduction
This chapter describes all language features that are relevant to the structuring of collections of scenarios and supporting infrastructure.
This includes scenario libraries, domain model extensions, and helper libraries.
This chapter is targeted at library mechanisms for the language and does not consider scenario databases.
7.7.2 Scenario entry point
The scenario entry point is the initial scenario that is selected for scenario execution. It is instantiated first and starts the overall execution.
For ASAM OpenSCENARIO it is defined by implementation how the scenario entry point is selected from the available scenarios in a file. This allows implementations to experiment with the best ways to select this entry point while still allowing full flexibility in a scenario and scenario file reuse.
A future version of ASAM OpenSCENARIO is likely to provide more standardized and advanced facilities for entry point selection. |
7.7.3 Namespaces
To avoid name conflicts, ASAM OpenSCENARIO allows the placement of definitions into separate namespaces.
7.7.4 The namespace statement
This is done with the namespace
statement:
namespace-statement ::= 'namespace' namespace-name ['use' namespace-list ] NEWLINE
namespace-list ::= namespace-name (',' namespace-name)*
namespace-name ::= identifier | global-namespace-name
global-namespace-name ::= 'null'
The namespace statement switches the active namespace. Within its scope, all definitions place the defined identifier into this active namespace, unless the defined identifier is explicitly qualified.
The namespace statement is a main statement and can occur at any place any other main statement can occur in a file. Its scope extends from the point of the namespace statement until the next namespace statement is encountered or the end of file is reached.
Note that namespace statements do not influence the namespace of definitions in files that are imported to a file, meaning the effect of the namespace declaration does not cross file boundaries:
Each file processing starts in the implicit null
namespace, which is accessed if no namespace declaration is encountered, or if explicitly switched to using a namespace null
statement.
The namespace statement takes an identifier as its mandatory argument.
The mandatory argument identifies the namespace.
Note that all identifiers starting with std
are reserved for use by this or future releases of ASAM OpenSCENARIO.
Using a naming convention — such as reverse domain name notation — is suggested to avoid name clashes when exchanging namespaces with other entities.
This involves replacing the dots in a component-wise reversed domain name under the author’s control with underscores (_) to create a unique prefix for namespace names.
An example would be |
7.7.4.1 Referencing namespaces
# Defining 'bar' within the namespace 'demo_foo'
namespace demo_foo
struct bar:
baz: uint
Identifiers defined inside of a namespace can be referenced outside of this namespace by pre-pending the namespace name to the identifier name, forming a prefixed or explicitly qualified identifier:
identifier ::= ( id-start-char id-char* ) | ( '|' non-vertical-line-char+ '|' )
qualified-identifier ::= identifier | prefixed-identifier
prefixed-identifier ::= ( [ namespace-name ] '::' identifier )
The namespace name, followed by two colons (::
), followed by the definition identifier name, forms a valid reference.
For the null namespace, either the null
namespace name or the empty string `, both followed by two colons (meaning `null::
or ::
) are valid prefixes.
Conversely, identifiers not prefixed with a namespace name and two colons are called unprefixed identifiers, or implicitly qualified identifiers. Unprefixed (meaning implicitly qualified) identifiers are resolved to explicitly qualified identifiers during namespace resolution, under the identifier resolution rules specified in Section 7.7.4.2, “Resolution rules for unprefixed identifiers”.
namespace space_one
global baz: uint
namespace space_two
global x: uint = space_one::baz
Definitions in the current namespace do not need any prefix prepended to access them. Explicitly prefixing them is still allowed.
To ease the access of identifiers in other namespaces, namespaces can be added to the use list of a namespace by including them in the namespace
declaration with a use
clause.
namespace foo
export bar, az
struct bar:
az: uint
ay: uint
namespace bazzle use foo, drizzle
struct foobar inherits bar:
myz: uint = az # or foo::az
myy: uint = foo::ay # ay would reference bazzle::ay, which does not exist, hence an error is raised
By including a namespace in the use list of a namespace, all the identifiers in the export list of the other namespace are made available in the current namespace statement scope under the identifier resolution rules specified in Section 7.7.4.2, “Resolution rules for unprefixed identifiers”.
Note that the effect of the use clause is restricted to the scope of the current namespace statement. Any other namespace statement that switches to the same namespace can have a different or no use clause, and correspondingly different or no identifiers will be available in the scope of those statements.
The export list of a namespace is maintained with export
statements, which place identifiers onto the namespace export list:
export-statement ::= 'export' export-specification (',' export-specification)* NEWLINE
export-specification ::= qualified-identifier | export-wildcard-specification
export-wildcard-specification ::=[ [ namespace-name ] '::' ] '*'
namespace foo
export bar, az
struct bar:
az: uint
ay: uint
namespace moo use foo
export newbar, bar, foo::ay # Can also export identifiers from other non-used namespaces
struct newbar inherits bar
namespace bazzle use moo
struct foobar inherits bar: # Or moo::bar or foo::bar
myz: uint = foo::az # Not available from moo via use
myy: uint = ay # or moo::ay or foo::ay
It is possible to export identifiers from a namespace that are either local to the namespace, or exist in another namespace, by explicitly qualifying the identifier. In either case the identifier is added to the export list of the current namespace. A re-exported identifier is still considered identical to the original identifier.
Using the export wild-card specification, an export statement can also add all identifiers of a namespace to the export list of that or another namespace:
namespace foo
export * # In this example equivalent to export of bar, az, ay
struct bar:
az: uint
ay: uint
namespace moo use foo
export *, foo::* # Equivalent to newbar, foo::bar, foo::az foo::ay
struct newbar inherits bar
namespace bazzle use moo
struct foobar inherits bar: # Or moo::bar or foo::bar
myz: uint = az # Available from moo via re-export
myy: uint = ay # or moo::ay or foo::ay
Note that a wild-card export specification includes all identifiers in the given namespace, wherever they are defined.
7.7.4.2 Resolution rules for unprefixed identifiers
The following rules apply to the resolution of unprefixed (meaning implicitly qualified) identifiers to explicitly qualified identifiers:
-
For initial definitions of identifiers, the identifier is always placed into the current namespace, even if another identifier from a used namespace would be accessible.
For types, events, units, enumeration values, labels, and fields, the place of their definition is always considered a place of initial definition for their identifier.
For methods, the place of initial definition of their identifier is defined as the first place, in order of inheritance, that the method is defined in, whether an implementation is provided or not. All other places where a method is defined — meaning through overriding using
is only
— are not considered places of initial definition. Hence, an identifier from a used namespace could be resolved for the method name — but only if accessible and not shadowed by an identifier of the current namespace. -
When resolving an unprefixed identifier in all places that are not a place of initial definition, any identifiers from the current namespace shadow any identifiers accessible from the used namespaces. In this case, this means that the unprefixed identifier is resolved to an existing identifier from the current namespace, regardless of any identifiers accessible from the used namespaces.
When no identifier local to the current namespace is present, the unprefixed identifier is resolved against the identifiers accessible from the used namespaces. An error is raised during identifier resolution if more than one same-named identifier is accessible from the used namespaces.
7.7.4.3 Resolution examples
namespace foo
export bar, az, my_method, another_method
struct bar:
az: uint
def my_method() -> float is undefined
def another_method() -> float is undefined
namespace moo use foo
struct newbar inherits bar:
az: float # Is moo::az, hence no conflict with foo::az
def my_method() -> float is only expression 42 # Will resolve to foo::my_method, and override it
def another_method() -> float is expression 42 # Will resolve to moo::another_method and not override foo::another_method
scenario demo:
mybar: newbar
keep(mybar.az == mybar.my_method()) # mybar.moo::az == mybar.foo::my_method()
keep(mybar.foo::az == mybar.another_method()) # mybar.foo::az == mybar.moo::another_method()
7.7.5 Import mechanism
To structure source code, it is good practice to split the code into manageable parts and put these parts into separate files. The files can then be recombined using an import mechanism.
The import mechanism built into ASAM OpenSCENARIO allows the content of one file (the referenced file) to be reused in another file (the referencing file). This mechanism also works recursively, so that a referenced file can refer to further referenced files.
7.7.5.1 The import statement
The import
statement allows importing a file.
The import statement makes all definitions found in the referenced file effective as if they had been included in the referencing file:
-
Any prelude statements found in the referenced file are treated as having occurred at the textual location of the import statement.
-
Any other statements are treated as having appeared before all non-prelude statements in the referencing file, taking the order of imports into account, meaning non-prelude statements from earlier import statements precede those from later import statements.
The file to be imported can be specified inside the import statement as a string literal or using identifiers.
In all cases, a file that is referenced multiple times (either directly or transitively) is only imported once. The import occurs at the place in the transitive reference chain that is first according to depth-first traversal: All later references to the same file result in no further import occurring.
The implementation shall detect multiple references as described using the mechanisms the implementation considers necessary. In case no other mechanism is found more suitable, an implementation may consider two files to be the same if they have identical content.
7.7.5.1.1 Import using string literals
If the file is specified using a string literal, then the file specification is considered to be a Uniform Resource Identifier (URI). Relative URIs are resolved relative to the location of the referencing file.
Implementations shall support the file
URI scheme.
Implementations may support more URI schemes.
It is recommended to use a file extension of .osc
to identify files of this version of ASAM OpenSCENARIO.
This differentiates such files from the XML-based ASAM OpenSCENARIO XML 1.3.0 files using the .xosc
file extension.
7.7.5.1.2 Import using identifiers
If the file is specified using one or more identifiers separated by a period (.
), the file specification is considered to be a module reference and is resolved to a file specification by the implementation in an implementation-specific manner.
This mechanism can include user- and system-defined library search paths.
Any structured identifier that starts with the osc
identifier is reserved for use by this version and future versions of ASAM OpenSCENARIO (see also Section 7.7.5.2, “Importing the standard library”).
Note that the reserved identifier |
The current specification of how module references are resolved is intentionally left non-specific for this release in order to allow implementations to evolve with actual usage practice. Future versions of the standard are likely to encode best practices when actual practice is clearer and the other components of a module system are in place. For maximum portability across implementations the URI-based string specifications can be used. |
# URI-based import statements
import "foo/bar.osc"
import "file:///c:/Users/someone/src/foo/bar.osc"
import "file:/c:/Users/someone/src/foo/bar.osc"
import "/c:/Users/someone/src/foo/bar.osc"
import "file:///home/someone/src/foo/bar.osc"
import "file:/home/someone/src/foo/bar.osc"
import "/home/someone/src/foo/bar.osc"
# Identifier-based import statements
import osc.standard.all # Imports the standard library, see next section.
import foo.bar # Imports a module foo.bar in some implementation defined way
7.7.5.2 Importing the standard library
The ASAM OpenSCENARIO standard library is described in Section 8.15, "Standard library". Any scenario file that is to use standard library facilities must import the standard library in one of the ways laid out in the following sections.
ASAM OpenSCENARIO provides three files with definitions that match the ones mandated by the ASAM OpenSCENARIO standard library: types.osc
, domain.osc
, and standard.osc
.
Whether an implementation implements access to the standard library by importing some variant of these files, or through any other means (for example, by providing access to built-in definitions) is left to the implementation.
7.7.5.2.1 Complete import of standard library
The full standard library, including all sub-modules thereof, can be imported with the following import statement:
import osc.standard.all
This ensures that all the definitions of the standard library are accessible in their respective namespaces (meaning std
and stdtypes
).
If those namespaces are to be used, they need to be placed on the use list of the current namespace, via use clauses.
Note that in future releases more modules of the standard library might be defined, and all of them will be imported when using the import statement above.
7.7.5.2.2 Partial import of standard library
If only one or more of the sub-modules of the standard library is needed, then they can be imported with one of the following import statements:
import osc.standard.types # Imports the physical types and units and related structs
import osc.standard.domain # Imports the rest of the domain model
This ensures that all the definitions of the relevant standard library sub-module are accessible in their respective namespaces (meaning stdtypes
or std
).
If those namespaces are to be used, they need to be placed on the use list of the current namespace, via use clauses.
7.7.5.2.3 Legacy import with auto-use of standard library
For backward-compatibility with ASAM OpenSCENARIO 2.0, the following import statement imports the full standard library and adds both the std
and stdtypes
namespaces to the use list of the null
namespace:
import osc.standard
This ensures that all the definitions of the standard library are accessible in unprefixed form from the null
namespace, as long as no namespace statement is encountered.
Once a namespace statement is encountered, its namespace and use list specifications will take effect normally.
This facility is only intended for backward-compatibility with ASAM OpenSCENARIO 2.0, where this was the mandated way to import the standard library, and no namespaces existed. Once a scenario is properly migrated to this or later releases, it should use one of the non-legacy mechanisms to import the standard library.