This is one of the
STIPPLE Documentation pages.
STIPPLE Expressions
In STIPPLE, expressions are a short-hand
notation for routine invocations and simple
assignments. This section describes the
expression transformation rules that the
STIPPLE compiler applies to expressions.
The following table lists some expressions
and their resulting transformations:
-
i := j ==>
-
i := j
-
i := 17 ==>
-
i := 17
-
i := i + 17 ==>
-
i := add@unsigned(i, 17)
-
i :+= 17 ==>
-
i := add@unsigned(i, 17)
-
m.size := 17 ==>
-
size_set@matrix(m, 17)
-
m.size :+= 17 ==>
-
size_set@matrix(m,
add@unsigned(size_get@matrix(m), 17))
-
m[i+j, k+l] := 17 ==>
-
store2@matrix(m,
add@unsigned(i, j),
add@unsigned(k, l), 17)
-
m[i+j, k+l] :+= 17 ==>
-
temp1 := add@unsigned(i, j)
temp2 := add@unsigned(k, l)
store2@matrix(m, temp1, temp2,
add@unsigned(fetch2@matrix(m,
temp1, temp2), 17))
-
invert@(m) ==>
-
invert@matrix(m)
-
i := j++ ==>
-
temp := j
j := temp + one@signed32
i := temp
-
i := ++m.size ==>
-
temp := size_get@matrix(m) + one@signed32
size_set@matrix(m, temp)
i := temp
-
i := m.size++ ==>
-
temp := size_get@matrix(m)
size_set@matrix(m,
add@signed32(temp, one@signed32))
i := temp
-
i, j := j, i ==>
-
temp1 := j
temp2 := i
i := temp1
j := temp2
Assignable and Value Expressions
An assignable expression is an expression that can
reside on the left side of an assignment operator
and its syntax is:
-
assignable_expression
-
variable_expression
-
variable_definition
-
field_expression
-
index_expression
-
variable_expression
-
variable_definition
-
variable :: type_reference
-
variable
-
field_expression
-
value_expression . field_name
-
field_name
-
index_expression
-
value_expression [
value_expression ,... ]
Assignable expressions are required for the left
side of all assignment statements and the
auto-increment/decrement unary operators.
A value expression is an expression that can
reside on the right side of an assignment
operator and its syntax is:
-
value_expression
-
( value_expression )
-
value_expression binary_operator
value_expression
-
prefix_operator value_expression
-
value_expression postfix_operator
-
assignable_expression
in_line_assignment_operator
value_expression
-
value_expression ?
value_expression :
value_expression
-
value_expression (
{ value_expression
,... } )
-
routine_name @( value_expression
{ , { value_expression
,... } } )
-
value_expression
[ value_expression ,... ]
-
value_expression . field_name
-
variable
-
leaf_expression @ type_reference
-
leaf_expression
-
leaf_expression
-
routine_name
-
object_name
-
translated_string
-
untranslated_string
-
integer
-
float
-
variable
-
routine_name
-
object_name
-
prefix_operator
-
postfix_operator
-
binary_operator
-
in_line_assignement_operator
-
::+=
-
::-=
-
::*=
-
::/=
-
::%=
-
::**=
-
::&=
-
::|=
-
::^=
-
::<<=
-
::>>=
Expression Transformation Notation
Throughout this chapter, the following notation:
-
pattern
is used to represent an expression transformation.
For example,
-
value_expression1 + value_expression2
-
add@{value_expression1}(value_expression1,
value_expression2)
is the transformation to convert the `+' binary
operator into a call to a routine named add.
Enclosing an expression in braces (i.e.
"{value_expression1}") is the notation is
used to specify the type of an expression.
For example, if variables a and b are of type
unsigned
, the expression,
a + b
is converted into
add@unsigned(a, b)
Whenever a transformation translates into multiple
statements, the last statement of the transformation
is used as the expression value. For example,
-
assignable_expression ++
-
result :: {assignable_expression} :=
assignable_expression
assignable_expression :=
result + one@{assignable_expression}
result
the result of this transformation is "result".
Precedence and Parenthesis ("(...)")
The expression operators are described in the
following sections. The operators are described
from highest precedence to lowest precedence.
All operators, except assignment operators and
the power operator (`**'), are left associative.
All assignment operators are right associative.
For example,
a := b + c + d ::= e + f + g ** h ** i
is associated as
a := ((b + c) + (d ::= ((e + f) + (g ** (h ** i)))))
Parenthesis (`(...)') can be used to group operands
and operators as desired.
The Type Scoping and Variable Definition Operators
("@", "::")
The STIPPLE compiler parses an expression into an
expression tree, where the exterior nodes are either
constants or symbols and the interior nodes are
operators. Henceforth, the exterior nodes are
called leaf nodes and the interior nodes are
called operator nodes.
Leaf nodes are either constants or symbols. A
constant can be either an integer, string, or
floating-point constant. A symbol can refer to
either a variable, field, routine, or object.
Whenever the compiler encounters a non-field
symbol, it always tries to see whether the
symbol is variable first, then a routine, and,
lastly, an object name. With the exception of
variables and fields, symbols can be explicitly
type scoped with the at sign ("@") operator,
followed by the type.
It is a compiler error to use the type scoping
operator on either a field or a variable. Any
non-variable, non-field leaf node in an
expression tree that is not explicitly type
scoped is implicitly type scoped via type
inference.
Any place a variable can be used, a new variable
can be defined. This is done with the double
colon operator ("::"). In order to define a
new variable, it is followed by a double colon
("::") and the desired variable type. The new
variable is in scope from the next statement
until the end of the current nesting level.
Within the statement that contains the expression
that defines a variable, the variable may only
occur once at the definition site. In other
words, the variable is not in scope in the
statement that contains the expression that
defines the variable.
The same variable can be defined in non-overlapping
scopes. However, in order to reduce confusion within
a routine, the compiler enforces the additional
constraint that the variable types be the same.
The Unary Operators ("!", "~", "-", "++", "--")
The not ("!"), compliment ("~"), and minus
("-") unary operators cause the following
transformations:
-
! value_expression
-
not@logical(value_expression)
-
~ value_expression
-
compliment@{value_expression}(value_expression
-
- value_expression
-
minus@{value_expression}(value_expression)
where the respective signatures for not, compliment and minus are:
procedure not@logical
takes logical
returns logical
signals
overflow
procedure compliment@{value_expression}
takes {value_expression}
returns {value_expression}
procedure minus@{value_expression}
takes {value_expression}
returns {value_expression}
signals
overflow
underflow
The unary plus operator ("+") is used to convert
between types and causes the following transformation:
-
+ value_expression
-
desired_type_name_convert@{value_expression}(value_expression)
where desired_type_name is the name of the
type needed in the context of the expression.
See the section below to find out more about
type inferencing.
{convert signature goes here}.
The auto-increment ("++") and auto-decrement ("--")
operators operate only on assignable expressions.
These operators can be used in a pre-fix or a
post-fix position. When the operators are used
in a pre-fix position, they result in
transformations to the following assignment
expressions:
-
++ assignable_expression
-
assignable_expression ::+=
one@{assignable_expression}
-
-- assignable_expression
-
assignable_expression ::-=
one@{assignable_expression}
See section ??? for more information on how these
assignment expressions are further transformed.
When the auto-increment and auto-decrement
operators are used in a post-fix position,
their transformation is dependent upon whether
the expression is a variable, field, or index
assignable expression.
The transformations for the post-fix auto-increment
operators are shown below:
-
variable ++
-
result :: {variable} := variable
variable :+= one@{variable}
result
-
value_expression . field_name ++
-
temp :: {value_expression} :=
value_expression
result :: {value_expression} :=
{value_expression}@field_name_get(temp)
field_name_set@{value_expression}(temp,
temp + one@{value_expression})
result
-
value_expression [ value_expression1
,..., value_expressionN]++ {value_expression} := value_expression
temp1 :: {value_expression1} := value_expression1
...
tempN :: {value_expressionN} := value_expressionN
result :: {value_expression} :=
fetchN@{value_expression}(temp,
temp1 , ..., tempN})
storeN@{value_expression}(temp,
temp1 , ..., tempN},
temp + one@{value_expression})
result
The similar transformations for the post-fix
auto-decrement operator are:
-
variable --
-
result :: {variable} := variable
variable :-= one@{variable}
result
-
value_expression . field_name --
-
temp :: {value_expression} :=
value_expression
result :: {value_expression} :=
{value_expression}@field_name_get(temp)
field_name_set@{value_expression}(temp,
temp - one@{value_expression})
result
-
value_expression [ value_expression1
,..., value_expressionN]-- {value_expression} := value_expression
temp1 :: {value_expression1} := value_expression1
...
tempN :: {value_expressionN} := value_expressionN
result :: {value_expression} :=
fetchN@{value_expression}(temp,
temp1 , ..., tempN})
storeN@{value_expression}(temp,
temp1 , ..., tempN},
temp - one@{value_expression})
result
The Field and Object-Invocation Operators (".", "~")
When the binary field operator (`.') is not used
as the target of an assignment, it causes the
following transformation:
-
value_expression . field_name
-
field_name_get@{value_expression}(value_expression)
What happens when the field operator is the target of
an assignment is described in section ???>
The object invocation operator, "@(", has the same
precedence as the field operator. The form of object
invocation is:
routine_name@(value_expression, value_expression1, ...)
Note that it must always be used in conjunction with
the invocation operator ("(...)"). For this reason,
the object call transformation is described in the
section describing the invocation operation (see
next section.)
The Index and Invocation Operators
("[...]", "(...)", "@(...)")
The index ("[...]") and invocation operators
("(...)") have the same precedence and occur
in a post-fix position.
When the index operator is not used as the target
of an assignment, it causes the following
transformation:
-
value_expression [ value_expression1
,... , value_expressionN ]
-
fetchN@{value_expression}(value_expression,
value_expression1,... , value_expressionN)
where the N in fetchN is replaced by the decimal
number of expressions between the square brackets.
For example, if `m' is of type matrix,
m[1, 2]
is transformed into
fetch2@matrix(m, 1, 2)
because there are two expressions between the square
brackets.
There are two forms of the invocation operator --
regular and object. The regular invocation has
the following form:
-
value_expression( {
value_expression1,... ,
value_expressionN } )
-
value_expression( {
value_expression1,... ,
value_expressionN } )
and needs no further transformation.
Object invocation results in the following
transformation:
-
routine_name @( value_expression,
{ value_expression1,... ,
value_expressionN } )
-
routine_name@{value_expression}(value_expression
{ , value_expression1,... , value_expressionN } )
The expression is used as the first argument
to the routine.
The Shift Operators ("<<", ">>")
The left shift ("<<") and right shift (">>")
operators result in the following transformations:
-
value_expression1 << value_expression2
-
left_shift@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 >> value_expression2
-
right_shift@{value_expression1}(value_expression1,
value_expression2)
The respective signatures for left_shift and
right_shift are:
procedure left_shift@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
procedure right_shift@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
underflow
The AND Operator ("&")
The AND operator ("&") results in the following
transformation:
-
value_expression1 & value_expression2
-
and@{value_expression1}(value_expression1,
value_expression2)
The signature for the AND operator is:
procedure and@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
The OR Operator ("|")
The OR operator ("|") results in the following
transformation:
-
value_expression1 | value_expression2
-
or@{value_expression1}(value_expression1,
value_expression2)
The signature for the OR operator is:
procedure or@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
The XOR Operator ("^")
The XOR (exclusive OR) operator ("^") results in the
following transformation:
value_expression1 ^ value_expression2 -
xor@{value_expression1}(value_expression1,
value_expression2)
The signature for the XOR operator is:
procedure xor@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
The Power Operator ("**")
The power operator ("**") results in the following
transformation:
-
value_expression1 ** value_expression2
-
power@{value_expression1}(value_expression1,
value_expression2)
The signature for the power operator is:
procedure power@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
The Multiply, Divide, and Remainder Operators
("*", "/", "%")
The multiply ("*"), divide ("/"), and remainder
("%") operators result in the following
transformations:
-
value_expression1 * value_expression2
-
multiply@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 / value_expression2
-
divide@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 % value_expression2
-
remainder@{value_expression1}(value_expression1,
value_expression2)
The respective signatures for multiply, divide,
and remainder are:
procedure multiply@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
procedure divide@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
procedure remainder@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
The Add and Subtract Operators ("-", "+")
The add ("+") and subtract ("-") operators result in
the following transformations:
-
value_expression1 + value_expression2
-
add@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 - value_expression2
-
subtract@{value_expression1}(value_expression1,
value_expression2
The respective signatures for add and subtract are:
procedure add@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
procedure subtract@{value_expression1}
takes {value_expression1}, {value_expression1}
returns {value_expression1}
signals
overflow
underflow
The Arithmetic-if Operators ("...?...:...")
The arithmetic-if operator (`?' and `:') is an
in-fix trinary operator that results in the
following transformation:
-
value_expression1 ?
value_expression2 : value_expression3
-
temp :: {value_expression1} :=
{value_expression1}
if value_expression1
temp := value_expression2
else
temp := value_expression3
temp
The type of value_expression1 must be logical and
both value_expression2 and value_expression2 must
be of the same type.
The relational operators
("=", "!=", "<", "<=", ">", ">=")
The equal ("="), not equal ("!="), less than
("<"), less than or equal ("<="), greater than
(">"), and greater than or equal (">=") operators
result in the following transformations:
-
value_expression1 = value_expression2
-
equal{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 != value_expression2
-
!(equal@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 < value_expression2
-
lesser@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 <= value_expression2
-
!(greater@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 > value_expression2
-
greater@{value_expression1}(value_expression1,
value_expression2)
-
value_expression1 >= value_expression2
-
!(lesser@{value_expression1}(value_expression1,
value_expression2)
The respective signatures for equal, lessor, and greater are:
procedure equal@{value_expression1}
takes {value_expression1}, {value_expression1}
returns logical
procedure lesser@{value_expression1}
takes {value_expression1}, {value_expression1}
returns logical
procedure greater@{value_expression1}
takes {value_expression1}, {value_expression1}
returns logical
The And-if Operator ("&&")
The and-if operator ("&&") results in the following
transformation:
-
value_expression1 && value_expression2
-
result :: logical := false@logical
if value_expression1
result := value_expression2
result
Both value_expression1 and value_expression2 must
be of type logical. The result of the operation
is always of type logical.
The Or-if Operator ("||")
The or-if operator ("||") results in the following
transformation:
-
value_expression1 || value_expression2
-
result :: logical := true@logical
if !value_expression1
result := value_expression2
result
Both value_expression1 and value_expression2 must be
of type logical. The result of the operation is always
of type logical.
The In-line Assignment Operators
("::=", "::operator=")
The in-line assignment operators provide the
ability to perform an assignment in the middle
of an expression. The simple in-line assignment
operator ("::=") depends upon whether the
assignable expression is a variable, field, or
index assignable expression. The simple in-line
assignment results in the following transformations:
-
variable ::= value_expression
-
variable := value_expression
variable
-
value_expression1 . field_name ::=
value_expression2
-
result :: {value_expression2} := value_expression2
value_expression1 . field_name := result
result
-
value_expression0
[ value_expression1,... ,
value_expressionN ] ::= value_expression
-
result :: {value_expression} :=
value_expression
value_expression0
[ value_expression1,... ,
value_expressionN ] := result
result
The compound in-line assignment operators
(`::operator=') involve a binary operator
in addition to the assignment. The permitted
binary operators are listed in the following
table:
Binary Operator Compound in-line operator
<< ::<<=
>> ::>>=
& ::&=
| ::|=
^ ::^=
** ::**=
* ::*=
/ ::/=
% ::%=
+ ::+=
- ::-=
The compound in-line assignment operators depend
upon whether to the assignable expression is a
variable, field, or index assignable expression
and results in the following transformations:
-
variable ::operator= value_expression
-
result :: {variable} :=
variable operator value_expression
variable := result
result
-
value_expression1 . field_name
::operator= value_expression2
-
temp :: {value_expression1} :=
value_expression1
result :: {value_expression2} :=
temp . field_name operator value_expression2
temp . field_name := result
result
-
value_expression0
[value_expression1,... ]
::operator= value_expression
-
temp0 :: {value_expression0} := value_expression0
temp1 :: {value_expression1} := value_expression1
...
tempN :: {value_expressionN} := value_expressionN
result :: {value_expression} :=
temp0[temp1,... , tempN] operator
value_expression
temp0[temp1, º, tempN] := result
result
The comma operator (",")
The comma (",") operator comma is used to separate
expressions in expressions lists. Expression lists
are used for argument lists to procedure invocations
and index lists in indexing operations. In addition,
expression lists are permitted with the regular
assignment operators to support multiple assignment.
The regular assignment operators (":=", ":operator=")
The regular assignment operators (`:=', `:operator=')
have the lowest precedence of all operators.
Multiple Assignment
In STIPPLE, multiple assignments can be specified
with a single regular assignment operator. In
multiple assignment, a comma separated list of
assignable expressions on the left of the operator
is assigned the respective values of the a comma
separated list of expression on the right. The
following transformations result from multiple
assignment:
-
assignable_expression1,...,
assignable_expressionN :=
value_expression1,..., value_expressionN
-
temp1 := value_expression1
...
tempN := value_expressionN
assignable_expression1 := temp1
...
assignable_expressionN := tempN
-
assignable_expression1,...,
assignable_expressionN :operator=
value_expression1,..., value_expressionN
-
temp1 := value_expression1
...
tempN := value_expressionN
assignable_expression1 :operator= temp1
...
assignable_expressionN :operator= tempN
Multiple Return
STIPPLE permits routines to return more than one
value. Multiple return routines (and iterators)
may occur on the right side of a multiple assignment
statement. In this case, the return values are
treated as a comma separated list of expressions.
For example, consider the following routine:
procedure swap
takes
a unsigned
b unsigned
returns unsigned, unsigned
return b, a # Swap input arguments
The following expression:
a, b, c, d, e := swap(2, 1), 3, swap(5, 4)
after the two calls to swap is equivalent to:
a, b, c, d, e := 1, 2, 3, 4, 5
which assigns 1, 2, 3, 4, and 5 to
a, b, c, d, and e, respectively.
In additions, multi-return routines may be invoked
as in expressions to routine invocations and
indexing operations where they are again treated
as a comma separated list of expressions.
Continuing the previous example,
a, b := swap(swap(1, 2))
after the inner-most call to swap is equivalent to
a, b := swap(2, 1)
after the outer-most call to swap is equivalent to
a, b := 1, 2
which assigns 1 and 2 to a and b, respectively.
Thus, the transformation for a procedure invocation
with N multiple returns is:
-
value_expression(value_expression1,...,
value_expressionN)
-
temp1,..., tempN :=
value_expression(value_expression1,...,
value_expressionN)
temp1,..., tempN
Assignment Transformations
The simple regular assignment operator (":=")
depends upon whether the assignable expression
is a variable, field, or index assignable
expression. A simple regular assignment to
a variable is basic and can not be transformed
any further. The transformations resulting from
assignment to field and index assignable expressions are:
-
value_expression1 . field_name :=
value_expression2
-
field_name_set@{value_expression1}(
value_expression1,
value_expression2)
-
value_expression0 [value_expression1,...,
value_expressionN ] := value_expression
-
storeN@{value_expression0}(value_expression0,
value_expression1,...,
value_expressionN, value_expression)
where the N in storeN is replaced by the decimal
number of expressions between the square brackets.
The complex regular assignment operators (":operator=")
all involve a binary operator from the following table:
Binary Operator Compound Assignment Operator
<< :<<=
>> :>>=
& :&=
| :|=
^ :^=
** :**=
* :*=
/ :/=
% :%=
+ :+=
- :-=
The transformation of complex assignment operators depend
upon whether the expression on the left is a variable,
field, or index assignable expression resulting in the
following transformations:
-
variable :operator= value_expression
-
variable :=
variable operator value_expression
-
value_expression1 . field_name
:operator= value_expression
-
temp :: {value_expression1} :=
value_expression1
temp.field_name :=
left.field_name operator value_expression
-
value_expression0[value_expression1,...,
value_expressionN] :operator=
value_expression
-
temp0 :: {value_expression0} :=
value_expression0
temp1 :: {value_expression1} :=
value_expression1
...
tempN :: {value_expressionN} :=
value_expressionN
temp0[temp1,..., tempN] :=
temp0[temp1,..., tempN]
operator value_expression
Type Inferencing
{This section confuses the heck out of me; I don't
think there is chance of anybody else understanding
it.}
Type inferencing is used to reduce the number of
times that the programmer must specify the type
of a procedure, object, or constant. For example,
if the procedure color@add has the following
signature,
procedure add@color
takes color, color
returns color
the following expressions,
c :: color := red
c :+= green + blue
is converted to,
c :: color := red@color
c :+= green@color + blue@color
via type inferencing. The use of type inferencing
significantly reduces the clutter that must be
entered by the programmer.
The basic type inferencing algorithm is performed
using the following steps:
-
The entire expression into a sequence of
statements consisting of basic assignments
and procedure invocations using the rules
specified by this chapter.
-
The parse tree for each statement is traversed
in a depth-first, left-first fashion. A
different action is taken depending upon
the type of each node.
-
Assignment. For an assignment node, the left
assignable expression type is inferred to be
the right value expression type.
-
Invocation. For a procedure invocation node,
the procedure signature is used to infer the
argument value expression types.
-
Symbol. For a symbol node, it is either a
variable, procedure or object. For a variable,
the variable type is used. Otherwise, the
inferred type is used. If there is no inferred
type, a compiler error occurs.
-
Convert Operator ("+"). The automatic conversion
operator causes the compiler to insert a routine
invocation of the form
desired_type_convert@actual_type into the parse
tree. This operator will only succeed if the
compiler can figure out what the actual type
of the value expression is.
-
Constant. A constant is an integer, string, or
floating-point number, which have implicit types
of dint, string, and qfloat. If the inferred type
does not match the implicit type, the compiler
attempts to find a routine of the form
desired_type_convert@actual_type. If found and
the routine signature is marked with the
autotmatic_convert keyword, the conversion
routine is inserted into the tree. If the
compiler can not find the conversion routine,
it will generate an error.
-
Implicit Conversion. Whenever the compiler is
about to generate a type mismatch error, it
will attempt to perform an automatic conversion
first. An automatic conversion will succeed
only if the conversion routine signature is
marked with the automatic_convert keyword.
Returning to the previous example, the parse tree for
c :: color := red
yields the parse tree:
{Parse treee is still on Frame document.}
The depth-first left-first algorithm finds the symbol
c first and discovers that it is of type color. This
type is passed up the parse tree until the assignment
operator is encountered. At this point, the inferred
type is passed across the assignment operator and
goes back down the tree. When the symbol red is
encountered, it does not match a variable so it
is inferred to be either a procedure or an object
with a type of color.
For the other example,
c :+= green + blue
is translated to the following expression
{To be continued.}
From here you can go to either the
STIPPLE Rationale or back to the
table of contents.
Copyright (c) 1991 --
Wayne C. Gramlich. All rights reserved.