All types derive from classes. Classes for the most commonly used data types are predefined in the language. The language offers customised syntax for these classes.
Integer
Real
Boolean
String
Array <T> (generic)
Two different kinds of classes exist: manifest classes and dynamic classes. The following sections will introduce manifest and dynamic classes, describe operations that are available on all classes, and then list the predefined classes and their operations. Finally, user defined classes are described.
Manifest classes are classes where all objects are known statically. The objects pre-exist with the definition of the class and do not have to be created. The manifest classes are Integer, Real, Boolean, String and Enumeration classes. The first four of those are predefined and all values are known to the Blue compiler. Enumeration classes are user defined. The definition of such a class consists of an enumeration of all existing objects of that class, simultaneously creating a named reference to each object.
The literal '2', for instance, is a reference to the unique integer object with the value 2. The code segment
a := 2
b := 2
assigns references to the same object to a and b. Only one integer object with the value 2 exists. This does not create two distinct objects.
All literals are constant references to objects of manifest classes.
Dynamic classes are classes where, rather than listing all objects, a creation method for objects is specified. Dynamic classes are arrays and user defined general classes.
With the definition of a dynamic class, no object is created automatically. The user has to execute an explicit create operation to create objects of these types.
Care must be taken not to confuse this with pointer and non-pointer types in other languages. In Blue, all variables hold references to objects. An integer variable holds, when assigned a value, the reference to that integer object. Thus the object model is simple: only references to objects exist. The difference between manifest and dynamic objects affects only the time and method of creation of the objects of the class, not the mechanism by which they are referenced.
This section describes operation that are common to all classes.
The general operations are:
:= assignment
= comparison (equality)
<> comparison (inequality)
The assignment (:=) assigns an object reference to a variable (variables always store object references). The object itself is not copied.
Example:
Consider variables a and b which are declared to be of some user defined class:
var
a: Myclass
b: Myclass
Then the code segment
a := create Myclass
b := a
causes a and b to reference the same object. Changes to a will be visible using b.
The comparison compares two values (variables or expression results). Values are always object references.
Example:
var
a: myclass
b: myclass
result: Boolean ...
result := b = a
result will be true if a and b reference the same object. It will not be true, if a and b reference different objects in identical states.
a <> b is true, if a and b do not reference the same object.
The predefined types and their operations are as follows. Many of the operations mentioned here are aliases. The full list of operations and their aliases are given in the individual sections for each type below.
Integer Real Boolean String Array
n + m x + y not b s.length a [n]
n - m x - y a and b str (s1, s2) a.size
-n -x a or b s.substring (n,m) a.setSize (n)
n * m x * y s[n] a.init (v)
n div m x / y s1.find (s2,n)
n mod m
n ^ m x ^ y
sqrt(x)
n > m x > y s1 < s2
n < m x < y s1 > s2
n >= m x >= y s1 <= s2
n <= m x <= y s1 >= s2
str (n) str (x) str (b)
Integer, Real, Boolean, and String are manifest classes. Array is a dynamic class.
The predefined types are now discussed in more detail. For a full class interface of the predefined types see Appendix D.
The class Integer stores whole numbers. Integer is a manifest class.
The operations defined on integers are:
alias operation return type meaning
n + m n.add (m) Integer addition
n - m n.sub (m) Integer subtraction
-n n.neg Integer negation
n * m n.mult (m) Integer multiplication
n div m n.div (m) Integer Integer division
n mod m n.mod (m) Integer remainder
n ^ m n.pow (m) Integer power
n > m n.greater (m) Boolean greater than
n < m n.less (m) Boolean less than
n >= m n.greaterEq (m) Boolean greater or equal
n <= m n.lessEq (m) Boolean less or equal
str (n) n.toString String conversion to String
In addition to these operations, integer expressions can always be used where real expressions are expected. An implicit conversion to Real takes place in that case.
Integer literals are written as usual:
42
-99
Constants are predefined for the smallest and largest available integer value in every specific implementation:
MAXINT largest representable integer
MININT smallest representable integer
The actual value of MAXINT and MININT are implementation dependent.
The class Real stores floating point numbers. Real is a manifest class.
The operations defined on reals are:
alias operation return type meaning
x + y x.add (y) Real addition
x - y x.sub (y) Real subtraction
-x x.neg Real negation
x * y x.mult (y) Real multiplication
x / y x.div (y) Real division
x ^ y x.pow (y) Real power
x.sqrt Real square root
x.trunc Integer truncation to integer
x.round Integer round to closest Integer
x > y x.greater (y) Boolean greater than
x < y x.less (y) Boolean less than
x >= y x.greaterEq (y) Boolean greater or equal
x <= y x.lessEq (y) Boolean less or equal
str (x) x.toString String conversion to String
2.0
0.111
-0.001
Note that an implicit conversion takes place when an integer is provided where a real value is expected. Thus it is legal to write 2 instead of 2.0, where the value of 2 will be implicitly converted to the real value 2.0.
Variables of class Boolean store boolean values (i.e. true or false). Boolean is a manifest class.
The operations defined on booleans are:
alias operation return type meaning
not b b.invert Boolean negation
a and b a.and (b) Boolean logical and
a or b a.or (b) Boolean logical or
str (b) b.toString String conversion to String
true
false
Note that, since "true" and "false" are keywords, and the case of keywords is insignificant, variation of case such as True or TRUE are also recognised.
Boolean expressions are incrementally evaluated. Evaluation starts from the left (subject to operator precedence rules) and stops as soon as the result can be determined.
Examples:
Consider the expression
a and b
If a evaluates to false, the evaluation of the expression will terminate and return the result false. b is only evaluated if a evaluates to true.
Likewise, in the expression
a or b
b is only evaluated if a is false. If a is true, the result of the expression will be determined without evaluating b.
This allows the following code segments to be written:
if obj<>nil and obj.size>N then ...
or
if n<=a.size and a[n]>0 then ...
Strings hold sequences of characters. Strings are manifest. Thus a String variable can reference any possible String. There is no notion of a specific (maximum) number of characters or a specific memory section associated with a String variable. String literals are written using double quotes (").
Example:
s1, s2: String ... s1 := "Da steh' ich nun, ich armer Tor"
s2 := s1
The effect of class String being a manifest type is that it behaves like having value semantics. In this example, subsequent changes to s1 will not affect s2. (A change of s1 only makes s1 reference another string.)
Characters and substrings can be extracted by using indices. String indices always start at 1.
There is no character type in Blue. Characters are strings with length one.
The most common operations on strings are:
alias operation return type meaning
s.length Integer number of characters in s
str (s1, s2) s1.concat(s2) String returns concatenation of
s1 and s2
s.substring (n,m) String substring of s starting at
n with length m
s[n] s.substring (n,1) String character in s at position n
s2.find (s1, n) Integer position of s1 in s2 after n
s1.insert (s2, n) String inserts s2 into s1 at pos. n
s.delete (n, m) String delete m characters,
starting at n
s1 < s2 s1.less (s2) Boolean s1 less than s2 ?
s1 > s2 s1.greater (s2) Boolean s1 greater than s2 ?
s1 <= s2 s1.lessEq (s2) Boolean s1 less or equal s2 ?
s1 >= s2 s1.greaterEq (s2) Boolean s1 greater or equal s2 ?
str (s) s.toString String identity
s.toUpper String convert to upper case
s.toLower String convert to lower case
s.caseEqual (s2) Boolean equality (ignore case)
Special characters
A number of escape sequences are provided to specify non-printable characters:
"\t" tab character
"\n" end-of-line character
"\\" "\"
"\nnn" ISO character nnn (decimal)
If a string is too long to be written in one line in the editor, it can be broken into sevaral parts:
s := "This is one string"
" (and this is still the same string)"
String literals that are separated by whitespace only are processed as one string. The neighbouring quotes and the whitespace in between are ignored. The strings
"one " "two " "three"
and
"one "
"two "
"three"
and
"one two three"
are identical. Every string literal (every part of the string) must end on the same line it starts on.
For a full interface of class String, see Appendix D.
The decision to make the class String a manifest class may surprise initially. Programmers tend to think about strings as memory buffers holding characters. Objects of manifest classes can not change - this contradicts the model programmers used to some other languages have of strings: they can change. After careful examination, however, it turns out that we usually think of strings not as different objects. Making strings a dynamic class would lead to the effect that in the code fragment
a := "marvin"
if a = "marvin" then ...
the expression a = "marvin" results in false (because a literal string is then an object creator and a and "marvin" are two distinct objects). Intuitively, we do not think about strings as independent objects, where two distinct objects with the same value can exist. If they have the same value, then they are the same. And this is exactly the behaviour of manifest objects.
All string functions that change a string (such as toUpper) do not change the current string, but rather return a reference to another string that contains the required text.
Arrays are the only predefined dynamic class. Therefore array objects need to be explicitly created.
Arrays are sequences of homogenous elements. The class specification is generic. (For details about generic classes, see section 17.) The element type is specified when declaring a variable; the index type is always Integer. The size of an array is dynamic. The boundaries are not statically fixed and are not part of the type.
var a: Array <Boolean>
...
a := create Array<Boolean> (20)
This example creates an array of 20 booleans. Array indices always start at 1, so this array contains elements from a[1] to a[20].
Arrays can then be assigned like all dynamic classes.
Operation on arrays:
alias operation return type meaning
create Array<t> (n) Array<t> create a new array with
n elements of type t
a [n] a.getElem (n) t element at index n
a [n] a.putElem (n, t) - set element at index n
a.init (v) - set all elements in a to v
a.size Integer number of elements in a
a.setSize (n) - change array size to n.
If n > old n, the new
elements are undefined.
Apart from these operations, literal Array constructors can be specified.
Example:
var a: Array <Integer>
...
a := [23, 2, 42]
This code fragment creates an integer array of size three with a[1]=23, a[2]=2, a[3]=42 and assigns a reference to that array to a.
Elements of an array can be of any type. In particular, arrays of arrays are possible:
var
matrix: Array <Array<Integer>>
i1, i2: Integer
...
i1 := matrix [2][2]
The array bracket alias is special in that it can appear on the right or the left side of an assignment. Dependent on its position, it translates to either getElem or putElem.
num := a [i] translates to num := a.getElem (i)
a [i] := num translates to a.putElem (i, num)
The bracket alias therefore is the only alias that is not a simple macro.
User defined classes can be either general classes (user defined dynamic classes) or enumeration classes (user defined manifest classes).
The definition of a general class is the closest thing to a "program" in procedural languages. A class typically consists of some internal data (encapsulated), internal routines, a creation routine and interface routines. The interface cannot contain variables.
BNF:
class-decl ::= class identifier [ "<" generics-list ">"] is
class-definition
generics-list ::= gen-param { , gen-param }
gen-param ::= identifier [ is identifier ]
class-definition ::= [ identifier ] general-class-decl
| ...
general-class-decl ::= class-comment
uses [ ident-list ]
[ internal
[ const const-decls ]
[ var var-decls ]
[ routines routine-decls ] ]
interface
[ creation [ "(" parameter-list ")" ]
routine-body ]
[ routines routine-decls ]
[ invariant condition ]
end class
class Rectangle is
== Author: M. Kölling
== Date: April 1995
== Version: 1.0
== Short: Graphical representation of a rectangle.
==
== Class Rectangle represents a rectangle with specified
== coordinates and colour that can be ... uses Point, Colour internal var
top_left: Point
bottom_right: Point
fill_col: Colour
border_col: Colour interface creation (tl: Point, br: Point) is
== Create rectangle at coordinates defined by tl (top-left)
== and br (bottom-right). Default colours are: fill white,
== border black. do
top_left := tl
bottom_right := br
fill_col := white
border_col := black
end creation
routines move (dx: Integer, dy: Integer) is
== Move rectangle by distance defined by dx, dy. do
top_left.move (dx, dy)
bottom_right.move (dx, dy) post
size = old size end move
size -> (width: Integer, height: Integer) is
== Return size of rectangle in width and height. do
width := bottom_right.x - top_left.x
height := bottom_right.y - top_left.y
end size invariant
(top_left.x < bottom_right.x) and
(top_left.y < bottom_right.y) end class
There is no separate interface definition for a class. The interface of a class is a restricted view of the class attributes (the internals are hidden) and it is presented to the user as a special view that is produced by a tool in the Blue Programming Environment from the full class definition.
For details on generic classes, see section 17.
The name is always first in all declarations. The reason for this is that a class definition is mostly used to look up interface features (members) and their characteristics (parameters, types). The feature should be easy to find. This is achieved by placing the name first, making it easy to read down a column of identifiers. In C-type syntax (type and some keywords first) the identifier is shifted somewhere towards the middle of the line, which makes it harder to find. The name is followed by the access definition (type, parameters) showing the syntax for usage, and the implementation definition (routine body), which is class internal information.
Creation is separate to make clear it is not a normal routine. Create looks similar to a routine but has a special syntax and semantics. It is separated from normal interface procedures and put into a special position in the class declaration to make it easy to find by enforcing a certain position for it and to symbolise its special meaning.
Comments are compulsory. Writing comments is not seen as a luxury but as part of programming. Therefore the compiler also deals (as well as it can) with proper commenting. This includes a class comment, containing certain keywords, used by the library browser, and comments for routines.
Eliminating separate interface files avoids the danger of inconsistency and repetition of code.
Enumeration classes are the only user defined manifest classes. In a manifest class all objects exist automatically and no other objects of this class can be created. Every object is referenced by a named constant.
class-decl ::= class identifier [ "<" generics-list ">" ] is
class-definition
class-definition ::= Enumeration enum-class-decl
| ...
enum-class-decl ::= class-comment
manifest ident-list
end class
ident-list ::= identifier {"," identifier }
class Colour is Enumeration == Simple enumeration type for basic colours manifest red, white, blue end class
This example defines and creates three objects which are referenced through the constant identifiers red, white and blue. In another class a variable can be defined of class Colour, and one of the constant references can be assigned to that variable.
Example:
var col1, col2: Colour ... col := red
col2 := blue
Note that, because no new objects are created at any time, after assigning red to both col1 and col2, (col1 = col2) is true.
Enumeration classes inherit from the predefined class Enumeration. Enumeration defines two routines pred and succ and the standard routine toString, which can then be applied to all enumeration types. It also provides a routine ord for conversion to Integer. For a full interface definition of Enumeration, see appendix D.
No further attributes or routines can be defined for enumeration objects. The only characteristics of enumerations are: they exist, they are distinct, and they are ordered.
Qualified Enumeration Constants
In some situation it may be necessary to qualify enumeration values with their type. This situation arises when a class uses two enumeration types that define one or more common enumeration constants. Consider the following two enumeration classes:
class Colour is Enumeration == Simple enumeration type for basic colours manifest red, white, blue end class class Program is Enumeration == Simple enumeration type for basic colours manifest emacs, vi, blue end class
If a third class now uses both Colour and Program, the constant blue is ambiguous. Trying to use it will result in a compile time error. This conflict can be resolved by preceding the enumeration value with its class name and an exclamation mark (!):
Example:
var col : Colour
prog : Program ... col := Colour!blue
prog := Program!blue
(to be written)