2 Simple Arithmetic Expressions
2.1 Constants and Literals
The simplest expression is a constant, which can be given either by
the symbolic name of the constant, or by a literal. By default, the
symbolic names of constants supported are
PI,
pi,
E,
e,
true,
false,
i,
j,
NaN,
Infinity,
PositiveInfinity,
NegativeInfinity,
MaxUnsignedByte,
MinUnsignedByte,
MaxShort,
MinShort,
MaxInt,
MinInt,
MaxLong,
MinLong,
MaxFloat,
MinFloat,
MaxDouble,
MinDouble.
For example,
PI/2.0
is a valid expression that refers to the symbolic name "PI" and the
literal "2.0." The constants i and j are the imaginary number with
value equal to the square root of -1. The constant NaN is "not a
number," which for example is the result of dividing 0.0/0.0. The
constant Infinity is the result of dividing 1.0/0.0. The constants
that start with "Max" and "Min" are the maximum and minimum
values for their corresponding types.
Numerical values without decimal points, such as "10" or "-3" are
integers (type int).
Numerical values with decimal points, such as "10.0" or "3.14159" are of type
double.
Numerical values followed by "f" or "F" are
of type float. Numerical values without decimal
points followed by the character "l" (el) or "L" are of type
long. long. Numerical values without decimal
points followed by the character "s" or "S" are of type
short. Unsigned integers followed by "ub" or
"UB" are of type , unsignedByte, as in
"5ub". An unsignedByte has a value between 0 and 255; note that it
not quite the same as the Java byte, which has a value between -128
and 127. Numbers of type int, long, short or
unsignedByte can be specified in decimal, octal, or
hexadecimal. Numbers beginning with a leading "0" are octal
numbers. Numbers beginning with a leading "0x" are hexadecimal
numbers. For example, "012" and "0xA" are both equal to the
integer 10.
A complex is defined by appending an "i" or a "j"
to a double for the imaginary part. This gives a purely imaginary
complex number which can then leverage the polymorphic operations in
the Token classes to create a general complex number. Thus 2 + 3i will result in the expected complex number. You can optionally
write this 2 + 3*i.
Literal string constants are also supported. Anything between double
quotes, "...", is interpreted as a string
constant. The following built-in string-valued constants are defined:
Table 1: String-valued constants defined in the expression language
Variable name | Meaning | Property name | Example under Windows |
|
PTII | The directory in which Ptolemy II is installed | ptolemy.ptII.dir | c:\tmp |
HOME | The user home directory | user.home | c:\Documents and Settings\\you |
CWD | The current working directory | user.dir | c:\ptII |
TMPDIR | The temporary directory | java.io.tmpdir | c:\Documents and Settings\\you\Local Settings\Temp\ |
The value of these variables is the value of the Java virtual machine
property, such as user.home. The properties user.dir and
user.home are standard in Java. Their values are platform
dependent; see the documentation for the
java.lang.System.getProperties() method for details. Note that
user.dir and user.home are usually not readable in unsigned applets,
in which case, attempts to use these variables in an expression will
result in an exception. Vergil will display all the Java properties if
you invoke JVM Properties in the View menu of a
Graph Editor.
The ptolemy.ptII.dir property is set automatically when Vergil
or any other Ptolemy II executable is started up. You can also set it
when you start a Ptolemy II process using the java command by a syntax
like the following:
java -Dptolemy.ptII.dir=${PTII} classname
where classname is the full class name of a Java application.
The
constants() utility function returns a record with all the globally defined constants. If you open the expression evaluator and invoke this function, you will see that its value is something like:
CLASSPATH = "xxxxxxCLASSPATHxxxxxx", CWD = "/Users/Ptolemy/ptII",
E = 2.718281828459, HOME = "/Users/cxh", Infinity = Infinity,
MaxDouble = 1.7976931348623E308, MaxFloat = 3.4028234663853E38,
MaxInt = 2147483647, MaxLong = 9223372036854775807L,
MaxShort = 32767s, MaxUnsignedByte = 255ub, MinDouble = 4.9E-324,
MinFloat = 1.4012984643248E-45, MinInt = -2147483648,
MinLong = -9223372036854775808L, MinShort = -32768s,
MinUnsignedByte = 0ub, NaN = NaN, NegativeInfinity = -Infinity,
PI = 3.1415926535898, PTII = "/Users/Ptolemy/ptII",
PositiveInfinity = Infinity,
TMPDIR = "/var/folders/7f/7f-o2nyjFgewH67h0keKu++++TI/-Tmp-/",
boolean = false, complex = 0.0 + 0.0i, double = 0.0,
e = 2.718281828459, false = false, fixedpoint = fix(0,2,2),
float = 0.0f, general = present, i = 0.0 + 1.0i, int = 0,
j = 0.0 + 1.0i, long = 0L, matrix = [], nil = nil,
null = object(null), object = object(null), pi = 3.1415926535898,
scalar = present, short = 0s, string = "", true = true,
unknown = present, unsignedByte = 0ub, xmltoken = null
2.2 Variables
Expressions can contain identifiers that are
references to variables within the scope of the expression. For
example,
PI*x/2.0
is valid if "x" a variable in scope. In the expression
evaluator, the variables that are in scope include the built-in
constants plus any assignments that have been
previously made. For example,
>> x = pi/2
1.5707963267949
>> sin(x)
1.0
In the context of Ptolemy II models, the variables in scope include
all parameters defined at the same level of the hierarchy or
higher. So for example, if an actor has a parameter named "x" with
value 1.0, then another parameter of the same actor can have an
expression with value "PI*x/2.0", which will evaluate to π/2.
Consider a parameter P in actor X which is in turn contained by
composite actor Y. The scope of an expression for P includes all the
parameters contained by X and Y, plus those of the container of Y, its
container, etc. That is, the scope includes any parameters defined
above in the hierarchy.
You can add parameters to actors (composite
or not) by right clicking on the actor, selecting "Configure" and
then clicking on "Add", or by dragging in a parameter from the
utilities library. Thus, you can add variables to any scope, a
capability that serves the same role as the "let"
construct in many functional programming languages.
Occasionally, it is desirable to access parameters that are not in
scope. The expression language supports a limited syntax that permits
access to certain variables out of scope. In particular, if in place
of a variable name x in an expression you write A::x,
then instead of looking for x in scope, the interpreter looks
for a container named A in the scope and a parameter named
x in A. This allows reaching down one level in the
hierarchy from either the current container or any of its containers.
2.3 Operators
The arithmetic operators are +, -, *, /,
^, and %. Most of these operators operate on most data types,
including arrays, records, and matrices. The ôperator computes "to
the power of" or exponentiation where the
exponent can only be an int, short, or an
unsignedByte.
The unsignedByte, short, int and long
types can only represent integer numbers. Operations on these types
are integer operations, which can sometimes lead to unexpected
results. For instance, 1/2 yields 0 if 1 and 2 are integers, whereas
1.0/2.0 yields 0.5. The exponentiation operator "^" when used with
negative exponents can similarly yield unexpected results. For
example, 2^-1 is 0 because the result is computed as 1/(2^1).
The % operation is a modulo or remainder operation. The result is the remainder after division. The sign of the result is the same as that of the dividend (the left argument). For example,
>> 3.0 % 2.0
1.0
>> -3.0 % 2.0
-1.0
>> -3.0 % -2.0
-1.0
>> 3.0 % -2.0
1.0
The magnitude of the result is always less than the magnitude of the
divisor (the right argument). Note that when this operator is used on
doubles, the result is not the same as that produced by the
remainder() function (see Table 10). For instance,
>> remainder(-3.0, 2.0)
1.0
The remainder() function calculates the IEEE 754 standard remainder operation. It uses a rounding division rather than a truncating division, and hence the sign can be positive or negative, depending on complicated rules (see 8.0.4). For example, counter intuitively,
>> remainder(3.0, 2.0)
-1.0
When an operator involves two distinct types, the expression language has to make a decision about which type to use to implement the operation. If one of the two types can be converted without loss into the other, then it will be. For instance, int can be converted losslessly to double, so 1.0/2 will result in 2 being first converted to 2.0, so the result will be 0.5. Among the scalar types, unsignedByte can be converted to anything else, short can be converted to int, int can be converted to double, float can be converted to double and double can be converted to complex. Note that long cannot be converted to double without loss, nor vice versa, so an expression like
2.0/2L yields the following error message:
Error evaluating expression "2.0/2L"
in .Expression.evaluator
Because:
divide method not supported between ptolemy.data.DoubleToken '2.0'
and ptolemy.data.LongToken '2L' because the types are incomparable.
Just as long cannot be cast to double, int cannot
be cast to float and vice versa.
All scalar types have limited precision and magnitude. As a result of this, arithmetic operations are subject to underflow and overflow.
- For double numbers, overflow results in the corresponding
positive or negative infinity. Underflow (i.e. the precision does
not suffice to represent the result) will yield zero.
- For integer types and fixedpoint, overflow results in
wraparound. For instance, while the value of MaxInt is
2147483647, the expression MaxInt + 1 yields
-2147483648. Similarly, while MaxUnsignedByte has value
255ub, MaxUnsignedByte + 1ub has value 0ub. Note, however,
that MaxUnsignedByte + 1 yields 256, which is an int,
not an unsignedByte. This is because MaxUnsignedByte
can be losslessly converted to an int, so the addition is
int addition, not unsignedByte addition.
The bitwise operators are &, |, # and ~.
They operate on boolean, unsignedByte, short, int and long (but not fixedpoint, float, double orcomplex). The operator
& is bitwise AND, ~ is bitwise
NOT, and | is bitwise OR, and #
is bitwise XXOR ( exclusive or, after MATLAB).
The relational operators are <,
<=, >, >=, == and !=. They return
type boolean. Note that these relational operators check the
values when possible, irrespective of type. So, for example,
1 == 1.0
returns true. If you wish to check for equality of both type
and value, use the equals() method, as in
>> 1.equals(1.0)
false
Boolean-valued expressions can be used to give conditional values. The syntax for this is
boolean ? value1 : value2
If the boolean is true, the value of the expression is value1;
otherwise, it is value2. The logical
boolean operators are &&, ||, !, & and
|. They operate on type boolean and return type
boolean. The difference between logical && and and
logical & is that & evaluates all the operands
regardless of whether their value is now irrelevant. Similarly for
logical || and |. This approach is borrowed from
Java. Thus, for example, the expression false && x will
evaluate to false irrespective of whether x is defined. On the
other hand, false & x will throw an exception.
The << and >> operators performs arithmetic left and
right shifts respectively. The >>> operator performs a logical
right shift, which does not preserve the sign. They operate on
unsignedByte, short, int, and long.
2.4 Comments
In expressions, anything inside
/*...*/
is ignored, so you can insert comments.