Table Of Contents

Previous topic

The Siml Tutorial - Bioreactors

Next topic

The Built-In Library

This Page

Syntax of the Siml Language

Terse Language Description

Siml is an object oriented language, that has typesafe macro expansion as its central idea. It is statically (and structurally) typed. It is intended to describe dynamic systems in an object orieted way, and compile them into an other description suitable for a certain numerical solver.

Solvers usually are not object oriented. They see the state variables of the system as an array of floating point values. To compute the solution they need an array of initial values, and a function that describes the system dynamics.

The siml compiler creates several functions: A group of functions to compute the initial values; functions that describe the system dynamics; a function that is called when the solver has finished.

Currently onlyx one solver for systems of differential equations is supported.

Basics

Whitespace Sensitive Syntax

Floating Point Numbers

Text Strings

Identifiers

Type Systems

Structural typing
This type system is enforced by the compiler for data attributes that are passed to the code generator. These built in objects (Float, String, Bool) must be compatible for all operations. http://en.wikipedia.org/wiki/Structural_typing
Nominal typing
Nominal typing is used at function call sites, if type annotations are present. http://en.wikipedia.org/wiki/Nominative_type_system
Duck typing
Duck typing is used for data attributes with user defined types. The attribute must only have the correct name. If the attribute can be used correctly is determined by its leaf objects. http://en.wikipedia.org/wiki/Duck_typing

Expressions

Simple Statements

return Statement

The return statement delivers data from a function back to its caller. Additionally it ends the execution of the function.

The keyword return is followed an expression. The expression is evaluated and the result is delivered to the function’s caller. Only a single value can be returned. Even though functions are always inlined (their code is copied into the caller), the illusion (semantics) of calling a function and returning a value is maintained for the user.

Return statements are illegal inside if statements: if Statement

func square(x):
    return x * x

Example: The function square multiplies its argument x by itself. The expression x * x is computed and the result is delivered to part(s) of the program where the function is called.

See also: Function Definition

pragma Statement

These statements are used to invoke behavior/algorithms for which no special syntax has been developed yet.

At the time of writing, no behavior can be triggered with the pragma statement.

pass Statement

The pass statement does nothing. It is needed to create empty compound statements (if, ifc, func, class), because of the whitespace sensitive syntax.

func dummy():
    pass

Example: The function dummy does nothing.

Assignment (=) Statement

The assignment statement stores values in data attributes (variables, parameters, constants). The value on the right hand side of the = operator is stored in the attribute on the left hand side. Attributes must be assigned to exactly once; however it is possible to implement special classes that behave differently.

An assignment statement begins with the expression on the left hand side, followed by an equal (=) character, followed by the expression on the right hand side.

a = 3 * 2

Example: This assignment stores the value 6 in an attribute named a.

If an assignment is possible is constrained by the roles of attribute and value. If both objects are constant the assignment is performed at compile time.

The assignment statement calls the method __siml_assign__ of the attribute on the left hand side. Therefore the exact meaning of a particular assignment is determined by the programmer who writes the class of the attribute on the left hand side.

Todo

Link to roles

Todo

Expand roles section

Expression Statement

The expression statement contains an expression, which is evaluated. The expression’s value is discarded. This statement is normally used to call a function or method.

print('hello')

Example: Call the built in function print with a String argument.

Todo

Link to function call

data Statement

The data statement creates data attributes (variables, parameters, constants): It instantiates a class, and binds the new object to a name. The new objects are intended to be unknown attributes, but the exact semantics can be decided by the implementor of the class.

The data statement can create multiple objects of the same type.

The data keyword is followed by one or more comma separated attribute names, a colon, a class name, and an optional role modifier.

data a, b, c: Float param

Example: The data statement creates three unknown attributes of type Float, with role parameter.

See also: Class Definition

Todo

Link to roles

compile Statement

See also: Class Definition, data Statement

Compound Statements

Compound statements [1] contain other dependent statements. They somehow control the execution or meaning of those dependent statements. They usually occupy multiple lines, although they can sometimes be written in a single line.

All compound statements have a similar syntactic structure: They consist of a header, and a suite of dependent statements. The header starts with a unique keyword (if, ifc, func, class, ...), followed by syntax specific to the statement, it always ends with a colon (:). Then follows the suite.

A suite is a group of statements. It is usually written as an indented block of statements; but simple statements can be written on the same line as the compound statement’s header, separated by semicolons (;).

#The suite in the function body is written as an indented block of statements.
func bacterial_growth_1(s, x): #The compound statement's header
    mu = 0.3 * s/(s+0.01) # This
    dx = mu*x             # is
    return dx             # the suite

#Function definition written in one line:
#---- This is the header ----| |----------- This is the suite -----------|
func bacterial_growth_2(s, x): mu = 0.3 * s/(s+0.01); dx = mu*x; return dx

Example: Two functions that perform the same computation.

Some compound statements (if, ifc) consist of multiple clauses. A clause is a header with its suite.

if a < 2:             #clause 1
    b = 0             #   ~   1
else:                 #clause 2
    b = (a - 2) * 0.5 #   ~   2

Example: An if statement, which consists of two clauses.

if Statement

The keywords if, elif, and else together form a conditional statement. Different computations can be executed depending on one or more conditions. The decision to perform a specific computation is done at run-time, in contrast to the ifc (note the c) statement, which is evaluated at compile-time. ifc Statement

The statement is structured into several clauses. The if clause is always the first clause, it is followed by optional elif clauses, the else clause comes always last.

  • The if clause begins with the keyword if, which is followed by an expression (the condition), a colon (:), and a usually indented suite of statements.
  • The elif clause is structured exactly like the if clause.
  • The else clause has no condition; the keyword else it is directly followed by a colon and a suite of statements.

The conditions must evaluate to a Bool value. The clauses’ conditions are evaluated in the order in which they appear in the program text. If a condition evaluates to TRUE the statements in the clause’s suite are executed. If no condition is TRUE then the statements of the else clause are executed. Only the statements of one clause are executed.

if a < 0:
    b = 0
elif a < 2:
    b = 0.5 * a
else:
    b = 1

Example: Definition of a piecewise function b(a).

The dependent statements can also be written directly after the colon (:), separated by semicolons (;).

if a < 0:   b = 0
elif a < 2: b = 0.5 * a
else:       b = 1

Example: The same piecewise function as above, but more compact syntax.

Note

As the if statement is executed at run-time, it must be part of a useful set of differential equations. Therefore it is subject to several restrictions:

  • All clauses of the if statement must contain assignments to the same variables.
    • The above is a simplification. The exact rules are not finished yet, but will be similar to: All variables that are used outside the if statement must be assigned in all clauses.
  • An else clause is compulsory, because all variables must always be computed; elif clauses however are optional.

  • return statements are illegal inside if statements: return Statement

See also: ifc Statement

ifc Statement

The keywords ifc, elif, and else together form a conditional statement, which is executed at compile-time. Different pieces of program code can be compiled depending on one or more conditions; it must be possible to evaluate the conditions at compile-time.

The elif and else clauses are optional. There are no restrictions for the ifc statement, all statements can appear inside each clause.

The syntax is exactly like the if (note no c) statement.

See also: if Statement, printc

Todo

Define compile-time and run-time

Function Definition

A function definition creates a function object and binds it to a name in the local name space.

func bacterial_growth(s, x):
    mu = 0.3 * s/(s+0.01)
    dx = mu*x
    return dx

Example: A function definition that could be useful in the context of Example 2: Batch Reactor.

Function definitions can often be written in a single line, although it usually results in poorly readable programs.

func bacterial_growth(s, x): mu = 0.3 * s/(s+0.01); dx = mu*x; return dx

Example: The same function as above, but written in one line.

See also: return Statement

Todo

Link to function call

Todo

Functions are polymorphic (generic), but argument types can be constrained

Todo

Arguments: type annotations

Todo

Arguments: default values

Todo

Code of functions is copied into caller

Todo

Complete section.

Class Definition

A class definition creates a class object and binds it to a name. Data attributes and methods must be defined in the class definition [2]. Currently there is no inheritance.

A class definition begins with the class keyword, followed by the class’ name, and a colon (:). Then follows a suite of statements, the class’ body.

class LinearGrowth:
    data x: Float

    func initialize(this):
        x = 0
        solution_parameters(20, 0.1)

    func dynamic(this):
        $x = 0.5

    func final(this):
        graph(x, title="Linear Growth")

Example: A class definition. When compiled this class solves the equation {dx \over dt} = 0.5.

When the compiler encounters a class definition it creates a new local name space, and executes the statements of the class’ body in it. The attributes of this name space then become the class’ attributes. As the class’ body’s statements are executed at compile time they must not contain any computations with unknown variables.

Classes are specifications that say how other objects should be structured. Objects that are structured to a class’ specification are called the class’ instances. Creating instances of a class is called instantiation. During instantiation the class’ data attributes are copied into the new instance. The methods are not copied but shared by all instances of a class.

The first argument of all methods must be named this. It contains the instance upon which the method currently acts [3].

See also: data Statement, compile Statement

[1]The general definition of compound statements is exactly as in the Python language.
[2]Python classes by contrast do not define their instances’ data attributes. In Python data attributes can be created any time after instantiation. They are often created in the __init__ method.
[3]The special this argument is equivalent to Python’s self. However Siml requires that it is named “this”.