Programming language Chora: Expressions
Similarly to other programming languages Chora provides diverse language constructs permitting you to perform calculations, in particular arithmetic-logical operations just at the runtime of your GUI application. These, so-called expressions are built by simply combining operands with operators. For example, the expression current = voltage / resistance calculates a new value for the operand current by dividing the operand voltage by the value of the operand resistance.
The possibility to evaluate expressions is essential for the dynamic behavior of every GUI application. With Embedded Wizard you can implement such functionality without depending on any third party development tool or compiler. Moreover since Chora is particularly platform independent, the expressions implemented in Chora will work on every target system. With this chapter we intend to introduce the typical aspects relating to the formulation and usage of expressions. More details are found in the following chapters.
The precise effect of an expression depends on the data type of the involved operands and the operators used to combine them. For example, the addition operator + (plus) used in context of two int32 operands has other function than when used with e.g. string operands. In the first case, the operator + will result in an arithmetic addition of the both int32 operands. In turn, in the second case, the operator results in a new string concatenated from the both original string operands:
var int32 a = 13; var int32 b = 69; var string c = "13"; var string d = "69"; trace a + b; // 82 trace c + d; // "1369"
Knowing the data type of the operands involved in an expression, the Chora compiler can select the appropriate operation to perform. However, if you try to combine operands of incompatible data type, the Chora compiler will report an error.
The simplest form of an operand is the literal. In Chora, every data type provides its particular literal notation permitting you to specify conveniently fixed values of the respective data type direct within an expression. For example, the number 1369 is a literal of the data type int32. The keywords true and false, in turn, are literals of the data type bool. Knowing this, you can use the literals in expressions:
var int32 a = 13; var int32 b = ( a * a ) + ( 69 * 69 ); // b = 4930 var bool c = true; var bool d = false;
More complex data types are represented through more complex literals. The data type point, for example, is intended to store a position with its individual X and Y components. Accordingly, its literal has to include two fixed numbers for the both components. Assuming, you want to specify a point with coordinates X=13 and Y=69 then the literal <13,69> (including the angle brackets) is used:
var point a = <13,69>; var point b = a + <12,51>; // b = <25,120>
When working with text you will often use the corresponding string literal, which contains the original text enclosed simply between a pair of "..." (double quote) signs. For example:
var string a = "Hello"; var string b = a + " world!"; // b = "Hello world!"
Literals are thus nothing else than simple constant, immutable values representing an operand of the respective data type. The description of the literal syntax is found in the chapter corresponding to the particular data type.
Variables, arrays and properties
Other operands you can involve within an expression are properties, variables, arrays, method local variables, method local arrays and method parameters. At the runtime, when the expression is evaluated the actual value of the respective operand (e.g. the value stored in a variable) is used. For example:
// The actual 'rect' value of the property 'Bounds' is evaluated // and combined with the 'point' literal <0,100> resulting in the // original 'rect' value being moved 100 pixel in vertical direction. Bounds = Bounds + <0,100>; array string locArray; locArray = "Hello"; locArray = " world"; // The actual values of the array elements 0 and 1 are evaluated // and, since they are strings, concatenated. var string text = locArray + locArray; // text = "Hello world"
Please note, accessing a property causes the corresponding onget or onset method being called - depending on whether the property is read or modified. In turn, accessing an ordinary variable or array affects the content in the associated memory only. To directly access the memory associated with a property the keyword pure has to be used:
// Access the property 'Bounds' by calling its 'onget' method. var rect r1 = Bounds; // Read the memory associated to the property 'Bounds' without // the 'onget' method being called. var rect r2 = pure Bounds;
Operators determine the operation to perform on the affected operands. The operator + (plus), for example, calculates the sum of two operands. In turn, the operator / (slash sign) calculates the division of them. As mentioned at the beginning of this chapter, the exact function of an operator depends on the data type of the involved operands. Accordingly, the operator +, when used with two string operands will result in the concatenation of the both operands instead of the sum calculation.
You should consider, that every operator is optimized to operate on operands of particular data type. When applied with operands of incompatible type, the operator is not able to perform any meaningful operation. In such case, the Chora compiler will report an error. For example, the addition of a point and bool operand doesn't make sense and thus raises an error message just at the compilation time:
var point a = <13,69>; var bool b = true; trace a + b; // ??? Error: Don't know how to add 'point' and 'bool'
The assignment is a special case of operation intended to assign the value of the right operand to the left destination operand. Similarly to other programming languages, the assignment is represented by the operator = (equal sign). For example:
// Assign the 'rect' literal <0,0,100,50> to the property 'Bounds'. Bounds = <0,0,100,50>;
Besides the simple assignment operator = Chora also supports a set of compound assignment operators. These operators combine the assignment = with another regular operation. When executed, the operator first performs the regular operation with the both operands found on its left and right side and then, in second step, assigns the result of the operation to the operand on its left side. One example is the addition assignment operator +=. It implies a regular addition + operator and the assignment operator. As such it can be considered as abbreviation for the more complex operation a = a + b:
var string str = "Hello"; var int32 num = 1369; str += " World"; // str = "Hello World" num += 1; // num = 1370
Depending on the data type of the operands involved in an operation, the operation may produce ambiguous results. In such case the Chora compiler tries to promote the involved operands implicitly to avoid so potential side effects. If the implicit type conversion is not possible, an adequate warning or even error message is reported. For example, the addition of an int32 and float operand can result in an int32 or float value. To avoid data lose the Chora compiler converts in this case the integer operand to float automatically before performing the addition. The result of the operation is consequently float:
var float a = 13.69; var int32 b = 1251; // Implicit conversion of 'int32' operand to 'float' var float c = a + b; // 1264.69
If the results of the implicit conversion is not what you expect, you have to apply in such case an explicit type conversion on one or on both of the operands instructing so Chora to convert the operands just before operating on them. In this manner you can force the addition of an int32 operand with a float operand to be performed with integer precision instead of floating-point. To convert an operand in another data type you prefix the operand by the desired data type enclosed between a pair of (...) (parentheses):
var float a = 13.69; var int32 b = 1251; // Convert the 'float' operand 'a' in 'int32' and perform an integer addition var int32 d = (int32)a + b; // 1264
Parentheses (...) and nested expressions
In complex expressions enclosing a partial expression in parentheses ( ... ) causes this partial expression being evaluated first. Its result can thus be used in the overall expression as operand. If the parentheses are missing, the order of evaluation within a complex expression is determined by the so-called operator precedence. For example:
var int32 result1 = 123 + 456 * 789; // result1 = 359907 var int32 result2 = ( 123 + 456 ) * 789; // result2 = 456831
An expression can include an object by explicitly referring the object by its name. Assuming, the component you are currently implementing contains a Text view named CaptionView, then the following expressions evaluate the properties of this object:
// Evaluate the property 'String' and 'Bounds' of the object // 'CaptionView' existing in the actual component. var string str = CaptionView.String; var rect bounds = CaptionView.Bounds;
In case of one object being nested (embedded) within another object, you address the desired object by specifying the names of both objects separated by the . (dot) sign. For example:
// Access the object 'CaptionView' nested within the object 'Menu'. var string str = Menu.CaptionView.String;
An expression can contain invocations of methods. The expression calculates then with the value returned from the method. If the method expects parameters, you specify them as individual expressions enclosed between a pair of parentheses ( ... ) following the method name. The following example calls the method FindViewAtPosition() in context of this object:
// Invoke the method 'FindViewAtPosition' with the given parameters. // The value returned from the method is stored then in the local // variable 'view'. var Core::View view = FindViewAtPosition( null, <10,20>, Core::ViewState);
You can call the method also in context of another object. Assuming, the component you are currently implementing contains a Text view named CaptionView, then the following expression invokes a method of this object:
// Get the area occupied by the text. var rect r = CaptionView.GetContentArea();
Method super() call
When you override a method it is often necessary to invoke from your implementation the original, inherited version of the method. This can be achieved by using the keyword super. Similarly to the invocation of an ordinary method, you append a pair of parentheses ( ... ) after this keyword. Eventual parameters are specified between the parentheses:
// Invoke the inherited version of the actual method and pass // the both parameters to it. var rect area = super( Bounds, true );
Constants and resources
If your project contains constants, you can involve and evaluate them within an expression. To achieve this you simply use the complete name identifying the constant. For example, assuming the constant is named Application::VersionText and it is defined with string as data type, then to access the constant you have to involve its name Application::VersionText within the expression:
var string str = Application::VersionText;
Similarly, bitmap and font resources existing in your project can also be evaluated within an expression. Again, to address the resource you have to specify its complete name. This name results then in an object representing the resource. Depending on the type of the resource, the object is of the class Resources::Bitmap or Resources::Font. Assuming your project contains a bitmap resource named Application::Logo, then with following code you access the object representing this resource and e.g. query the dimension in pixel of the bitmap:
var point size = Application::Logo.FrameSize;
Please note, if the constant or bitmap resource is localized for several languages, the right language variant is determined dynamically at the runtime just in the moment when the constant or bitmap resource is evaluated in the expression.
With an autoobject Chora provides a kind of global instance you can access from elsewhere within your application. Autoobjects are thus ideal to provide functionality or store information which should be available globally. To address an autoobject you use its complete name. Assuming, your project contains an autoobject named Application::Device serving as the interface to your device then the following expressions access the autoobject and e.g. invoke its methods:
Application::Device.SetCentrifugeSpeed( 1000 ); Application::Device.StartCentrifuge();
Built-in functions and variables
Besides the above mentioned operators Chora provides an extensive set of built-in functions and variables you can involve within an expression wherever it is appropriate. While the built-in variables provide a convenient interface to query or modify the current state of the application, the functions implement various general purpose operations. For example, the function math_rand() determines a random number from the specified range:
var int32 randomNumber = math_rand( 0, 10 ); // randomNumber = 0 .. 10
The instant constructors provide another set of built-in functions allowing the creation (construction) of operands of Chora own data types. Unlike the literals, the operands are created dynamically using expressions passed in the parameters of the invoked instant constructor. For example, the instant constructor string() creates a new string operand from the given parameters, here the instant constructor converts a number in a string. As such, instant constructors are also useful when converting operands between different data types:
var int32 a = 13; var int32 b = 69; // Create a new 'string' by converting the number 'a+b' // and if the string is shorter than 4 signs, add leading // '0' zero signs. var string c = string( a + b, 4 ); // c = "0082"
Instant properties and instant methods
Many Chora data types expose so-called instant properties and instant methods. In analogy to objects the properties and methods allow you to perform operations on the operand they are applied on. For example, an operand of the data type color stores internally the color information in four separate components red, green, blue and alpha. To access these components, the data type color and thus all operands of this data type provide the instant properties red, green, blue and alpha you can simply evaluate within an expression:
var color clr = #F40033FF; // red = 244, green = 0, blue = 51, alpha = 255 var int32 gray = (( clr.red * 3 ) + ( clr.green * 4 ) + clr.blue ) >> 3; // gray = 97;
With instant properties it is also possible to modify an operand in place:
var color clr = #F40033FF; // red = 244, green = 0, blue = 51, alpha = 255 var int32 gray = (( clr.red * 3 ) + ( clr.green * 4 ) + clr.blue ) >> 3; // gray = 97; // Modify the variable 'clr' through its instant properties. Note the additional // typecast operator to convert the 'int32' value in an 'uint8'. clr.red = (uint8)gray; clr.green = (uint8)grey; clr.blue = (uint8)grey; trace clr; // #616161FF
With instant methods the operations to perform on an operand are even more sophisticated. As with the instant properties above, the instant methods are applied in context of an operand you want to perform the operation. For example, operands of the data type string expose the instant method middle() permitting you to extract a part from the original string operand:
var string str1 = "Hello world"; var string str2 = str1.middle( 6, 5 ); trace str1; // "Hello world" trace str2; // "world"
Property references and indirections
Chora supports the language concept of a property reference permitting the code to access a property indirectly - through a reference. Accordingly, you can create expressions operating with a reference to a property. Finally applying an indirection to a reference (dereferencing it) causes the property to be read or modified. For this purpose Chora provides the reference and indirection operator ^:
// An expression to create a reference to the property 'Bounds' // of the object 'CaptionView' var ^rect result = ^CaptionView.Bounds; [...] // Later access the property using the indirection operator result^ = <100,200,110,220>;
Built-in and user defined macros
Chora provides a set of built-in macros permitting you to evaluate the configuration of the application you are actually prototyping or generating code for. Simplified, when you involve a macro within an expression, the content of the macro is unveiled and used as if you had entered this content in place. You should thus consider that when using macros, Embedded Wizard will report an error if the content of the macro is incorrect for the evaluation in the actual expression context.
Macros can be recognized by the preceding $ (dollar) sign. For example, the built-in macro $ScreenSize expands to a literal of the data type point corresponding to the configured screen size. Thus, wherever your application needs to calculate with the predetermined screen size you can use this macro:
var int32 width = $ScreenSize.x; var int32 height = $ScreenSize.y;
Similarly, your own macros can be evaluated in expressions. For example, if you have defined a macro with the name Revision (containing e.g. 1.0.12) the following expression will evaluate the macro and result in a string:
var string str = "Version $Revision"; // str = "Version 1.0.12"