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
variable
field_expression
field_name
index_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
leaf_expression
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
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 ++
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
~ 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
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
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 ++
value_expression . field_name ++
value_expression [ value_expression1 ,..., value_expressionN]++
The similar transformations for the post-fix auto-decrement operator are:
variable --
value_expression . field_name --
value_expression [ value_expression1 ,..., value_expressionN]--

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
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 ]
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 } )
and needs no further transformation.

Object invocation results in the following transformation:

routine_name @( 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
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
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
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
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:

  1. The entire expression into a sequence of statements consisting of basic assignments and procedure invocations using the rules specified by this chapter.
  2. 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.