Enumerations
Objects may have properties that can have relatively few
values, such as True or False; Yes, No, or Maybe; Red, Green,
or Blue; or Small, Medium, Large, SuperSize.
These properties could be defined with a string datatype, in which case
they would be set to values like 'True'
or 'False'
.
Or they could be
defined with a numeric datatype like Float, in which case they would
be set to values like 0
or 1
.
Both of these options are unappealing:
- Using strings ties you to a particular case-sensitive representation of the values.
- Using numbers makes the code unclear, as the meaning of, say, 2 is not readily apparent.
- Neither strings nor numbers provide any compile-time validation of values.
That is, if the value of a property can be either
'True'
or'False'
, the compiler will allow the property to be set to'True'
, or'False'
, or anything else, for that matter.
The special type of class called enumerations solves these problems. An enumeration class doesn't actually describe a set of objects but a set of values or, more specifically, names for a set of values. Since values are syntactically nouns, as are objects, enumeration handling is very similar to object handling. SOUL supports system enumerations (pre-defined in the Model 204 nucleus) as well as user enumerations (defined within a SOUL request.
Declaring enumeration variables
Enumeration variables are declared very much like objects:
variable [Is] Enumeration className [Initial(value)]
The Boolean enumeration is so heavily used that the keyword Enumeration is optional for Boolean enumeration variables:
variable [Is] Boolean [Initial(value)]
For example:
%myAim is boolean
An Enumeration variable, like an Object variable can be null; in fact this the the default initial value for all enumeration variables. This means that certain operations on enumeration variables can result in null-pointer reference errors. It also means that, in some sense, all enumerations contain a special value of Null (though Null behaves somewhat differently from other values). This means that caution should be used for inequality tests on enumeration variables as a null will satisfy an inequality test.
As of Sirius Mods 7.1, enumeration variables, including Booleans, can have a compile-time initial value as specified by an Initial clause:
%myAim is boolean initial(true)
Such a clause can also be placed on variables in a class Public, Private, or Shared block:
class alison public variable myaim is boolean initial(true) ... end public ... end class
As with other variable types, an Initial clause in a Public or Private block sets the initial value for the variable when the containing object is instantiated.
The sections that follow describe how to use enumerations, including the methods that may be applied to them. Most of the individual enumeration classes are documented elsewhere: in the context of the (typically one) system class method that uses the enumeration.
Using enumerations
An explicit New method is not allowed for enumeration variables. Instead, they must be set from the possible values for the enumeration:
%well is enumeration boolean ... %well = %(boolean):true ... %well = %(boolean):false
Enumeration values are similar to shared properties of an object class and, as with other shared properties, an enumeration variable can be used instead of the class name to reference the shared properties:
%well is enumeration boolean ... %well = %well:true ... %well = %well:false
Or, because the class can be inferred from the target, enumerations can be set using just the value name:
%well is enumeration boolean ... %well = true ... %well = false
You can also use just the value name when a method parameter is an enumeration:
class customer public subroutine setActive(%active is enumeration boolean) ... end public ... end class ... %myCustomer is object customer ... %myCustomer:setActive(false)
Often, though, an enumeration will be a property rather than a parameter. The previous example would probably be recast as:
class customer public property active is enumeration boolean ... end public ... end class ... %myCustomer is object customer ... %myCustomer:active = false
System enumerations can be declared with an explicit "system" on their declaration:
%active is enumeration system:boolean
Enumerations have a ToString method that returns the string form of a value:
%active is enumeration boolean %active = true print %active:toString
Note:
Prior to Sirius Mods version 7.3,
explicitly calling the ToString method was required to
display the string form of an enumeration value, and simply
specifying print %active
in the example above was an error,
because %active
is an object, not a string.
However,
as of Sirius Mods 7.3, explicitly specifying
ToString is optional, and a statement like print %active
automatically implies print %active:toString
.
This is described further in ToString's "Usage notes".
Enumerations can also have other methods.
One class of such methods are “Is” methods that return
0
or 1
depending on whether the method has a particular
value.
For example, the following method tests if
the value of %active
is True
:
%active is enumeration boolean ... if %active:isTrue then
This is almost exactly equivalent to the following:
%active is enumeration boolean ... if %active eq true then
The one difference between the two forms is that the former would cause
a null-pointer-reference request cancellation if %active
had never
been set, while the latter would simply evaluate %active eq true
as false, since a Null value is not equal to True
.
Note:
There is an alternative to using these Is methods for Boolean enumerations
in the context of an If statement:
automatic conversion of
a Boolean True to a 1, and of a False to a 0.
See "Using Boolean enumerations".
It is possible to use enumerations without ever declaring a variable with that enumeration. For example, one can set the UseDefault property of a NamedArraylist by just using the enumeration value:
%description is collection NamedArraylist of string len 64 ... %description:useDefault = true
Common enumeration methods
The methods listed below are available to all system enumerations. The individual method descriptions follow.
The versatile Boolean enumeration has additional methods available.
Copy function
This function returns an exact copy of the method object. It is identical to the DeepCopy method. Since enumeration variables simply have values, a Copy or DeepCopy is no different from an assignment, so enumerations are always copyable and deep copyable, as described in "Copying objects". As such, there is no real reason to do a Copy or DeepCopy of an enumeration variable, and the presence of these methods simply makes the copyability of enumeration variables explicit.
Copy syntax
%cop = enum:Copy
Syntax terms
%cop | An enumeration variable to contain the copy of enum. %cop does not have to be empty. |
---|---|
enum | An enumeration variable or an expression that results in an enumeration. |
The method object (enum) may be Null. The output of a copy of a Null object is a Null object.
DeepCopy function
This function returns an exact copy of the method object. It is identical to the Copy method. Since enumeration variables simply have values, a Copy or DeepCopy is no different from an assignment, so enumerations are always copyable and deep copyable, as described in "Copying objects". As such, there is no real reason to do a Copy or DeepCopy of an enumeration variable, and the presence of these methods simply makes the copyability of enumeration variables explicit.
DeepCopy syntax
%dcop = enum:DeepCopy
Syntax terms
%dcop | An enumeration variable to contain the deep copy of enum. %dcop does not have to be empty. |
---|---|
enum | An enumeration variable or an expression that results in an enumeration. |
The method object (enum) may be Null. The output of a deep copy of a Null object is a Null object.
FromString function
FromString is a shared function that converts a string argument into a value of the specified enumeration type. This is the opposite of the enumeration ToString method, which converts an enumeration value to its String representation.
FromString is available as of Sirius Mods Version 7.8.
FromString syntax
%enum = [%(enumType):]FromString(string)
Syntax terms
%enum | An enumeration variable or expression. |
---|---|
%(enumType) | This explicit specification of the enumeration type in parentheses denotes a virtual constructor. See "Usage notes", below, for more information about invoking this function. |
string | A longstring variable that is assigned the current string value of enum. |
Usage Notes
- As an example, consider the following user-defined enumeration:
enumeration Animal public value cat value dog value gecko value parrot end public end enumeration
You can populate an
Animal
enumeration variable with one of theAnimal
enumeration values by making a call to FromString:%pet is enumeration animal %pet = fromString('gecko')
The result of a Print of
%pet
above isgecko
.
Note: In the method call,fromString
might but does not have to be preceded by%(Animal):
to identify the type of enumeration, because the type is determined from the result variable. You could also get the same result from invoking with an already defined enumeration variable, as in:%pet = %pet:fromString('gecko')
- Only strings that match a value of the particular enumeration type
can be converted. If a string cannot be converted to an enumeration
value, FromString throws an Invalidvalue exception:
%pet = fromString('alien') *** 1 CANCELLING REQUEST: MSIR.0750: Class Animal, function FromString: InvalidValue exception: ALIEN is not a valid enumeration value in line 84, procedure ENUM, file MYPROC
ToString property
This non-settable property examines an enumeration and returns a printable or testable string representation of its value.
ToString syntax
%ls = enum:ToString
Syntax terms
%ls | A longstring variable that is assigned the current string value of enum. |
---|---|
enum | An enumeration variable or an expression that results in an enumeration. |
Calling ToString implicitly
To return a string representation of the value of a SOUL system or user-defined enumeration, you can do either of the following:
- Explicitly specify the ToString method.
- Print (or audit) the value of an enumeration, and ToString is implied,
as shown in the following example:
begin enumeration color public value red value white value blue value green end public end enumeration %x is boolean initial(true) %z is enumeration color initial(blue) %daem is object daemon %daem = new print %x with ' ' %z printText {~} = {%x}, {~} = {%z} print %daem:haveDaemon printText {~} = {%daem:haveDaemon} end
The Print and PrintText statements above produce these results:
True blue %x = True, %z = blue True %daem:haveDaemon = True
Note:
In addition, the implicit ToString feature extends beyond enumerations: upon any attempt to print or audit any object value, SOUL will try to apply a ToString method to the object. If the object is an enumeration (as shown above) or is an instance of a system or user-defined class that has a ToString method, a ToString is implicitly applied and the result is a successful print or audit of the object value.
For example, the user-defined class in the following request includes a ToString method. The request's
printText
statement prints%x = a=11, b=22
.begin class mumble public variable a is float variable b is float constructor new(%a is float nameRequired, %b is float) function tostring is longstring end public constructor new(%a is float nameRequired, %b is float) %this:a = %a %this:b = %b end constructor function tostring is longstring return 'a=' with %a with ', b=' with %b end function end class %x is object mumble %x = new(a=11, b=22) printText {~} = {%x} end
If the object you try to print or audit directly is not an enumeration or is an instance of a class that does not have a ToString method (system or user-written), you receive a compilation error. For example, if
%sl
is a Stringlist object, no user ToString method exists, and your request contains aPrint %sl
statement, you get a message like the following:*** 1 MSIR.0733: Member TOSTRING not found in class STRINGLIST print %sl (FILE = JALWORK, PROCEDURE = FOO, LINE = 28) *** M204.1042: COMPILATION ERRORS
Finally, if the object you try to print directly is a Unicode variable under Sirius Mods 7.6 and higher, the implicit ToString method returns an EBCDIC character-encoded value for Unicode characters that do not translate to EBCDIC. This is described further in "Implicit Unicode conversions".
Boolean enumeration
The Boolean enumeration is probably the most commonly used, and so the most important enumeration class. Because of its importance, it is treated specially by User Language, and is a topic unto itself.
User Language enumerations
While there are a wide variety of system enumerations that can be useful in many contexts (especially the Boolean enumeration), it is also possible to define enumerations in SOUL. This is done via the Enumeration statement/block, which is very similar to the Class statement/block (as in "Classes and objects").
The Enumeration block
The Enumeration block can contain Public, Private, Public Shared, and Private Shared blocks, just like any other class. However, because enumeration classes don't really describe objects, there can be no instance variables. That is, the Public and Private blocks cannot contain Variable declarations.
Instead, the Public block must contain one or more Value declarations whose syntax is:
Value value [attribute]
Each value declared by a Value declaration becomes one of the values of the enumeration.
One or more attributes (a constant datum of type String, Float, or Enumeration) can be attached to an enumeration value (as of Sirius Mods version 7.8).
No values or attributes are allowed in the Private or Shared blocks.
Note: The value in a Value clause is unusual in that the case (lower or upper) of the characters in its value are saved and used in the implicit ToString method. The same is true for the values in an Attribute clause.
The following is an example of a simple enumeration:
enumeration shape public attribute code is string value triangle (code='31') value square (code='44') value rhombus (code='42') value rectangle (code='41') value quadrilateral (code='40') value pentagon (code='50') value circle (code='01') end public end enumeration
All attributes must be declared before any value is declared. In a given Value clause:
- A specification for each attribute must be explicitly present.
- An attribute may be specified by name or by position; and like a method named parameter, an attribute specified with its name may not precede an attribute specified only by position.
- An attribute name may not repeat.
Note: Just as for Class blocks as of Sirius Mods version 7.3, an Enumeration block you declare inside a method or complex subroutine must be preceded by the keyword Local. For more information about locally-scoped classes and enumerations, see "Local classes, enumerations, and structures".
Using User Language enumerations
You can use User Language defined enumerations just like system enumerations. That is, variables are declared with the Enumeration keyword and class name:
%cookieShape is enumeration shape
Enumeration values may be assigned to an enumeration variable:
%cookieShape = circle
Such a variable may be printed (Print %cookieShape
), and it may be used to access an enumeration attribute:
Print %cookieShape:code
More commonly, enumerations may be used as method parameters:
class cookie ... subroutine cut(%shape is enumeration shape) ... end class
And, typically, literal values are used as the arguments when invoking such a method:
%biscuit is object cookie ... %biscuit:cut(pentagon)
Enumeration values are not strings, so they are case insensitive. While the class of an enumeration value can almost always be determined from context, the class name can always be explicitly specified for a value:
%cookieShape = %(shape):circle ... %biscuit:cut(%(shape):pentagon)
Like system enumerations, User Language enumeration variables can be in a state where they have no value, that is, they can be null. In fact, that is the initial value of any enumeration variable.
As with system enumerations (as of Sirius Mods 7.1), you can change the default initial value with the Initial clause on the variable declaration:
variable [Is] Enumeration enumerationName [Initial(value)]
You can set the default initial value for local variables:
%cutout is enumeration shape initial(square)
And you can set it for variables in class blocks:
class cookie public variable shape is enumeration shape initial(round) ... end public ... end class
Of course, instance-specific (non-Shared) initial values are set for each instance of the class when it is created.
Enumeration methods
Enumeration classes can also contain methods that operate on
or are related to the enumeration values.
For example, the Shape
enumeration can have a method that returns the number
of sides in a shape:
enumeration shape public ... function sides is float end public function sides is float if %this eq circle then return 0 end if if %this eq triangle then return 3 end if ... end function end enumeration
As this example illustrates, the implicit %this
variable for enumeration
methods contains not an object reference but an enumeration value.
Enumeration methods can be applied to enumeration values as well as enumeration variables. This is one case, however, where the enumeration value must be qualified with the class name, because it appears in a context where the class cannot be determined otherwise:
print %(shape):rhombus:sides
And, as with objects, an enumeration method could be applied to the output
of another method that returns an enumeration value.
For example, if the PickCookie
method of the Jar
class returns a
Cookie
object, and the Shape
method of the Cookie
class returns a
Shape
enumeration value, the following prints the number of sides of
a picked cookie:
%jar is object jar ... print %jar:pickCookie:shape:sides
Inverse attribute methods
Inverse attribute methods let you derive an enumeration Value based on the value of one of
its Attributes. To do so, you declare an inverse
method at the end of an Attribute declaration, then you invoke the method, as is shown with the fromOz
method in this example:
enumeration coffee public attribute oz is float inverse fromOz value tall (12) value grande (16) value venti (20) end public end enumeration %order is enumeration coffee %order = fromOz(16) Print %order
The result is:
grande
Usage notes
- Inverse attribute calls throw an InvalidValue exception if the given value cannot be inverted.
- Since the same attribute value may appear in more than one Value specification,
inverse attribute calls are checked at compile time for such ambiguous
value parameters. If you try to invert an attribute value that has multiple possible inverses,
you receive
MSIR.1029: Attribute error: Duplicate values specified for invertible attribute
when you attempt to compile the offending enumeration.
Automatic methods
Several methods are automatically provided for User Language enumeration classes. Some of these are identical to common methods available in most system enumerations. These methods are:
Copy | Performs a “copy” of the enumeration value. Since enumeration variables simply have values, a Copy or DeepCopy is no different from an assignment, so enumerations are always copyable and deep copyable, as described in "Copying objects". As such, there is no real reason to do a Copy or DeepCopy of an enumeration variable, and the presence of these methods simply makes the copyability of enumeration variables explicit. |
---|---|
DeepCopy | Performs a “copy” of the enumeration value. Since enumeration variables simply have values, a Copy or DeepCopy is no different from an assignment, so enumerations are always copyable and deep copyable, as described in "Copying objects". As such, there is no real reason to do a Copy Or DeepCopy of an enumeration variable, and the presence of these methods simply makes the copyability of enumeration variables explicit. |
FromOrdinal | This shared method is the inverse of the Ordinal method and converts an ordinal number to an enumeration value. The class specified in the shared method invocation determines the class of the result enumeration value.
By default, the FromOrdinal method behaves as if it is a private method. That is, it can only be accessed inside the class. This can be overridden by specifying |
NumberOfValues | This shared method returns the number of values in the enumeration class. The class specified in the shared method invocation determines the class whose value count is returned.
By default, the NumberOfValues method behaves as if it is a private method. That is, it can only be accessed inside the class. This can be overridden by specifying |
Ordinal | Returns the ordinal position of the Value clause associated with the value of the enumeration. For example, if an enumeration was declared as follows:
enumeration shot public value rock value paper value scissors end public end enumeration The ordinal for a value of Rock is 1, for Paper 2, and Scissors 3. By default, the Ordinal method behaves as if it is a private method. That is, it can only be accessed inside the class. This can be overridden by specifyingAllow Ordinal in the Public block, as described below. |
ToString | Returns a string representation of the enumeration value. The value returned uses the same case for the characters as was used on the Value declaration. |
Using the Ordinal method
The Ordinal method, especially, bears further discussion.
It can be very useful in writing enumeration methods, especially in
conjunction with the Jump statement.
For example, the Sides
method in the Shape
enumeration above could have been
written like:
function sides is float jump to (three, four, four, four, four, five, none) %this:ordinal three: return 3 four: return 4 five: return 5 none: return 0 end function
While this might justifiably be disparaged as questionable programming technique, it is very efficient and simple.
One disadvantage of such a technique is that the code is now highly dependent on the order of the value declarations. This means that it becomes difficult to rearrange the value declarations, or to insert one into the list after there is code that uses the Ordinal method on an enumeration class. For this reason, the Ordinal method behaves like a private method, by default. That is, it is only available to methods inside the Enumeration class block.
One use of the Ordinal method that you might want to make public occurs when the enumeration values have a natural ordering. Consider, for example, the following enumeration:
enumeration volume public value low value medium value high value deafening end public end enumeration
With such an enumeration, one might want to use an enumeration value's ordinality in comparisons. To allow this to be done, the Allow Ordinal directive must be placed in the Public block of the enumeration definition:
enumeration volume public allow ordinal value low value medium value high value deafening end public end enumeration
Then, code like the following can be used outside the class:
%leftSpeakerVolume is enumeration volume %rightSpeakerVolume is enumeration volume ... if %leftSpeakerVolume:ordinal gt - %rightSpeakerVolume:ordinal then ... end if
Of course, the same effect could be achieved by adding a different method
to the enumeration, called perhaps Level
, that returned a non-ordinal value
that could still be used for comparisons:
%leftSpeakerVolume is enumeration volume %rightSpeakerVolume is enumeration volume ... if %leftSpeakerVolume:level gt - %rightSpeakerVolume:level then ... end if
The advantage of this approach is that a non-ordinal value is less likely to be misused for computed jumps, and the levels leave space to add values associated with new enumeration values. Another approach that avoids exposing the ordinal values of an enumeration is to provide a comparison method to compare two enumeration values.
Using the FromOrdinal and NumberOfValues methods
The FromOrdinal and NumberOfValues methods were introduced in Sirius Mods version 8.1.
These two methods make it easy to enumerate all the values in an enumeration class without having to maintain code that has a list of all the values. Because these methods could be used to build an equivalent to the automatic Ordinal method, they require Allow Ordinal to be set in the enumeration class against which they are used (see example below).
The FromOrdinal method is a shared method and has a single input argument: the ordinal number of the value to which to set the target enumeration variable. For example, if one has a Stooge enmeration:
enumeration stooge public value moe value larry value curly value shemp allow ordinal end public end enumeration
then the following sequence:
%favorite is enumeration stooge %favorite = %(stooge):fromOrdinal(3) printText {~=%favorite}
would print:
%favorite=curly
If an invalid ordinal number is specified as input to the FromOrdinal method, an InvalidValue exception is thrown. For example, the following:
%favorite = %(stooge):fromOrdinal(-5)
would result in the following request canceling error message:
MSIR.0750: Class Stooge, function FromOrdinal: InvalidValue exception: -5 is not - a valid ordinal number for class Stooge ...
You can catch such an exception to produce a list of all values in an enumeration class:
for %i from 1 to 99999999 try printText {%(stooge):fromOrdinal(%i)} catch invalidValue; loop end end try end for
This outputs:
moe larry curly shemp
The NumberOfValues automatic method for enumerations lets you do this a little more tidily:
for %i from 1 to %(stooge):numberOfValues printText {%(stooge):fromOrdinal(%i)} end for
The NumberOfValues method has no parameters.
NumberOfValues and FromOrdinal can be used to build an Arraylist that contains one item for each value in the enumeration class:
%stoogeValues is arraylist of enumeration stooge ... %stoogeValues = new for %i from 1 to %(stooge):numberOfValues %stoogeValues:add(%(stooge):fromOrdinal(%i)) end for
One advantage of having such an Arraylist is that it can easily be sorted or searched. For example, the following displays the stooge values sorted in ascending character order:
%stoogevalues:sortNew(ascending(toString)):print
This displays:
1: curly 2: larry 3: moe 4: shemp