Charm Language Guide


Keywords


The Charm language contains a number of reserved key words (e.g. if, for and proc) i.e. these are not available for use as variable or procedure names. See words marked in bold in the following grammar syntax definition section for a complete list.

Syntax

Like Pascal and C, Charm is a block structured language with block limits delimited by { } braces.

The source code which appears in a block is said to be in the scope of the block, and is normally indented in order to make the program structure obvious, though this makes no difference to the compiler as all extra white space is ignored.  Like Pascal and C, statements in Charm are separated by a ; delimiter, for example x := 1; y := 2;

The high level language syntax is defined in terms of recursive productions. The following key is used :

  •           {phrase} denotes a phrase which may be repeated 0 or more times.
  •           [phrase] denotes an optional phrase.
  •           (a | b)  denotes a choice of either a or b.

Terminals (language keywords and characters) are shown in bold lower case.  Non-terminals are shown capitalised, and the language start symbol is Start.

Assignment = {val} Object := Expression
Block = { Clause }
Boolean = Logical_and [or Boolean]
Call_proc = Name_ref [( Reference {,Reference} )]
Case = case { Expression Case_list [otherwise : Clause] }
Case_list = {Number {, Number} : Clause Case_list}
Clause Statement {; Statement}
Comparison = :=: | :#:
Constant = Const_sum | + Const_sum | - Const_sum
 | true | false
Const_factor = Number | Float | ( Constant )
Const_list = Name = Constant [, Const_list] ;
Const_set = Name [, Const_set] ;
Const_sum = Const_term [(+ | -) Const_sum]
Const_term = Const_factor [(* | /) Const_term]
Declaration [export] [dynamic] Dec_type | declare Forward  |
                  declare record Name ;
Dec_type = Type Var_list | const Const_list |  list Const_set 
                  record Record_def  define  Type_list | 
                  proc Name [Type Name {, Type Name}) [Type] Block
Digit 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Expression Sum [(l_or | l_and | l_xor | l_sl | l_sr | l_rr | l_clr)
                  Expression]
Else else Unit | else_if Boolean Unit Else
Escape
\ t | n | v | f | r | e | x Hex_digit Hex digit
Escape_sequence \ Escape
Factor ( Expression ) | Number | Float | Object | Call_proc |
                   size ( (Object | Type) ) | address ( Object )
Fields Type Name ; Fields | union { Fields } Fields
Float = (+ | -) Digit . Digit {Digit} [(+ | -) e Digit {Digit} ]
For for Clause step Clause while Boolean Unit
Forward proc Name Ref_proc ; | Type Var_list
Hex_Digit       = Digit | a | b | c | d | e | A | B | C | D | E | F
If if Boolean Unit [Else]
Init_item = Init_list {, Init_list} [,]
Init_list = Name | Constant | String | ( Init_item )
Letter a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p |
                  q | r | s | t | u | v | w | x | y | z | A | B | C | D | E | F |
                  G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V |
                  W | X | Y | Z | _
Logical = Expression Relation Expression |
                  Call_proc Object Comparison (Object | nil) | [ Boolean ] |
                  true | false | not Logical | Name
Logical_and = Logical [and Logical_and]
Module module Module_name { {Declaration} }
Module_name     = Name
New             = new Record_name
Name = Letter {Letter | Digit}
Name_ref        = Name | Module_name . Name
Number = {Digit} | hex {Hex_digit} | bin {0 | 1} | Name
Object = Name | ref Object | array [ [Constant] ] Object |
                  [ Expression ] |  Name_ref {. Name} | this . Name |
Offset inc ( Object ) | dec ( Object )
Record_def = Record_Name { Fields }
Record_name     = Name
Reference = String | Expression | Object | New | nil
Ref_proc [( Type {, Type} )] [Type]
Ref_type proc Ref_proc | Type | array Type
Relation # | = | > | >= | < | <= | <>
Repeat repeat Block Boolean
Return return [Boolean | Reference]
Statement Type Var_list | 
Assignment | Call_proc | If | For | While |
    Repeat | Case | Return | delete Object | Offset | break
String = " {letter | digit | character | Escape_sequence} "
Sum = Term [(+ | -) Sum]
Start           = { import { Name .} Module_name ; } Module
Term = Factor [(* | /) Term]
Type char | int | boolean | real | ref Ref_type |
                  array [ Constant ] Type
Type_list       = Name = Type , Type_list | Name = Type ;
Unit = Block | Statement ;
Var_list  Name (; | , Var_list) | Name = Init_list (; | ,
 Var_list) 
While while Boolean Unit

Note that additional type checks and range restrictions are applied by the compiler during the compilation process, for instance use of Name in the above productions usually implies a name that has been previously declared and is of compatible type.

Comments

Comments may be placed anywhere within Charm source code, and are delimited on both sides by the | character.  A newline is not allowed within a comment :-

| This is a valid comment               |

| This is not 
      a valid comment |
unless the comment directive is used :


directive "comment"
{
    export proc leave_it_out ()
    {
    }
}
in which case the comment block ends when the curly brace nesting level drops back to the level the directive was declared at.

Modules


Each Charm program is composed of modules. The program author must supply at minimum a module containing the program ~start procedure. The linker will add in the required startup and run time library system modules.

Each source code .src file presented to the compiler must contain a single module preceded by imports of all module dependencies.  The module name follows the module keyword e.g.

module MyModule { }

This name should be the same (apart from case) as the name of the containing file so that module imports can be resolved.

When referring to an exported declaration, a Charm source file should import the module and qualify references to the declaration with the module name e.g. MyModule.myproc.

It is not necessary or permitted for a module implementation to qualify references to data or code for its own module (the compiler treats unqualified names as belonging to the module being compiled).

Modules may contain dynamic content. Dynamic member variables can be accessed from procedures declared with the
dynamic keyword attribute  via the implicit this parameter (implicit in the sense that callers do not have to explicitly provide it and the callee method signature does not have to explicitly declare it). Instances of dynamic modules (i.e. objects or classes) can be created on the heap (using the new keyword), on the stack or as module level member variables.

Numbers


Charm supports floating point, decimal, hexadecimal and binary numbers e.g.

  • 1.23 = one point two three
  • 101 = one hundred and one
  • hex 101 = two hundred and fifty seven
  • bin 101 = five

Note that hexadecimal numbers need not be necessarily prefixed with a leading zero e.g. hex abcd is valid.

Numbers may be signed, and floating point numbers may additionally specify an exponent e.g. -3.456e-3.

Charm also supports ASCII character constants e.g. 'A' has the value hex 41 or decimal 65.

Constants


Sometimes a number appears several times during the course of a program, for example the maximum number of data items a program can handle.  It is good practice to use a constant declaration introduced using the const keyword to define this number e.g.

const  MAX_ITEMS = 3;                | max data items |

Now, the constant identifier MAX_ITEMS can be used in place of the number e,g.

x := MAX_ITEMS - 1;

This has the benefits of making the program simpler to read and understand, and allows the number of items to be easily changed if necessary.

Constant definitions may contain expressions which include other constants e.g.

const   MAX_ITEMS = 3,
        TWICE_MAX_ITEMS = MAX_ITEMS * 2;

will replace the constant TWICE_MAX_ITEMS with 6 wherever it is used. Constants may also be declared as real numbers, or as either of the boolean values true or false.

Sometimes a program requires to use a disjoint list of constant identifiers, for example as status return values. The list keyword can be used to introduce such a list.  The compiler will automatically assign ascending constant values to the constants in the list, starting from 1 e.g.

list PASSED, FAILED, IN_PROGRESS;

is equivalent to defining constants PASSED, FAILED and IN_PROGRESS with values 1, 2 and 3 respectively.

Variables


At present, Charm supports four basic data types :-

  • char - character
  • int - integer
  • boolean - true or false
  •         real - floating point

Variables may be declared to be of one of these basic types e.g.

int x;
char   y;
boolean b;
        real
r;

Each variable is given a name with which it may be referred to from the source code.  Variable names can be of any length, but must not clash with reserved keywords used by the compiler and the assembler.  The name and the type of a variable are examples of variable attributes.  The sizes of variables defined by the four types are 32 bits for int, 8 bits for char8 bits (though only 1 bit is logically required) for boolean and 96 or 64 bits for real.

In general, variables can be initialised e.g.

int x = 3;
boolean flag = false;
real    r = -1.23e4;

both declares variables xflag and r, and gives them initial values of 3, false and -12300 respectively (if variables are defined at the procedure rather than the module level, use ':=' to initialise them at run time instead of '=' to initialise them at compile time).

Arrays


There are many times when it is useful to define a variable that contains more than a single object of a particular type.  In Charm this can be done using the array keyword e.g.

array [10] int x;

defines x to be an array of ten integers.  When x is used as part of an expression, it becomes necessary to refer to which of the ten integers is desired.  This is done using an index expression which follows the variable name and is enclosed between [ ] parentheses i.e. x[i] refers to the ith integer in the array.

In Charm, the array index is allowed to run from 0 to one less than the bound of the array i.e. in the above example x[3] refers to the fourth integer in the array.  An attempt to refer to x[10] may generate a run time error since x[0] is the first element of the array, and x[9] is the last.

It is possible to define arrays of more than one dimension e.g.

array [2] array [3] array [4] int xyz;

defines xyz to be a three dimensional array which contains 2 x 3 x 4 = 24 elements.  It is necessary to specify three indices when referring to an element of the array e.g. xyz[0][1][2] or xyz[i][j][k].

Arrays may be initialised by a comma separated list enclosed in parentheses e.g.

array [] int x = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Note that in this example the size of the array (i.e. 10) has been omitted as it can be deduced from the number of initialisers. If required, a constant may then be defined for use in say iterating over the array which in general terms is the size of the array divided by the size of the array type. In this particular example this would be:

const SIZE_ARRAY_X = size (x) / size (int);

Note that each "column" of an array is initialised in this way e.g.

array [3] array [4] int = ((1, 2, 3, 4),
                           (2, 4, 6, 8),
   (3, 6, 9, 12),
                           );

Arrays are often accessed from within for loops where the control or count variable is used to index the array.

Records


As well as declaring variables which contain more than one object of a particular type, it is also possible to define variables which contain objects of different types.  In Charm this can be done using the record keyword.

The number and type of objects a particular record contains is defined by a record definition e.g.

record Rec_xy
{
    int field_x;
char field_y;
}

defines a record type called Rec_xy.  A variable of aggregate data type Rec_xy is defined as follows :-

Rec_xy z;

In order to refer to a specific field of the record, the . specifier is used e.g. z.field_x refers to the integer field field_x and z.field_y refers to the character field field_y.

The size of a record in bytes may be determined using the size keyword e.g. size(z) has the value 8.  Note that record fields are always aligned on word boundaries so that they can accessed correctly (except for consecutive char variables), and record size is always adjusted to a whole number of words (on the ARM one word contains four bytes). Note that the size keyword can also be used to find the size in bytes of the char, int and real basic types.

Records may be initialised by a comma separated list enclosed in parentheses, in the same way as arrays e.g.

Rec_xy z = (0, 1);

It is possible both to define arrays of records, and fields of records which are arrays e.g.

array [2] Rec_xy xyrec = ((1, 2),(3, 4));

defines an array of two records of record type Rec_xy.  The second field of the record in the second element of the array is xyrec[1].y and contains the value 4.

For advanced users, the union keyword introduces a set of record fields that occupy the same storage locations in the record i.e. the fields overlay each other. Usually a field before the union defines the type of the record which is used to select which is the correct record field from the union to use. The size of the record is the size of the fixed fields plus the size of the largest overlaid field.

Pointers


Sometimes it is useful to use a variable which points to data, rather than containing a data value directly.  This is particularly true where the data is part of an aggregate structure such as an array or record.

Rather than passing the whole array or record to a procedure for instance, it is more economical to pass a pointer to it which fits into a single register or memory word.

A variable is defined as being a pointer using the ref keyword.  For example ref int z defines z to point to an integer variable e.g.

int y;
ref int x;
x := y;

sets the value of x to the address of y.  The contents of the variable pointed to by x may be changed using the val keyword on the left hand side of an assignment statement e.g.

val x := 1;

sets the value of y to 1.  It does not affect the pointer held in x.

It is possible to compare addresses for equality or inequality in a boolean expression using the :=: and :#:  comparison operators respectively, hence x :=: y is true if x has been set to point to y.

Note that this is different to the comparison x = y where it is the values of the variable pointed to by x and the variable y which are compared i.e. after

int x := 2;
int y := 2;
ref int z := y;

then z :=: x is false, but z = x is true.

There is a special pointer value called nil which is used to indicate that a pointer is not pointing at any variable i.e. it is uninitialised.  An attempt to dereference a nil pointer will cause a run-time error, but a pointer may be compared against nil e.g.

if z :#: nil then ...

Note that on the RISC PC, nil has been assigned the value hex 80000000. This is not a legal memory address, and an attempt to dereference it will result in a data abort. This is by far the most common type of abort in a Charm program which is fortunately relatively easy to diagnose and correct.

For low-level address manipulation, the address keyword can be used to obtain the address held in a pointer as a number, and the [ ] parentheses may be used to convert a number to a reference prior to assignment to a ref variable e.g.

ref int z := [hex 100];
z := [address(z) + 4];
val z := 1;

makes z point first to absolute memory address hex 100, and then adjusts it to address hex 104, before storing the value 1 at this address. Be careful using this language feature as it provides unchecked access to memory.

It is possible to use ref variables to point at arrays and records e.g.

Rec_xy xyrec;
array [10] int x;
access (xyrec, x);
proc access (ref Rec_xy p1, ref array int p2)
{
p1.field_y := 2;
p2[1] := 3;
}

In Charm, records and arrays must always be passed to procedures by reference.  Also, records cannot be declared as local to procedures, though pointers to them can.

Strings


Conventionally, Charm supports strings as arrays of characters in which the last element contains a terminating 0 e.g.

array [4] char cat;
cat[0] := 'C';
cat[1] := 'a';
cat[2] := 't';
cat[3] := 0;

will assign the string "Cat" to cat.  There is a simpler way of initialising strings using a string of characters enclosed in double quotes.  In Charm this is notionally of type ref array char e.g.

ref array char cat = "Cat";

The following procedure shows how to access a string :-

proc output_string (ref array char string)
{
for int i := 0 step inc (i) while string[i] # 0
Out.vdu.ch (string[i]);
}

Strings may contain escape sequences starting with the escape character \:

  • \ - backslash
  • " - double quote
  • t - tab
  • n - newline
  • v - vertical tab
  • f - form feed
  • r - carriage return
  • e - escape character
  • xhh - hexadecimal character encoded by hh e.g. \a0 for hard space

The Charm String library module dynamically wraps a low level Charm string array in an object and by managing its allocation and deallocation on the heap or stack provides a useful abstraction which also allows multiple string operations to be chained into a single statement.


Expressions


Expressions can appear in the Charm language in a number of places.

Numeric expressions evaluate to a number, and appear on the right hand side of assignment statements, and as parameters in procedure calls.  Numbers,  int and char variables can appear in numeric expressions (addition, subtraction, multiplication and division is also possible for real variables).  They are generally combined using dyadic operators.  If a and b are expressions :-

  • a       + b is the sum of a and b
  • a      - b is the difference of a and b
  • a      * b is the multiplicative product of a and b
  • a       / b is the integer result of a divided by b
  • a   mod b is the integer remainder of a divided by b
  • a  l_or b is the bitwise or of a and b
  • a  l_and b is the bitwise and of a and b
  • a  l_xor b is the bitwise exclusive or of a and b
  • a  l_clr b is the bitwise and of a and not b
  • a  l_sl b shifts a left by b bits
  • a  l_sr b shifts a right by b bits
  • a  l_rr b rotates a right by b bits

Note that the first four dyadic operators (addition, subtraction, multiplication and division) may replace one or both operands with a real variable or constant, in which case the result is also real.

The l_clr operator is used to clear bits in a flags variable in the same way that l_or is used to set them.
The only monadic numeric operator is the unary minus e.g. -x which is equivalent to the dyadic expression 0 - x.

Order of evaluation may be forced using ( ) parentheses, otherwise the natural order of operator precedence is (highest first) :-

  • *, /
  • +, -
  • l_or, l_and, l_xor, l_clr, l_sl, l_sr, l_rr

with clauses of expressions joined by operators at the same precedence level evaluated left to right e.g.

  • (x + 1) * 3    has the value 12 if x has the value 3.
  • x l_clr 1  has the value 2  if x has the value 3.

Boolean expressions evaluate to true or false, and appear as tests for conditional statements e.g. if and loops e.g. while.  They generally contain boolean clauses in which numeric expressions are compared with each other using a comparison operator e.g. >=.

Clauses may be combined using the dyadic and and or logical operators.  The only monadic logical operator is not which negates the truth value of the boolean expression i.e. false becomes true and vice versa.

Order of boolean evaluation may be forced using [ ] parentheses.

Statements


Statements appear within procedures and are often grouped to be executed in sequence separated by semicolon characters ; as a unit as part of selection or iteration constructs using { } parentheses. Conventionally statements between curly brackets are indented to help show flow of control. Variables declared within the sequence cannot be accessed outside the boundaries or scope of the bracketed group. Charm supports the following types of statement:-

  • Assignment
  • Conditional
  • Case
  • Loops

These are discussed individually in more detail below.


Assignment Statements


Variables can be assigned values when they appear on the left hand side of an assignment statement e.g.

x := 2;

assigns the value of x to be 2.  As mentioned earlier an assignment statement can be combined with the declaration of a variable e.g.

int x := 2;

If a variable appears on the right of an assignment statement, then its value is used to calculate the expression which appears there e.g.

x := x + 2;

adds 2 to the current value contained in x.

Note that assignment takes place at run time, whilst variable initialisation takes place at compile time.

Procedures


Each Charm module can contain a number of procedures.  Normally procedures contain executable source code, and are called from other procedures.  Each procedure is given a name which follows the proc keyword e.g.

proc test () {}

and a parameter list. Parameters are defined in a procedure declaration within the enclosing comma separated parameter list brackets e.g.

proc test (int x, char y)

If the procedure test is called from another procedure by the call :-

test (2, 3);

then inside procedure test, x has the value 2 and y has the value 3.

A procedure is allowed to return a single result.  If a procedure needs to do this, the type of the result follows the procedure or parameter list if present e.g.

proc sum (int x, y) int

The return keyword can be used within the procedure to setup the result of the procedure e.g.

proc sum (int x, y) int
{
return x + y;
}

If z is defined as an int then the assignment statement :-

z := sum (2, 3) + 1;

will store the value 6 into z.  Here the value returned by the sum function (or procedure which returns a result) is substituted in the expression on the right hand side of the assignment statement.

Procedures with a special meaning are:

  • export proc ~start ()
  • export proc ~startup ()
  • export proc ~shutdown ()
  • export dynamic proc ~new ()
  • export dynamic proc ~delete ()

Each program contains one ~start procedure which may optionally be passed the command line argument count and list, and any number of ~startup procedures which are invoked prior to ~start to permit static initialisation tasks such as dependency injection. The ~new and ~delete procedures may be implemented to allow dynamic module instance construction and destruction respectively. Finally, any ~shutdown procedures are invoked during normal program termination.


Scope


The visibility of a variable to code placed in modules and procedures is known as its scope.  Variables may be local to procedures, modules or may be global in scope.  In general, global variables are to be avoided, as they make it difficult to find where and when the data they contain is changed.  They do have their uses however, particularly if the data they contains remains unchanged while a program is running, or is only changed in one place.

If a variable is defined within a procedure, it is local to that procedure i.e. it can only be used from within the procedure.  The space for the variable is allocated on a shared area of memory known as the stack. When the procedure exits, this space is discarded, and so the value is lost i.e. it is not retained for the next time the procedure is entered.  Charm supports initialisation of procedure variables through assignment statements.

If a variable is defined within a module, but outside any procedure, then it is local to that module.  It may be used from any procedure within the module, but not from another module.  The value of the variable is not lost when control enters or leaves procedures in the module, since the space for it is statically allocated.

If a constant, record or variable needs to be accessible to a module outside the module it is defined in, it can be published by prefixing the variable definition with the export keyword :-

export const ITEMS = 5;
export record R {int f, int g}
export int x;

makes constant ITEMS, record R and variable x accessible from modules that import the exporting module.

By default the scope of procedures is local to their enclosing module.  In order for a procedure to call a procedure in another module, the called procedure must be exported :-

export proc callee (int x, char y) int

If the dynamic keyword precedes proc then the procedure must be called through a variable that contains a reference to the module. In this case an implicit first parameter is passed which is of type module reference and can be accessed using the this keyword. Module level data can be access from the this parameter provided it is also marked as dynamic in which case (of course) it cannot also be statically referenced.

Normally, since Charm is a single pass compiler, it is necessary to write the source code for a procedure which is local to a module before that procedure can be called.  It is possible however to declare a procedure (or record) before the procedure is defined by using the declare keyword.  The syntax is similar to a procedure definition, but does not include parameter names and hence only supports the , parameter separator.  Once a procedure has been declared, it may be called or referenced before it is actually defined e.g.

declare proc local (int);
local (1);
proc local (int x) {}

This facility is particularly useful when calling an inline assembler procedure, which should be coded in accordance with the register passing conventions of Charm (first 4 parameters in r0 - r3 and any result in r0). In this case ensure the procedure name in the assembler section is prefixed with an underscore character _.

Assembler


Note that ARM assembler code may be mixed inline with Charm code using the directive keyword e.g.

directive "inline"
{
    ... assembler source code ...
}

Assembler routines may be invoked from Charm code via labels which are declared as forward procedures (see end of scope section above).

Include files


A compiler or assembler source file or can include a file containing using the include keyword e.g.

include "<Charm$Home>.swi"

will effectively include the contents of file swi from the Charm home directory at the place the include appears. Usually the include appears within an inline section of a compiler file to define RISC OS related assembler definitions.

Conditional Statements


Charm supports conditional execution of source code through the if,  else_if and else keywords.  In its simplest form, the block of code following an if boolean expression is only executed if the boolean expression is true e.g.

if x > 1 Out.vdu.str ("x is greater than 1").nl ();

A simple boolean expression involves the comparison of two expressions with one of the following operators :-

  • a = b true if the value of a equals the value of b
  • a # b true if the value of a is not equal to the value of b
  • a <> b alternative notation for a # b
  • a > b true if a is greater than b
  • a < b true if a is less than b
  • a >= b true if a is greater than or equal to b
  • a <= b true if a is less than or equal to b

Compound boolean expressions can be built from simple boolean expressions using the logical operators notand and or.  If A and B are boolean expressions :-

  • not A is true if A is false
  • A and B is true only if both A and B are true
  • A or B is true if either A or B is true

Normally and takes precedence over or e.g. A and B or C is true if A and B are true, or if C is true.  Square brackets can be used to parenthesise compound boolean expressions to force them to be evaluated first e.g. A and [B or C] is true if A is true and if B or C is true.  Boolean variables can both be assigned to and used in conditional expressions e.g.

int y := 7;
boolean x := not [y > 1 and [y <= 5 or y = 7]];
if x { y := 0; }

Note that not is a monadic operator (i.e. it operates on a single expression), while and and or are dyadic operators (they combine two expressions).

The compiler will automatically short circuit evaluation of compound boolean expressions where possible.  If A is found to be false in A and B then B is not evaluated since the compound expression must be false.  If A is found to be true in A or B then again B is not evaluated since the compound expression must be true. Be aware of this if calling procedures in boolean expressions that have side effects.

The else keyword can be used in conjunction with and following the if keyword to specify what should happen if the boolean expression evaluates to false e.g.

if x > 1 Out.vdu.str ("x is greater than 1").nl ();
else 
Out.vdu.str ("x is less than or equal to 1").nl ();

Finally the else_if keyword can be used in conjunction with and following the if keyword but before the optional else keyword as a contraction of if and else without changing the block indentation level e.g.

if x > 1 Out.vdu.str ("x is greater than 1").nl ();
else_if x = 1 Out.vdu.str ("x is equals 1").nl ();
else Out.vdu.str ("x is less than 1").nl ();

Case Statements


Sometimes it is necessary to select a block of code depending on the value of a variable or expression.  Although if statements can be used to do this, a more efficient way is to use a case statement introduced by the case keyword e.g.

case x
{
1:  Out.vdu.str ("x is 1"); 
3:  Out.vdu.str ("x is 3"); 
otherwise: 
    Out.vdu.str ("x is neither 1 nor 3"); 
}

The expression following the case keyword must evaluate to an integer.  In addition, only numbers or constants are allowed before the : at the start of each case clause.

Note that the otherwise keyword is optional.  If present, it must be the last clause in the case statement.  The block of code which follows will be executed if no other match can be found.

Loops


Charm supports standard high level language looping constructs which allow an enclosed block of source code to be executed a number of times.

The simplest loop is the while loop introduced by the while keyword e.g. :-

while true Out.vdu.str ("Hello world"). nl(); 

The loop is only executed while the boolean expression evaluates to true (in the above example the loop is executed continuously i.e. it is an infinite loop).

Occasionally it is more convenient to place the loop exit condition at the bottom of the loop rather than the top. This is implemented in Charm using the repeat keyword e.g. :-

repeat { Out.vdu.str ("Hello world"); } Riscos.key_available ();

will loop until the function check_keyboard returns a true value to indicate a key has been pressed.  A repeat loop differs from a while loop in that it is always executed at least once.  Also the loop exits if the boolean expression is true, rather than false.

Often a loop must be executed a fixed number of times.  The most versatile Charm loop construct is the for loop e.g. :-

for int i := 1 step inc(i) while i <= 10 { Out.vdu.str ("Hi").nl (); }

Here the variable i is used to count from 1 to 10.  The clause following the for keyword i := 1 initialises the value of i to 1.  The inc(i) clause increments its value by 1 at the end of each pass through the loop  (dec(i) would decrement i by 1).  Finally, the i <= 10 clause checks the value of i at the top of the loop, and exits the loop if i is greater than 10. The result is that the "Hi" line is output ten times to the current output stream.

The clauses in for loops may contain more than one statement separated by semicolons, and can be quite complex e.g. :-

for int i := 0; int j := 0 step i := i + j; j := j + 2 while i < 10 or j < 100

The break statement allows immediate exit from an innermost loop at any point within it e.g.

while true if Riscos.key_available () { break; }

will exit the while loop if a key is pressed.  It is also possible to leave a procedure from within a loop using the return statement e.g.

while true if Riscos.key_available () { return; }

Writing a Program


The object of the Charm utilities is to allow programs which run on ARM platforms  to be written and developed. There are a few stages involved in producing a working Charm program.

Firstly it is important to have Charm correctly installed.  It should be possible to build and run the demos extracted from the installation archive.

Choose a name for the program and use the supplied !NewProject application to create a project directory using drag and drop.

Use the editor to modify the Charm project source code file in the src sub-directory.  If required create additional source files and add them to the project 'build file in dependency order.  If additional library modules are needed import these.  Note that if desired, other editors may be used e.g. EditStrongEd or Zap to name a few.

Build  the project using the Charm shell.  Correct any compilation errors until a clean compilation is achieved.  

The 'build file might contain the following :-

module myproject
module mymod1
mdoule mymod2
program myprog

indicating that the program myprog is to be created from object file modules mymod1 and mymod2. Note that the program and run time library object files program and rtl will be automatically included by the Charm linker, since they contains language support routines necessary for any Charm program to start and run correctly.

Use the relocatable keyword in the 'build file if a RISC OS module is required. In this case the module header object file lib.module or an equivalent project customised file must also always be included.  This is a template file which contains standard Acorn module header information.  

Make a Free Website with Yola.