Skip to content

Hello, Real-Time World!

This chapter introduces the smallest complete Termina application: a single task that prints a message to the console once every second. Despite its simplicity, the example exercises the essential elements of the language's reactive model, namely an event emitter that produces periodic activations, a task that reacts to those activations, and an access port through which the task reaches the services of the underlying platform. It also illustrates the canonical structure of a Termina project, in which the definitions of the component classes reside in their own modules, while the main application file is reserved for declaring the entities of the system and describing how they are connected.

Creating the project

In addition to translating source code, the transpilation tool is used to create new Termina projects, and it therefore supports several commands. In particular, the new command generates a new project consisting of an initial directory tree together with a configuration file populated with default parameters. Since no target platform is specified, the project adopts the default posix-gcc platform, which executes Termina applications on conventional Unix-like systems and is intended for prototyping and validation.

To create a project named hello_world, the following command is executed from the terminal:

$ termina new hello_world

Once the command has completed, a directory named hello_world will have been created with the following structure:

hello_world/
├── termina.yaml
├── app/
   └── app.fin
├── src/
└── output/

The file termina.yaml is the project's configuration file, in which the project name, build options, and target platform are specified. The app directory contains the main application module, named app.fin by default, whose role is to declare the entities that constitute the system and to define the way in which they are connected. The src directory holds the additional modules that define the component classes and support functions of the application. Finally, the output directory will store the C code generated by the transpiler.

Defining the task class

The behavior of the application is encapsulated in a task class. In Termina, a task is a reactive component that executes one or more actions in response to the events or messages it receives. A task may hold internal state and exposes ports that connect it to other elements of the system, such as event sinks or access ports to shared resources. Each action runs to completion without preemption, which guarantees deterministic and analyzable behavior.

The class definitions of an application are placed in modules within the src directory, and never in the main application file. Accordingly, the task class of this example is defined in a new module located at src/tasks/hello_world.fin, with the following content:

task class CHelloWorldTask {

    timer_port : sink TimeVal triggers timeout;

    system_port : access SystemAPI;

    action timeout(&priv self, _current_time : TimeVal) -> Status<i32> {

        let msg : [char; 32] = "Hello, Real-Time World!";
        let ret : Status<i32> = Success;

        self->system_port.println(&msg);

        return ret;

    }

};

The class CHelloWorldTask declares two ports. The first, timer_port, is an event sink declared as sink TimeVal triggers timeout, which specifies that the action named timeout is to be executed automatically whenever an event is received through this port. Each such event carries a value of type TimeVal, corresponding to the timestamp of the activation, which in this example is not used and is therefore named _current_time. The second port, system_port, is an access port to the SystemAPI interface provided by the platform, through which the task reaches low-level services such as writing to the console.

The action timeout constitutes the entire behavior of the task. It declares a fixed-length character array initialized with the message to be displayed and invokes the println procedure of the system port to print it, passing a reference to it. The action returns a value of type Status<i32>, one of the built-in result types of the language, which indicates whether the action completed successfully or encountered an error. In this example it always returns Success.

Declaring the application architecture

With the task class defined, the system is assembled in the main application file app/app.fin. As noted above, this file does not contain class definitions; its purpose is to declare the entities of the application and to establish the connections between them. The module begins by importing the module that provides the task class:

import tasks.hello_world;

The periodic activation of the task is produced by an event emitter. The following declaration creates an emitter named timer of type PeriodicTimer, whose period field specifies the interval between consecutive events by means of its tv_sec and tv_usec components, here set to one second:

emitter timer : PeriodicTimer = {
    period = {
        tv_sec = 1,
        tv_usec = 0
    }
};

Finally, an instance of the task class is declared and connected to the rest of the system. The annotation #[priority(10)] assigns the task a fixed scheduling priority, defined statically at compile time. The connection timer_port <- timer binds the task's event sink to the periodic emitter, so that the timeout action is executed on every emitted event, while the connection system_port <-> system_entry links the access port to the runtime-provided system_entry resource, granting the task access to the platform's input and output services:

#[priority(10)]
task hello_world_task : CHelloWorldTask = {
    timer_port <- timer,
    system_port <-> system_entry
};

Enabling the system port

The system_entry resource referenced above acts as the gateway between Termina code and the services of the Operating System Abstraction Layer, and it is not instantiated by default. Its deployment is controlled by a configuration parameter in the project's termina.yaml file, which must be enabled by adding the following line:

enable-system-port: true
sys-print-output-buffer-size: 32

When this option is set, the runtime instantiates and exposes the system_entry resource during system initialization, making it available for connection to the task's access port.

Building and running the application

With the task class defined, the architecture declared, and the system port enabled, the application is ready to be built. From the project's root directory, the build command is executed:

$ termina build

The termina build command invokes the transpiler, which validates the program, checks type safety, verifies the port connections, and translates the source modules into C code placed in the output directory. The generated code is then compiled with the build system provided for the posix-gcc platform, and the resulting executable is run:

$ cd output
$ make
$ ./bin/hello_world

The program prints the message Hello, Real-Time World! to the console once every second. To terminate its execution, the key combination Ctrl+C is entered in the terminal.