SIUnits
This language introduces SI units and allows language developers to integrate literals enriched with an SI unit to their language. Furthermore, this language provides functionality for unit compatibility and type checking.
The grammar files are (see details below):
SIUnits.mc4
This is the base grammar of the SIUnits language. It introduces SI
units and other units that can be derived from them.
This grammar defines SI units with their prefixes and complex,
composed SI units allowing us to parse simple and derived units
such as m
, km
, km^2
or km^2/VA^2h
.
Some Si units are ambiguous, e.g.,
ms
can be interpreted as metre times second, or as milliseconds.
In ambiguous cases, the interpretation with the prefix is preferred, e.g.,
by the typecheck.
To write metre times second, m^1s
can be used.
SIUnitLiterals.mc4
The SIUnitLiterals allow to describe concrete values, such as
5km
or 23.4m/s
that can be used within ordinary expressions.
For that purpose, they combine a NumericalLiteral resp.
SignedNumericalLiteral from the
MCCommonLiterals.mc4
grammar with a SIUnit from the SIUnits grammar.
SIUnitLiterals allows us to parse literals of the following forms.
Literals in combination with a unit. The unit may be of any form allowed
by the SIUnits.mc4 grammar, i.e. including unit expressions:
* Integer with unit: 5km
or 5 km/h
* Long with unit: 5l km
* Float with unit: 5.0km
or 5.0 km
; The space is obligatory for Farads to
avoid confusion with floats (5.0 F
)
* Float (explicit) with unit: 5.0f kg
or 5.0F kg
* Caution: A space is obligatory for liters to
avoid confusion with longs (5 l
)
Standard unitless literals are parsed as Literals as provided by the
MCCommonLiterals grammar:
* Unitless integer: 5
* Unitless long: 5l
or 5L
* Unitless float: 5.0
* Unitless float (explicit): 5.0f
or 5.0F
SIUnitTypes4Math.mc4
The SIUnitTypes interpret the SIUnits as a type in the MontiCore type universe,
such as [m]
.
Therefore, the grammar extends
SIUnits.mc4
and de.monticore.types.MCBasicTypes.mc4.
A SIUnitType implements the MCType and can therefore be used wherever a type is used,
e.g., when a variable is declared or a method parameter is typed.
The idea of this grammar is that the types are used in a mathematically ideal world (e.g., also in early stages of systems development), where no limitations on the type of numbers plays a role yet. This is opposed to the below-defined SIUnitTypes4Computing grammar, where concrete types of numbers can be added as well.
SIUnitTypes4Computing.mc4
SIUnitType4Computing interprets the SIUnits as a generic type that
have exactly one parameter, which is of a numeric type.
This allows one to specify SI unit and underlying numeric type, such as [km/h]<float>
or [m]<long>
in combination and use that as type.
This is more oriented toward computation because it allows one to model
numeric restrictions. Please note that e.g. [km]<int>
has a different behavior that
[m]<int>
in terms of rounding and overflow.
Technically, SIUnitType4Computing defines a type expression with an SI unit as a generic type (not SymTypeOfGenerics) and a MCPrimitiveType as an argument. The primitive part must be a numeric type.
Remark: while the syntax of SI Units is very carefully standardized, the use of SI Units as type definitions, and especially as generic types is an invention by the MontiCore team. Alternative syntaxes are definitely possible.
TypeCheck
There are two main approaches how to handle si units: at compile-time
or at runtime. The former variant is highly preferable as all unit-related
checks and conversions are performed at compile-time and unit information
can be thrown away completely at runtime. This approach is only feasible
if our type system is static, i.e., it is not possible to change the unit
of a variable at runtime.
A variable with type [km/h]
will always be interpreted as [km/h]
.
Assignments of compatible variables, e.g., typed as [m/s]
lead
to an implicit conversion by the compiler or code generator.
If static typing is not desired in an SI unit-based language and types can be altered dynamically at runtime, type information needs to be carried at runtime. Consequentially, all the compatibility checks and conversions need to be performed by the generated code. This approach is much less efficient, and we recommend to prefer static typing instead if possible.
- With the TypeCheck class SymTypeExpressions
(MontiCore's internal form of storage for types) can be
synthesized from a
MCType
and returns a SymTypeOfSIUnit or a SymTypeOfNumericWithSIUnit for aSIUnitType
orSIUnitType4Computing
. - With the TypeCheck class SymTypeExpressions
can be derived from a
Literal
or anExpression
and returns a SymTypeOfSIUnit or a SymTypeOfNumericWithSIUnit for expressions containingSIUnits
. The resulting types of the most common expressions is defined as follows:- The type of a SIUnitLiteral is a
SymTypeOfNumericWithSIUnit
as a combination of the types of its NumericLiteral and its SIUnit. \ E.g.typeOf(3.2f m) -> [m]<float>
- The multiplication of two
SymTypeOfNumericWithSIUnit
results in the combination of the multiplication of their numeric parts and of their SI unit parts. \ E.g.typeOf(3 m * 1.5 s) -> [m^s]<double>
- The division of two
SymTypeOfNumericWithSIUnit
results in the combination of the division of their numeric parts of their SI unit parts. \ E.g.typeOf(3 m / 2l s) -> [m/s]<long>
- The addition of two
SymTypeOfNumericWithSIUnit
results in the combination of the addition of their numeric parts and their SI unit parts. The addition of the SI unit parts is only defined if the units are compatible (same base units with different prefix) and results in the smaller unit. \ E.g.typeOf(3 m + 2 s) -> undefined
(results in an error) \typeOf(1.25 h + 30 min) -> [min]<double>
\typeOf(30 min + 1.25 h) -> [min]<double>
- Subtraction and modulo are defined in the same way as the addition.
- The type of a SIUnitLiteral is a
The TypeCheck classes here are:
- SIUnitBasic
which defines the SymType of a single SI unit basis, e.g.
m
orms^2
, but notK^2m
, as it contains two SI unit basis. - SymTypeOfSIUnit which contains a numerator and denominator list of SIUnitBasic.
- SymTypeOfNumericWithSIUnit which contains a numeric type and a SymTypeOfSIUnit.
For more info, see TypeCheck.
Runtime: Executing Calculations
While the previous part described, how SI Units are used in grammars and thus mainly for the concrete and abstract syntax of a modeling language, we here also discuss possibilities to execute calculations.
There are mainly two approaches, while the latter also comes in variants:
-
Due to the strong typing system that is provided, the generator in principle can throw all type information away after consistency has been checked. This is efficient and type safe, given a correctly implemented type checker. All needed scalar transformations can be explicitly added during the generation process, SI Unit information is not present in the values anymore. (efficient compilers do exactly that with their datatypes today). I.e.,
[km/h]<int> x = 100 * 30m / 100sec
would be translated toint x = (100 * 30 / 60) * 3600 / 1000
where the later multiplication and division handles the scaling. (Further optimization is not needed here, but handled by a subsequent compiler.) -
In some target languages, such as C, C++, or python, strong typing doesn't really exist, and people have implemented frameworks that make units explicit. I.e., instead of storing a value for
[km/h]<int> x
in form of a simpleint
value, an object with attributes likeint value
,String siUnit
, andint scaleFactor
are carried around at runtime. Unit is such a framework for Java.- As a side effect, the SI Units are themselves encoded as values (whereas in our language, we treat them as types, what they actually are) and thus the typing system is encoded explicitly — a common approach, when the programming language doesn't provide a strong typing system.
It is up to the developer of a generator to decide which solution to take.