Janus SOAP ULI V7.8 changes: Difference between revisions
Line 632: | Line 632: | ||
</th><td>A <var>StringTokenizer</var> object variable. | </th><td>A <var>StringTokenizer</var> object variable. | ||
</td></tr></table> | </td></tr></table> | ||
===The QuotesBreak property=== | |||
===The PreviousChar, PeekPreviousChar, and StringUpTo methods=== | ===The PreviousChar, PeekPreviousChar, and StringUpTo methods=== |
Revision as of 16:19, 3 July 2012
The following sections describe changes in the Janus SOAP ULI in this release.
New arguments for Record class ToXmlDoc method
The ToXmlDoc method in the Record class has the following new arguments:
CodepageTable | This optional, NameRequired, Boolean argument, which defaults to False, specifies whether to use the base codepage translation table when creating the XmlDoc. For more details, see the description of the CodepageTable argument in "LoadFromRecord subroutine in XmlDoc and XmlNode classes".
This argument was actually introduced in version 7.6 of the Sirius Mods. |
---|---|
AllowNull | The value of this optional, NameRequired, Boolean argument, which defaults to False, is copied to the AllowNull property of the XmlDoc created by ToXmlDoc. The XmlDoc's AllowNull property, in turn, determines whether field values that contain the X'00' character are stored in the XmlDoc with base64 encoding. Such values are base64 encoded if AllowNull is False.
For more information, see the description of the AllowNull argument in "NewFromRecord shared function in XmlDoc class". This argument was actually introduced in version 7.7 of the Sirius Mods. |
Field references in Record class CurrentRecord methods
For methods declared with a CurrentRecord attribute, it was the case under Sirius Mods 7.7 that field references were an exception to the following rule:
Statements within the method definition, even a CurrentRecord method call, may reference the record without having to be wrapped inside a record For loop.
Under Sirius Mods 7.8, field references are no longer an exception to this rule. You may reference a record field from within a method declared with CurrentRecord without being inside a record For loop.
For example, for the field COLOR,
the For Record currentRecord
and End For
statements containing the
print COLOR
statement in the method definition below
may be discarded under Sirius Mods 7.8:
local subroutine (Record in file myproc):printField currentRecord in file myproc for record currentRecord print COLOR end for end subroutine
New class: PersistentObjectInfo
Sirius Mods 7.8 contains the new PersistentObjectInfo class, which contains information about a global or session object in the current thread.
PersistentObjectInfo objects offer the advantage of the sorting, finding, and subsetting facilities of collections.
Three pieces of information (provided by class functions named as follows) are available for a global or session object in the PersistentObjectInfo class:
Name | The global/session name associated with the object. |
SetTime | The time the global/session object was set in YYYYMMDDHHMISSXXX format. |
ClassDescription | A description of the class of the object. For example, "System:Stringlist", or "MyUserLanguageClass", or "Arraylist of Object MyUserLanguageClass". |
SetTime and ClassDescription are intended for debugging and problem diagnosis and not for application purposes.
Creating PersistentObjectInfo objects
One way of creating a PersistentObjectInfo object is with the NewFromGlobal method or the NewFromSession method. These methods take a single, required, unnamed string parameter which indicates the name of the global or session variable.
Probably the most common way of creating PersistentObjectInfo objects is using the GlobalList and SessionList methods. These are shared methods that return an Arraylist of Object PersistentObjectInfo.
The GlobalList and SessionList methods have one optional parameter that contains the name of the variables to be returned, with wildcards allowed.
The PersistentObjectInfoList type
As a coding convenience, this Info class feature also introduces a new User Language %variable declaration type: PersistentObjectInfoList. This type is defined as an "Arraylist of Object PersistentObjectInfo". Consequently, instead of a declaration like this one:
%persInfoList is arraylist of object persistentObjectInfo
You can simply specify:
%persInfoList is type persistentObjectInfoList
Note: The keyword Type is required.
The GlobalList and SessionList Object class methods
In addition to belonging to the PersistentObjectInfo class, the GlobalList and SessionList methods are also Object class shared methods. This means that both of these statements are valid:
%persInfoList = %(persistentObjectInfo):globalList %persInfoList = %(object):globalList
New exception class: BadJournal
The BadJournal exception class reports errors in CCAJRNL or CCAJLOG datasets or streams, including naming errors. The New method of the Journal system class is the system method that automatically throws a BadJournal exception.
The following example shows a Try and Catch of a Journal class, New method, exception. An invalid journal name is specified to generate the BadJournal exception:
Begin %sl is object stringlist %rc is float %journal is object journal %bdjrnl is object BadJournal try printtext {~} is: {%journal = new('OLD~RNL')} catch BadJournal to %bdjrnl Print 'Failure!!! Reason code is: ' %bdjrnl:reasonCode end try %rc = %sl:appendJournalData( - Options='MAXIO=1000 WIDTH=138 ST AA USER', - Threads='*', Journal=%journal) Print %rc Print %sl:count %sl:print End
The Stringlist AppendJournalData method does not cancel if its Journal parameter is null. The request result shows the reason code (ReasonCode property value) stored in the exception object:
%journal = new('OLD~RNL') is: Failure!!! Reason code is: 1 0 0
The methods of the BadJournal class are described in the following subsections.
New constructor
This constructor generates an instance of a BadJournal exception. As shown below, the optional argument of the New method is a setting of the ReasonCode property.
New constructor syntax
[%bdJrnl =] [%(BadJournal):] New(ReasonCode=num)
ReasonCode property
This ReadOnly property returns a numeric reason code that indicates the cause of the BadJournal exception.
ReasonCode syntax
%rc = %bdJrnl:ReasonCode
Possible reason codes are:
1 | Either the dataset or stream name is invalid, or the journal is invalid. |
---|---|
2 | The dataset or stream is empty. |
3 | The journal was created with a different Model 204 version than the current Online. |
4 | A merged journal is invalid. |
New SelectionCriterion methods: IsNull and IsNotNull
These shared methods take no parameters and create a new SelectionCriterion object. The methods provide control for Null objects in the collection you are searching. They also let you determine whether a collection contains items that are objects, because they cancel the request if the collection being searched contains non-object (intrinsic type) items.
An IsNull criterion selects a collection item if the item is a Null object; an IsNotNull criterion selects an item object if it is not Null.
The syntax of the methods follows:
%selectionCriterion = [%(SelectionCriterion For itemType):]IsNull
%selectionCriterion = [%(SelectionCriterion For itemType):]IsNotNull
The examples below test a variety of searches against
Arraylist %al
of objects of class T
:
class T public variable x is float end public end class %al is arraylist of object t %t is object t %t1 is object t %t2 is object t %t1 = null %t2 = new %al = list(%t1, %t2)
- The Arraylist class FindNextItem method, which throws an exception if its selection criterion
matches no item, fails in the Try clause below when it tests the Null object item.
The method's exception is not thrown because the test failure prevents the method
from completing its search:
try %t = %al:findNextItem(EQ(x,1)) printtext found t printtext {~} = {%t:x} catch itemNotFound printText None! end try
The result is:
CANCELLING REQUEST: MSIR.0750: Class ARRAYLIST, function FindNextItem: reference to null object in line xx
To complete this request without cancellation, you can use an IsNotNull criterion to bypass Null items:
try %t = %al:findNextItem(AND(isNotNull, EQ(x,1))) printtext found t printtext {~} = {%t:x} catch itemNotFound printText None! end try
The search finds no matching items, so the Catch clause above catches the method's ItemNotFound exception, and the result is:
None!
- Instead of bypassing Null items, you might instead want the search to
include them:
try %t = %al:findNextItem(OR(isNull, EQ(x,1))) printtext found t printtext {~} = {%t:x} catch itemNotFound printText None! end try
The Null item is found, but the Try clause PrintText invocation of
%t:x
fails, and the result is:CANCELLING REQUEST: MSIR.0561: Text output: reference to null object in line xx
If you want to search exclusively for the next Null item in a collection, you can simply use this:
%t = %al:findNextItem(isNull)
- To successfully locate the non-Null item in
%al
, you could use either of the following method calls in the Try clause:%t = %al:findNextItem(isNotNull) %t = %al:findNextItem(AND(isNotNull, EQ(x,0)))
Thanks to the change in the Eq criterion in the second call above, the result of trying either of these searches is:
found t %t:x=0
New intrinsic methods
ToDegrees, ToRadians, and StringTokenizer
ToDegrees
This Float function converts to angular degrees its floating point argument which is a number of radians.
The syntax of ToDegrees is:
%number = float:ToDegrees
Where:
%number | A variable to contain the number of degrees of the method object. |
---|---|
float | A Float (datatype) value that is the number of radians. |
The following example shows the result of several ToDegrees calls:
begin printText {~} = {1:toDegrees} printText {~} = {0:toDegrees} printText {~} = {0.1:toDegrees} printText {~} = {-0.1:toDegrees} printText {~} = {3.1415926:toDegrees} printText {~} = {$pi:toDegrees} end
The result is:
1:toDegrees = 57.2957795130823 0:toDegrees = 0 0.1:toDegrees = 5.72957795130823 -0.1:toDegrees = -5.72957795130823 3.1415926:toDegrees = 179.999996929531 $pi:toDegrees = 180
ToRadians
This Float function converts to radians its floating point argument which is a number of angular degrees.
The syntax of ToRadians is:
%number = float:ToRadians
Where:
%number | A variable to contain the number of radians of the method object. |
---|---|
float | A Float value that is the number of degrees. |
The following example shows the result of several ToRadians calls:
begin printText {~} = {57:toRadians} printText {~} = {0:toRadians} printText {~} = {120:toRadians} printText {~} = {-120:toRadians} printText {~} = {360:toRadians} end
The result is:
57:toRadians = 0.994837673636768 0:toRadians = 0 120:toRadians = 2.0943951023932 -120:toRadians = -2.0943951023932 360:toRadians = 6.28318530717959
StringTokenizer
The String class StringTokenizer function returns a new instance of a StringTokenizer object using the method object string as the tokenizer string.
The StringTokenizer syntax is:
%stringTokenizer = string:StringTokenizer[( [TokenChars= string], - [Spaces= string], - [Quotes= string], - [Separators= string])]
where:
%stringTokenizer | A StringTokenizer object expression to contain the new object instance. |
---|---|
string | The string to be tokenized. |
TokenChars | This name required string argument TokenChars is a set of single-character token-delimiters (delimiters that are also tokens) that may be separated by whitespace characters. |
Spaces | This name required string argument Spaces is a set of "whitespace" characters, that is, characters that separate tokens. |
Quotes | This name required string argument Quotes is a set of quotation characters. |
Date/time conversion methods
These new date conversion methods correspond to the $Sir_Date2N* group and to the $Sir_N*2Date group.
String class:
Float class:
New Collection methods
Seven new methods are added to each of the collection classes in Sirius Mods Version 7.8.
The Sum, Average, Variance, and StandardDeviation methods
These functions have the same syntax and perform mathematical operations:
Sum | Returns the simple sum of the values of the items in the collection. |
---|---|
Average | Returns the average of the values of the items in the collection. |
Variance | Returns the "mean standard deviation" of the values of the items in the collection. From statistics, this is the average of the squares of the deviations of the value of each item from the mean of all the items. |
StandardDeviation | Returns the standard deviation, the variation from the mean, of the values of the items in the collection. This is the square root of the collection's variance. |
Here is an example:
b %al is arrayList of float %al = new %al:add(5) %al:add(3) %al:add(8) print %al:sum print %al:average print %al:variance print %al:standardDeviation end
The result is:
16 5.33333333333333 4.22222222222222 2.05480466765633
The syntax of the methods is:
%num = %collectionType:methodName( [method] )
Where:
%num | A Float variable to contain the numeric result. |
---|---|
%collectionType | An Arraylist, NamedArraylist, FloatNamedArraylist, or UnicodeNamedArraylist object variable. |
method | A function that operates on the type of the items in the collection. It may be a
local method or method variable or a class member (Variable, Property), and it must return an intrinsic (probably Float) value. The default method value is the special identity function, This, which simply returns the item value. |
The optional method parameter lets you further manipulate the collection item values before performing the requested method's operation. If your collection's items are not intrinsic values, you must specify a function that can map the item values to intrinsic values or the method will fail.
For example, for a collection that is a list of coordinates, you could return the average of their distance from the origin by first applying a local function as the Average method's method parameter:
b class point public constructor new(%x is float, %y is float) variable x is float variable y is float end public constructor new(%x is float, %y is float) %this:x = %x %this:y = %y end constructor end class local function (point):distance is float return (%this:x * %this:x + %this:y * %this:y):squareRoot end function %al is arrayList of object point %al = new %al:add(new(1,1)) %al:add(new(3,4)) %al:add(new(-5,12)) print %al:average(distance) end
The result is 6.47140452079103
.
The CountSubset method
The CountSubset function returns the number of items in a collection that match a specified selection criterion. It is related to the SubsetNew collection mathod, which returns not the count but a collection of the matching items for a specified criterion.
The syntax of the method is:
%num = %collectionType:CountSubset( criterion )
Where:
%num | A float variable to contain the numeric result. |
---|---|
%collectionType | An Arraylist, NamedArraylist, FloatNamedArraylist, or UnicodeNamedArraylist object variable. |
criterion | A SelectionCriterion object, which is a relational expression that is applied to each collection item value to determine whether the value satisfies the expression. This is a required parameter. |
As a simple example, for the ArrayList whose items are the odd integers between 0 and 10, and the selection criterion LT(this, 9))
, CountSubset returns 4
.
The MinItem and MaxItem methods
The MinItem and MaxItem functions return the minimum and maximum values in a collection. They are related to the Minimum and Maximum collection methods, which return the number or name of the item that has the minimum or maximum value in the collection.
The syntax of these methods is:
%num = %collectionType:methodName( [method] )
Where:
%num | A float variable to contain the numeric result. |
---|---|
%collectionType | An Arraylist, NamedArraylist, FloatNamedArraylist, or UnicodeNamedArraylist object variable. |
method | A function that operates on the type of the items in the collection. It may be a local method or method variable or a class member (Variable, Property), and it must return an intrinsic (probably Float) value. The default method value is the special identity function, This, which simply returns the item value. |
The optional method parameter lets you further manipulate the collection item values before performing the requested method's operation. If your collection's items are not intrinsic values, you must specify a function that can map the item values to intrinsic values or the method will fail.
For the ArrayList %al
whose items are the odd integers between 0 and 10, %al:maxItem
returns 9
.
New StringTokenizer methods
Several new methods are added to the StringTokenizer class in Sirius Mods Version 7.8.
The Separators method
This readWrite property introduces another class of characters in the StringTokenizer to delimit, or separate, tokens. Prior to this version, only Spaces and TokenChars characters were available to separate tokens. The new Separators characters are similar to Spaces and TokenChars, but in addition to delimiting tokens, Separators characters:
- do not compress to a single separator (like Spaces characters)
- are not themselves tokens (like TokenChars characters), so are not returned by repeated NextToken calls that encounter consecutive Separators characters
The syntax of the method is:
%string = %tok:Separators %tok:Separators = %string
Where:
%string | A string variable to contain the returned value of the current separator characters or to be set as the new value(s). The default value for a new tokenizer instance is the null string.
If you are setting Separators, each character in the separator string is a separator, and no character may repeat (except for apostrophe, which may be doubled). |
---|---|
%tok | A StringTokenizer object variable. |
Separators provide a way to handle consecutive occurrences of the same token delimiter character, for example, in a comma-separated value (csv) file, where they indicate a missing value. As an example, the adjacent separators in the token string below are detected and returned as nulls by the NextToken method:
b %toke is object StringTokenizer %toke = new(separators=',;') %toke:string = '0,1,2,,4,;6' repeat while %toke:notAtEnd printtext {~} = '{%toke:nextToken}' end repeat end
The result is:
%toke:nextToken = '0' %toke:nextToken = '1' %toke:nextToken = '2' %toke:nextToken = '' %toke:nextToken = '4' %toke:nextToken = '' %toke:nextToken = '6'
Separators override default and explicitly defined Spaces characters.
For example, if the only change to the example above is that the tokenizer string is "please, don't go"
,
the result is:
%toke:nextToken = 'please' %toke:nextToken = 'don't go'
The blank after don't
does not act as a token delimiter.
Note: Separators do not override explicitly defined TokenChars characters. If both separators and token characters are defined, all such characters act as token delimiters.
The CompressSpaces, FoldDoubledQuotes, and QuotesBreak methods
These readWrite properties all return or set a Boolean value.
The CompressSpaces property
CompressSpaces compresses the intermediate spaces in a string of whitespace characters.
For example, consider the following %toke
StringTokenizer string, defined with a blank as the whitespace character and with comma as a non-token separator character:
this, is a compression , example
Without compression, repeated calls of %toke:NextToken
strip the leading and trailing whitespace and select the following tokens:
this is a compression example
Without compression, that is, with CompressSpaces set to True, the result is:
this is a compression example
CompressSpaces compresses a token's intermediate whitespace
to a single whitespace character. If multiple whitespace
characters are defined, the first character in the Spaces string is the character to which intermediate whitespace is compressed. For example, if Spaces='X '
, the token
'foot ball'
will compress to 'footXball'
.
The CompressSpaces default value is False.
Note: CompressSpaces affects processing only when one or more Separators characters are set, because otherwise a token cannot contain internal whitespace.
The FoldDoubledQuotes property
The practice of doubling an apostrophe ( ' ) to yield a single apostrophe is common in User Language Print statements. For example, the result of Print 'Please, don''t go'
is:
Please, don't go
The doubled, or escaped, apostrophe is implicitly folded to a single apostrophe.
Similarly, if double quotation marks instead of the apostrophes are used to indicate a quoted string (as is allowed as of Sirius Mods version 7.8): Print "Please, don""t go"
The result is:
Please, don"t go
The escaped double quotation mark is implicitly folded to one double quotation mark.
The StringTokenizer, however, does not perform this implicit folding if it encounters a doubled Quotes character within a quoted region. For example:
b %toke is object StringTokenizer %toke = new(quotes='"') %toke:string = '"Please, don""t go"' repeat while %toke:notAtEnd printtext {~} = '{%toke:nextToken}' end repeat end
The result of this request is:
%toke:nextToken = 'Please, don' %toke:nextToken = 't go'
To provide for cases where you might expect or want implicit folding of a doubled Quotes character, version 7.8 adds the FoldDoubledQuotes property to the StringTokenizer class.
If the FoldDoubledQuotes property is set to True (this is not the default), the tokenizer considers two adjacent Quotes characters within a quoted region that is begun by the same Quotes character to be an escape sequence for a single quotation character, and the result of tokenizing %toke:string = '"Please, don""t go"'
from the previous request is:
Please, don"t go
The syntax of the FoldDoubledQuotes method is:
%bool = %tok:FoldDoubledQuotes %tok:FoldDoubledQuotes = %bool
Where:
%bool | An enumeration object of type Boolean to contain or set the value of FoldDoubledQuotes. The default value for a new tokenizer instance is False. |
---|---|
%tok | A StringTokenizer object variable. |
The QuotesBreak property
The PreviousChar, PeekPreviousChar, and StringUpTo methods
PreviousChar returns the value of the character that precedes the character that is at the tokenizing position, and it steps the tokenizing position back to the preceding character.
PeekPreviousChar returns the value of the character that precedes the character that is at the tokenizing position.
StringUpTo advances the tokenizer position past the next occurrence of its argument string, and it returns a substring of the tokenizing string, starting at the current position and ending just before the occurrence of its argument string.
New System class methods
The LastSubsystemErrorFile, LastSubsystemErrorSubsystem, and LastSubsystemErrorProcedure methods return information about the invoked procedure that forced transfer to the APSY error procedure.
These date retrieval methods corresponding to the $Sir_DateN* group and to $Sir_Date:
New Stringlist class methods
AppendFieldValues and AppendFieldImages are new Stringlist variants of $Field_List and $Field_ListI. AppendFieldValues has the same parameters as $Field_list except they are all NameRequired. AppendFieldImages has the same parameters as $Field_listI except they are NameRequired parameters.
Aliases for class names
As of Sirius Mods Version 7.8, you can define an alias name for an existing user class.
You do so by specifying an Alias parameter setting on the existing class declaration. For example,
you are changing a naming convention for some code, and you want to refer to user-defined class Blue
by another name, say Indigo
. Your class declaration would be:
class blue alias indigo
You can then use either the primary class name or the alias when declaring objects of the class:
%foo is object blue %bar is object indigo
An object of the alias class is compiled as an object of the primary class, and the runtime class of an object variable that was defined using an alias is the primary class. Consequently, all system messages you receive will specify the primary class.
For example, given the declarations above, if you call method TouchUp
for an object declared for the Indigo class:
%bar:touchUp
And method TouchUp
does not exist in the class, the error message you receive is:
MSIR.0733: Member TOUCHUP not found in class Blue
While this might seem potentially confusing, aliases are intended primarily for migrating class names, so any confusion will be limited to this migration period. In addition, only the owner of a class can declare an alias, so aliases are not likely to proliferate in your site's code.
Should it ever be necessary, you may also specify multiple aliases:
class blue alias indigo and ultramarine and navy
You may declare a user-defined class with an alias name that matches the name of a system class.
Success block added to exception catching
You use a Try/Catch statement block to catch a thrown User Language exception. For example, the following block catches an InvalidSortSpecification exception thrown by a Stringlist Sort statement:
try %strlist:sort(%sortSpec) catch invalidSortSpecification Print 'Invalid sort spec' end try
However, in more complex cases, opportunity for confusion exists if you want to execute additional statements after a Try statement if it produces no exceptions:
try <a>... <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try
The problem is that there's no way to know that <b>
, <c>
, <d>
, and <e>
can't throw an exception that might be one of the caught exceptions. In fact they might,
but you might not expect an exception from any of them in this context. There
seems no good way of preventing the catches to be in effect for them.
But as of Sirius Mods Version 7.8, you can use a Success block to make it clear that the catches apply to statement <a>
and do not apply to <b>
, <c>
, <d>
, and <e>
:
try <a>... success <b>... <c>... <d>... <e>... catch foo <x>... catch bar <y>... catch another <z>... end try
The principle benefits of the Success statement are:
- It makes it clear in the code which statement is expected to produce the exceptions being caught.
- It prevents a catch from accidentally catching an exception from a statement that didn't really expect that exception.
You can also reverse the order of the the Success and catches:
try <a>... catch foo <x>... catch bar <y>... catch another <z>... success <b>... <c>... <d>... <e>... end try
New common enumeration method: FromString
Sirius Mods Version 7.8 adds the FromString shared function as a method common to all system and user-defined enumerations. FromString converts a string argument into a value of the specified enumeration type. This is the opposite of an enumeration ToString method, which converts an enumeration value to its String representation.
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 the Animal
enumeration values by making a call to FromString:
%pet is enumeration animal %pet = fromString('gecko')
The result of a Print of %pet
above is gecko
. In the method call, fromString
does not have to be preceded by %(Animal):
to identify the type of enumeration, because the FromString method is automatically available for any User Language enumeration, system or user-defined.
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
Enumeration comparisons
The SelectionCriterion Eq and Ne methods now allow equality and inequality tests for enumerations so that you can do something like:
%suspects = %villagers:subsetNew(or(eq(evil, true), ne(nationality, usa)))
where Nationality
is an enumeration. Enumeration comparisons are not allowed in any other SelectionCriterion methods.
Enumeration attributes and attribute inverses
User Language enumeration support now includes enumeration attributes. These are constant data (types String, Float, and Enumeration are supported) that can be attached to enumeration values. Here's an example:
enumeration coffee public attribute oz is float attribute price is float value tall (oz=12, price=2.99) value grande (oz=16, price=3.99) value venti (price=4.99, oz=20) end public end enumeration %order is enumeration coffee %order = venti printtext {~} = {%order:price}
The result is:
%order:price = 4.99
New generic inverse attribute methods return an enumeration value based on the value of one of
its attributes. You do this by declaring an "inverse" method after an attribute declaration. The fromOz
method below is an 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
Anonymous functions
Anonymous functions are methods whose definition you specify but do not bind to a specific name. Typically, you define such a method in the context in which it is actually used.
Content moved to M204int page of same name, but history preserved here.