9.4: Examples of Key Classes
- Page ID
- 36381
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)We present now the most common or important collection classes using simple code examples. The main protocols of collections are: at:
, at:put:
— to access an element, add:
, remove:
— to add or remove an element, size
, isEmpty
, include:
— to get some information about the collection, do:
, collect:
, select:
— to iterate over the collection. Each collection may implement or not such protocols, and when they do, they interpret them to fit with their semantics. We suggest you browse the classes themselves to identify specific and more advanced protocols.
We will focus on the most common collection classes: OrderedCollection
, Set
, SortedCollection
, Dictionary
, Interval
, and Array
.
Common creation protocol. There are several ways to create instances of collections. The most generic ones use the methods new:
and with:
. new: anInteger
creates a collection of size anInteger
whose elements will all be nil
. with: anObject
creates a collection and adds anObject
to the created collection. Different collections will realize this behaviour differently.
You can create collections with initial elements using the methods with:
, with:with:
etc. for up to six elements.
Array with: 1 → #(1) Array with: 1 with: 2 → #(1 2) Array with: 1 with: 2 with: 3 → #(1 2 3) Array with: 1 with: 2 with: 3 with: 4 → #(1 2 3 4) Array with: 1 with: 2 with: 3 with: 4 with: 5 → #(1 2 3 4 5) Array with: 1 with: 2 with: 3 with: 4 with: 5 with: 6 → #(1 2 3 4 5 6)
You can also use addAll:
to add all elements of one kind of collection to another kind:
(1 to: 5) asOrderedCollection addAll: '678'; yourself → an OrderedCollection(1 2 3 4 5 $6 $7 $8)
Take care that addAll:
also returns its argument, and not the receiver! You can also create many collections with withAll:
or newFrom:
ArraywithAll:#(7 3 1 3) → #(7 3 1 3) OrderedCollection withAll: #(7 3 1 3) → an OrderedCollection(7 3 1 3) SortedCollection withAll: #(7 3 1 3) → a SortedCollection(1 3 3 7) SetwithAll:#(7 3 1 3) → a Set(7 1 3) BagwithAll:#(7 3 1 3) → a Bag(7 1 3 3) Dictionary withAll: #(7 3 1 3) → a Dictionary(1->7 2->3 3->1 4->3)
ArraynewFrom:#(7 3 1 3) → #(7 3 1 3) OrderedCollection newFrom: #(7 3 1 3) → an OrderedCollection(7 3 1 3) SortedCollection newFrom: #(7 3 1 3) → a SortedCollection(1 3 3 7) Set newFrom: #(7 3 1 3) → a Set(7 1 3) Bag newFrom: #(7 3 1 3) → a Bag(7 1 3 3) Dictionary newFrom: {1 -> 7. 2 -> 3. 3 -> 1. 4 -> 3} → a Dictionary(1->7 2->3 3->1 4->3)
Note that these two methods are not identical. In particular, Dictionary class»withAll: interprets its argument as a collection of values, whereas Dictionary class»newFrom: expects a collection of associations.
Array
An Array
is a fixed-sized collection of elements accessed by integer indices. Contrary to the C convention, the first element of a Smalltalk array is at position 1 and not 0. The main protocol to access array elements is the method at:
and at:put:
. at: anInteger
returns the element at index anInteger
. at: anInteger put: anObject
puts anObject
at index anInteger
. Arrays are fixed-size collections therefore we cannot add or remove elements at the end of an array. The following code creates an array of size 5, puts values in the first 3 locations and returns the first element.
anArray := Array new: 5. anArray at: 1 put: 4. anArray at: 2 put: 3/2. anArray at: 3 put: 'ssss'. anArray at: 1 → 4
There are several ways to create instances of the class Array
. We can use new:
, with:
, and the constructs #( ) and { }.
Creation with new: new: anInteger
creates an array of size anInteger
. Array new: 5
creates an array of size 5.
Creation with with: with:
methods allows one to specify the value of the elements. The following code creates an array of three elements consisting of the number 4, the fraction 3/2 and the string 'lulu'.
Array with: 4 with: 3/2 with: 'lulu' → {4 . (3/2) . 'lulu'}
Literal creation with #(). #() creates literal arrays with static (or “literal”) elements that have to be known when the expression is compiled, and not when it is executed. The following code creates an array of size 2 where the first element is the (literal) number 1 and the second the (literal) string 'here'.
#(1 'here') size → 2
Now, if you evaluate #(1+2)
, you do not get an array with a single element 3 but instead you get the array #(1 #+ 2)
i.e., with three elements: 1, the symbol #+ and the number 2.
#(1+2) → #(1 #+ 2)
This occurs because the construct #() causes the compiler to interpret literally the expressions contained in the array. The expression is scanned and the resulting elements are fed to a new array. Literal arrays contain numbers, nil
, true
, false
, symbols and strings.
Dynamic creation with { }. Finally, you can create a dynamic array using the construct {}. { a . b }
is equivalent to Array with: a with: b
. This means in particular that the expressions enclosed by { and } are executed.
{1+2} → #(3) {(1/2) asFloat} at: 1 → 0.5 {10 atRandom . 1/3} at: 2 → (1/3)
Element Access. Elements of all sequenceable collections can be accessed with at:
and at:put:
.
anArray := #(1 2 3 4 5 6) copy. anArray at: 3 → 3 anArray at: 3 put: 33. anArray at: 3 → 33
Be careful with code that modifies literal arrays! The compiler tries to allocate space just once for literal arrays. Unless you copy the array, the second time you evaluate the code your “literal” array may not have the value you expect. (Without cloning, the second time around, the literal #(1 2 3 4 5 6)
will actually be #(1 2 33 4 5 6)!)
Dynamic arrays do not have this problem.
OrderedCollection
OrderedCollection
is one of the collections that can grow, and to which elements can be added sequentially. It offers a variety of methods such as add:
, addFirst:
, addLast:
, and addAll:
.
ordCol := OrderedCollection new. ordCol add: 'Seaside'; add: 'SqueakSource'; addFirst: 'Monticello'. ordCol → an OrderedCollection('Monticello' 'Seaside' 'SqueakSource')
Removing Elements. The method remove: anObject
removes the first occurrence of an object from the collection. If the collection does not include such an object, it raises an error.
ordCol add: 'Monticello'. ordCol remove: 'Monticello'. ordCol → an OrderedCollection('Seaside' 'SqueakSource' 'Monticello')
There is a variant of remove:
named remove:ifAbsent:
that allows one to specify as second argument a block that is executed in case the element to be removed is not in the collection.
res := ordCol remove: 'zork' ifAbsent: [33]. res → 33
Conversion. It is possible to get an OrderedCollection
from an Array
(or any other collection) by sending the message asOrderedCollection
:
#(1 2 3) asOrderedCollection → an OrderedCollection(1 2 3) 'hello' asOrderedCollection → an OrderedCollection($h $e $l $l $o)
Interval
The class Interval
represents ranges of numbers. For example, the interval of numbers from 1 to 100 is defined as follows:
Interval from: 1 to: 100 → (1 to: 100)
The printString
of this interval reveals that the class Number
provides us with a convenience method called to:
to generate intervals:
(Interval from: 1 to: 100) = (1 to: 100) → true
We can use Interval class»from:to:by: or Number»to:by: to specify the step between two numbers as follow:
(Interval from: 1 to: 100 by: 0.5) size → 199 (1 to: 100 by: 0.5) at: 198 → 99.5 (1/2 to: 54/7 by: 1/3) last → (15/2)
Dictionary
Dictionaries are important collections whose elements are accessed using keys. Among the most commonly used messages of dictionary you will find at:
, at:put:
, at:ifAbsent:
, keys
and values
.
colors := Dictionary new. colors at: #yellow put: Color yellow. colors at: #blue put: Color blue. colors at: #red put: Color red. colors at: #yellow → Color yellow colors keys → a Set(#blue #yellow #red) colors values → {Color blue . Color yellow . Color red}
Dictionaries compare keys by equality. Two keys are considered to be the same if they return true when compared using =. A common and difficult to spot bug is to use as key an object whose = method has been redefined but not its hash
method. Both methods are used in the implementation of dictionary and when comparing objects.
The class Dictionary
clearly illustrates that the collection hierarchy is based on subclassing and not subtyping. Even though Dictionary
is a subclass of Set
, we would normally not want to use a Dictionary
where a Set
is expected. In its implementation, however, a Dictionary
can clearly be seen as consisting of a set of associations (key value) created using the message ->. We can create a Dictionary
from a collection of associations, or we may convert a dictionary to an array of associations.
colors := Dictionary newFrom: { #blue->Color blue. #red->Color red. #yellow->Color yellow }. colors removeKey: #blue. colors associations → {#yellow->Color yellow . #red->Color red}
IdentityDictionary. While a dictionary uses the result of the messages = and hash
to determine if two keys are the same, the class IdentityDictionary
uses the identity (message ==) of the key instead of its values, i.e., it considers two keys to be equal only if they are the same object.
Often Symbols
are used as keys, in which case it is natural to use an IdentityDictionary
, since a Symbol
is guaranteed to be globally unique. If, on the other hand, your keys are Strings
, it is better to use a plain Dictionary
, or you may get into trouble:
a := 'foobar'. b := a copy. trouble := IdentityDictionary new. trouble at: a put: 'a'; at: b put: 'b'. trouble at: a → 'a' trouble at: b → 'b' trouble at: 'foobar' → 'a'
Since a
and b
are different objects, they are treated as different objects. Interestingly, the literal 'foobar' is allocated just once, so is really the same object as a
. You don’t want your code to depend on behaviour like this! A plain Dictionary
would give the same value for any key equal to 'foobar'.
Use only globally unique objects (like Symbols
or SmallIntegers
) as keys for a IdentityDictionary
, and Strings
(or other objects) as keys for a plain Dictionary
.
Note that the global Smalltalk
is an instance of SystemDictionary
, a subclass of IdentityDictionary
, hence all its keys are Symbols
(actually, ByteSymbols
, which contain only 8-bit characters).
Smalltalk keys collect: [ :each | each class ] → a Set(ByteSymbol)
Sending keys or values to a Dictionary
results in a Set
, which we look at next.
Set
The class Set
is a collection which behaves as a mathematical set, i.e., as a collection with no duplicate elements and without any order. In a Set
elements are added using the message add:
and they cannot be accessed using the message at:
. Objects put in a set should implement the methods hash
and =
.
s := Set new. s add: 4/2; add: 4; add:2. s size → 2
You can also create sets using Set class»newFrom: or the conversion message Collection»asSet:
(Set newFrom: #(1 2 3 1 4)) = #(1 2 3 4 3 2 1) asSet → true
asSet
offers us a convenient way to eliminate duplicates from a collection:
{ Color black. Color white. (Color red + Color blue + Color green) } asSet size → 2
Note that red + blue + green = white.
A Bag
is much like a Set
except that it does allow duplicates:
{ Color black. Color white. (Color red + Color blue + Color green) } asBag size → 3
The set operations union, intersection and membership test are implemented by the Collection
messages union:
, intersection:
and includes:
. The receiver is first converted to a Set
, so these operations work for all kinds of collections!
(1 to: 6) union: (4 to: 10) → a Set (1 2 3 4 5 6 7 8 9 10) 'hello' intersection: 'there' → 'he' #Smalltalk includes: $k → true
As we explain below, elements of a set are accessed using iterators (see Section 9.5).
SortedCollection
In contrast to an OrderedCollection
, a SortedCollection
maintains its elements in sort order. By default, a sorted collection uses the message <= to establish sort order, so it can sort instances of subclasses of the abstract class Magnitude
, which defines the protocol of comparable objects (<, =, >, >=, between:and:
...). (See Chapter 8.)
You can create a SortedCollection
by creating a new instance and adding elements to it:
SortedCollection new add: 5; add: 2; add: 50; add: -10; yourself. → a SortedCollection(-10 2 5 50)
More usually, though, one will send the conversion message asSortedCollection
to an existing collection:
#(5 2 50 -10) asSortedCollection → a SortedCollection(-10 2 5 50)
This example answers the following FAQ:
FAQ: How do you sort a collection?
Answer: Send the message
asSortedCollection
to it.
'hello' asSortedCollection → a SortedCollection($e $h $l $l $o)
How do you get a String
back from this result? asString
unfortunately returns the printString
representation, which is not what we want:
'hello' asSortedCollection asString → 'a SortedCollection($e $h $l $l $o)'
The correct answer is to either use String class»newFrom:, String class»withAll: or Object»as::
'hello' asSortedCollection as: String → 'ehllo' String newFrom: ('hello' asSortedCollection) → 'ehllo' String withAll: ('hello' asSortedCollection) → 'ehllo'
It is possible to have different kinds of elements in a SortedCollection
as long as they are all comparable. For example we can mix different kinds of numbers such as integers, floats and fractions:
{ 5. 2/--3. 5.21 } asSortedCollection →
a SortedCollection((--2/3) 5 5.21)
Imagine that you want to sort objects that do not define the method <= or that you would like to have a different sorting criterion. You can do this by supplying a two argument block, called a sortblock, to the sorted collection. For example, the class Color
is not a Magnitude
and it does not implement the method <=, but we can specify a block stating that the colors should be sorted according to their luminance (a measure of brightness).
col := SortedCollection sortBlock: [:c1 :c2 | c1 luminance <= c2 luminance]. col addAll: { Color red. Color yellow. Color white. Color black }. col → a SortedCollection(Color black Color red Color yellow Color white)
String
A Smalltalk String
represents a collection of Character
s. It is sequenceable, indexable, mutable and homogeneous, containing only Character
instances. Like Array
s, String
s have a dedicated syntax, and are normally created by directly specifying a String
literal within single quotes, but the usual collection creation methods will work as well.
'Hello' → 'Hello' String with: $A → 'A' String with: $h with: $i with: $! → 'hi!' String newFrom: #($h $e $l $l $o) → 'hello'
In actual fact, String
is abstract. When we instantiate a String
we actually get either an 8-bit ByteString
or a 32-bit WideString
. To keep things simple, we usually ignore the difference and just talk about instances of String
.
Two instances of String
can be concatenated with a comma.
s := 'no', ' ', 'worries'. s → 'no worries'
Since a string is a mutable collection we can also change it using the method at:put:
.
s at: 4 put: $h; at: 5 put: $u. s → 'nohurries'
Note that the comma method is defined by Collection
, so it will work for any kind of collection!
(1 to: 3),'45' → #(1 2 3 $4 $5)
We can also modify an existing string using replaceAll:with:
or replaceFrom:to:with:
as shown below. Note that the number of characters and the interval should have the same size.
s replaceAll: $n with: $N. s → 'Nohurries' s replaceFrom: 4 to: 5 with: 'wo'. s → 'No worries'
In contrast to the methods described above, the method copyReplaceAll:
creates a new string. (Curiously, here the arguments are substrings rather than individual characters, and their sizes do not have to match.)
s copyReplaceAll: 'rries' with: 'mbats' → 'No wombats'
A quick look at the implementation of these methods reveals that they are defined not only for String
s, but for any kind of SequenceableCollection
, so the following also works:
(1 to: 6) copyReplaceAll: (3 to: 5) with: { 'three'. 'etc.' } → #(1 2 'three' 'etc.' 6)
String matching. It is possible to ask whether a pattern matches a string by sending the match:
message. The pattern can specify * to match an arbitrary series of characters and # to match a single character. Note that match:
is sent to the pattern and not the string to be matched.
'Linux *' match: 'Linux mag' → true 'GNU/Linux #ag' match: 'GNU/Linux tag' → true
Another useful method is findString:
.
'GNU/Linux mag' findString: 'Linux' → 5 'GNU/Linux mag' findString: 'linux' startingAt: 1 caseSensitive: false → 5
More advanced pattern matching facilities offering the capabilities of Perl are also available, but they are not included in the standard image.1
Some tests on strings. The following examples illustrate the use of isEmpty
, includes:
and anySatisfy
: which are further messages defined not only on String
s but more generally on collections.
'Hello' isEmpty → false 'Hello' includes: $a → false 'JOE' anySatisfy: [:c | c isLowercase] → false 'Joe' anySatisfy: [:c | c isLowercase] → true
String templating. There are three messages that are useful to manage string templating: format:
, expandMacros
and expandMacrosWith:
.
'{1} is {2}' format: {'Squeak' . 'cool'} → 'Squeak is cool'
The messages of the expandMacros family offer variable substitution, using <n>
for carriage return, <t>
for tabulation, <1s>
, <2s>
, <3s>
for arguments (<1p>
, <2p>
, surrounds the string with single quotes), and <1?value1:value2>
for conditional.
'look-<t>-here' expandMacros → 'look- -here' '<1s> is <2s>' expandMacrosWith: 'Squeak' with: 'cool' → 'Squeak is cool' '<2s> is <1s>' expandMacrosWith: 'Squeak' with: 'cool' → 'cool is Squeak' '<1p> or <1s>' expandMacrosWith: 'Squeak' with: 'cool' → '"Squeak" or Squeak' '<1?Quentin:Thibaut> plays' expandMacrosWith: true → 'Quentin plays' '<1?Quentin:Thibaut> plays' expandMacrosWith: false → 'Thibaut plays'
Some other utility methods. The class String
offers numerous other utilities including the messages asLowercase
, asUppercase
and capitalized
.
'XYZ' asLowercase → 'xyz' 'xyz' asUppercase → 'XYZ' 'hilaire' capitalized → 'Hilaire' '1.54' asNumber → 1.54 'this sentence is without a doubt far too long' contractTo: 20 → 'this sent...too long'
Note that there is generally a difference between asking an object its string representation by sending the message printString
and converting it to a string by sending the message asString
. Here is an example of the difference.
#ASymbol printString → '#ASymbol' #ASymbol asString → 'ASymbol'
A symbol is similar to a string but is guaranteed to be globally unique. For this reason symbols are preferred to strings as keys for dictionaries, in particular for instances of IdentityDictionary
. See also Chapter 8 for more about String
and Symbol
.
- We strongly recommend Vassili Bykov’s regular expression package, available at www.squeaksource.com/Regex.html.