Expressions are ColdC tokens and operators which, when evaluated, will perform their functionality and take on a value after completion (also known as the return value). If they evaluated without error, the return value is a standard ColdC data type. If an error occured, the return value is an error data type, and execution of the method is halted. For more information on errors see the section on Errors.
All ColdC data has a type and a logical truth value it will take on when evaluated as an expression. Each data type is explained in the section Data Types.
ColdC provides two kinds of variables, local variables and
object variables.
Local variables are used within methods to temporarily store
data. In order for a local variable to be used it must first be declared
at the top of the method in a var declaration (see section
Methods and Method Structure). Object
variables are defined on an object, and can be used to store data
for an indefinite period of time. More information on Variables can be
found in the section Variables.
When a variable is evaluated as an expression, its value is that of
its contents (the data stored in the variable name). Initially variables
will contain the integer data value of zero (0).
To set the contents of a variable, use the assignment expression, which has the following syntax:
variable = value;
This assigns the value value to the variable variable.
The values of a method's local variables are specific to the instant it
is being processed. When the method is invoked at another time all
of the local variables begin again with the value of zero (0).
Some examples of variable expressions follow:
When evaluated, this expression has the value of three (total = 3;
3).
If the variable total was not previously assigned this expression would evaluate to three (total + 3;
3), otherwise it would be three plus the previous value of the
variable total.
If the variable is a local variable, it must be declared at the top of the method. If it is not declared at the top of the method, the interpreter will assume it is an object variable. In the case that both a local variable and an object variable have the same name, the local variable will take precedence and be used in the method. In order to retrieve and set data on the object variable you must use the functions get_var() and set_var(). These functions need only be used if there is a concern about which variable will be used. If the variable is not declared in the method, the interpreter will always try to find it as an object variable.
Operators are used to perform simple operations on expression values,
such as adding two values together. ColdC provides a variety of
operators to perform arithmetic and logical operations on data. Most of
these operators fall into two syntactical categories: unary
operators and binary operators.
Unary operators act on a single expression value. In ColdC,
all unary operators are single characters which precede expressions.
For example, !a is the logical negation of the value of the
variable a.
Binary operators act on two expression values. For example,
a + b is the sum of the values of the variables a and
b.
Several operators or values are neither unary nor binary.
It is easy to write an expression whose order of evaluation is unclear.
For instance, the expression a - b + c could be parsed as
(a - b) + c or as a - (b + c). To resolve these
conflicts, each operator has two properties: precedence and
association.
Precedence determines whether an operator binds more tightly than
another operator; for instance, a + b * c is equivalent to
a + (b * c) because multiplication has higher precedence than
addition.
Association determines whether operators at the same precedence
level associate left to right or right to left; a - b - c is
equivalent to (a - b) - c because subtraction associates left to
right.
Here is a list of operators grouped by precedence, in order from highest precedence to lowest:
All operators associate from left to right except the[] . -- ++ ! and unary - * / % + - == != > >= < <= in && || ?| = += -= /= *=
&&,
||, and ?| operators, which associate from right to
left. You can always use parentheses (`(' and `)')
to specify the order of evaluation explicitly.
variable = expression
The interpreter assigns the value of expression to
variable.
Arithmetic Operators
ColdC provides seven operators for doing arithmetic, two unary
operators and five binary operators. These operators apply primarily to
integers and floats, but the addition operator also applies to strings. Using
these operators on inappropriate data is an error of type ~type.
The unary - operator takes the negative of an numeric value. The
expression -(3 + 4) has the value -7. The unary
+ operator has no effect on its argument, and is provided only for
completeness.
The binary operator * performs multiplication on its arguments.
The expression (3 * 4) has the value 12. The binary
operators / and % perform division and
modulus, respectively, on their arguments. The expressions
(13 / 5) and (13 % 5) have the values
2 and 3 respectively.
The binary operators + and - perform addition and
subtraction. The expressions (3 + 4) and (3 - 4)
have the values 7 and -1 respectively. The binary
+ operator can also apply to strings and lists, in which case
it performs concatenation; the expression ("foo" + "bar") has
the value "foobar", and the expression (["foo", "bar"]
+ ["baz"]) has the value ["foo", "bar", "baz"].
Furthermore, applying the binary + operator with the string
as one value, and any other data type as the other value will result in the
literal representation of that value being concatenated with the string.
For instance, "list: " + [1, 2, 3, 4] has the value
"list: [1, 2, 3, 4]".
If both sides of a binary arithmetic expression are integers, the
result will always be an integer, even if it could be a float. If one
side of the expression is a float, the result will always be a float.
For instance, 3 / 2 (both integers) would result in 1,
whereas 3 / 2.0 would result in 1.500000.
ColdC provides a number of relational and logical operators.
These operators are primarily useful for expressing conditions, since they
return either true or false, represented by the integers 1 and
0.
The == and != operators test for equality and
inequality, respectively, of their arguments. Two pieces of data are equal
if they have the same type and contain the same value. Equality of strings is
not case-sensitive, but equality of symbols is, so ("foo" ==
"fOo") is true, but ('car == 'CAr) is false. Lists are
equal if all of their elements are equal.
The <, <=, >=, and
> operators test whether their left-hand arguments are
less than, less than or equal to, greater than or equal to, or greater
than their right-hand arguments, respectively. Arguments to these operators
must both be of the same type, and must be of integer, string, or objnum type.
String comparison is not case-sensitive, so ("fooa" < "fooB")
is true, even though the ASCII value of `a' is greater than that
of `B'.
The unary ! operator tests whether its argument is false, thus
acting as a logical not operation.
The operators +=, -=, /= and
*= are a combination of the respective arithmetic operator and
an assignment operator. Using these operators will first perform the
arithmetic operation (such as addition)--with the left value being the
variable and the right value being an expression--and will then assign
the result back to the variable. The arithmetic operators will function
the same as their regular counterparts. Examples (the variable this
will be assumed to start at an integer value of 10):
this += 15; => 25 this += " total"; => "10 total" this /= 2; => 5 this *= 2; => 20
The || and && operators act as logical OR
and AND operators, respectively. The expression (this || that)
is equivalent to this OR that, where the return value of the expression
is this if this is logically true. If this
is not logically true then the return value of the expression is
that.
The expression (this && that) is equivalent to
this AND that, where the return value of the expression is
logically true when both this and that are also
logically true. If either this or that are not
logically true then the return value is false.
ColdC's conditional operators are
short-circuit operators, meaning that the right-hand argument
is never evaluated if the left-hand argument is sufficient to determine
the value of the expression. This is important if the right-hand
argument has side-effects or could cause an error. For instance, because
of this it is possible to have the following expression:
(| dict[key] |) || throw(~keynf, "Key " + key + " is not in the dictionary.");
With this expression if key is in the dictionary dict
then the return value is the related value to key. If it is not,
the error ~keynf is thrown by the index operator ([]critical expression.
Because of this the left value becomes ~keynf, which is logically
false, and the interpreter moves onto the throw function.
The ?| operator is a trinary operator, with the following syntax:
condition ? true-expr | false-expr
The result of this expression is the result of true-expr if
condition is true, or the result of false-expr if
condition is false. See section Data Types for the definition of
truth for ColdC data.
Functions are driver-defined procedures which perform a certain action which ColdC is incapable of (such as opening a network connection), or which the driver can handle more efficiently. A function is called using the function call expression, which has the following syntax:
function(arg1, arg2, ...)
function is an identifier naming the function, and arg1, arg2, ... are expressions. There do not have to be any arguments, but you must include the parentheses even if there are no arguments. The arguments are evaluated from left to right.
As an example, the pad() function pads a string argument with
spaces to a certain length. When evaluated with the string "foo" and a
length of six characters:
pad("foo", 6)
=> "foo "
Note: to better undestand the above example, read A Note on Conventions.
There are driver functions available for a variety of tasks. A comprehensive explanation of the available functions is available in the section Function/Method Reference.
Methods defined on an object can be executed with the method-call expression, which has the following syntax:
receiver.method(arg1, arg2, ...)
You may omit receiver, in which case it is assumed to be the
current object. If receiver is not an object or frob data type,
the interpreter will first attempt to lookup an object name using the
data type's symbol name. If an object exists with that name, the method
is called on that object. If not, or if receiver is an object which
does not exist in the database, the error ~objnf is thrown.
Otherwise, if receiver is an object, then the method is
called on the object. The result is the value returned by the method. If
the method does not return a value, the result is receiver.
If receiver is a frob, then the called on the frob's class
object, with the frob's representation inserted as the first
argument (other arguments are placed after the representation).
method must be either the name of the method, or an expression
which results in a symbol for the name of the method. If method
cannot be found on receiver or any of receiver's
ancestors, then the error ~methodnf is thrown.
arg1, arg2, ... are the arguments for the method. Arguments are evaluated from left to right, as with function arguments. You must include the parentheses around the argument list, even if there are no arguments.
Here are some examples of method-call expressions:
.tell("I don't see that here.");
=> $lynx
$sys.admins();
=> [$lynx, $dancer, $jenner]
([1, 2, 3]).reverse()
=> [3, 2, 1]
$parser.(tosym(what + "_mesg"))(messages)
=> $parser
(<$thing_frob, #[['myname, "coins"], ['amount, 300]]>).name()
=> "300 coins"
In order to prevent incidents of infinite recursion, there is a
maximum calling depth for methods. If calling a method would exceed
the maximum calling depth, the error ~maxdepth is thrown.
If a method overrides another method defined on an ancestor, the
overriding method can call the overridden method using the function
pass(). Arguments to pass() are sent to the
overridden method as if the method were called normally. The return
value of pass() is the normal return value of the overridden
method.
For instance, passing to an overridden method with:
pass("this arg", 2);
Would be rougly equivalent to calling the same method, if it was not
overridden, with:
obj.method("this arg", 2);
When executed in this manner, the overridden method sees the same object, sender, and caller as the current method.
Two mechanisms exist for handling errors in expressions. These are the critical expression and the propagation expression. The critical expression allows you to ignore errors which occur inside an expression. It has the following syntax:
(| expression |)
If an error occurs in expression the interpreter will stop evaluating expression and continue to execute the current method as if it had succeeded evaluating expression. The value of expression will be the error code for the error which occurred.
The propagation expression allows you to indicate that any errors which occur inside an expression are the responsibility of the calling routine. It has the following syntax:
(> expression <)
If an error occurs in expression it will propagate
to the calling method as if the error had originated in the calling method
and not in the current method. Otherwise, the calling method will receive
the error as ~methoderr.
Critical expressions override the behavior of catch statements so that errors which occur within critical expressions do not trigger catch error handlers. Propagation expressions do not override critical expressions or catch statements, however. They do not prevent errors from being caught; they only determine how errors propagate if they are not caught.
For more information on handling errors, see section Errors.
Whenever you are writing an argument list or a list constructor expression you can splice a list value into the sequence of expressions by prepending a list expression with `@'. For example:
['foo, @[1, 2, 3], 'quux] => ['foo, 1, 2, 3, 'quux]
You can use the splicing operator when sending arguments:
$string.do_something(this, that, @others);
The splicing operator is not listed in the operator precedence list (see section Precedence). This is because it is meaningless to talk about, for example, adding a spliced list to another value. Using the splicing operator never causes an ambiguity.
Last Modified on Mar 2 1996
Copyright © 1995, 1996, Brandon Gillespie