# Chapter 5. SPECIFICATION

This chapter is in two parts; the first explains briefly the fundamentals of ADTs, which is the notation that is used in the second part to specify the functions.

The functions are formally specified using Abstract Data Types (ADT). So, what is an ADT? A few definitions follow:

• An abstract data type viewpoint simply regards a system as a collection of interacting components [Stannett, 1989].
• An abstract data type is a data type where the implementation details are concealed, and access is limited to a number of pre-defined functions [Sommerville, 1986].
• An ADT is a collection of data, together with the operations that can be carried out on that data.

In other words an ADT shows what is done, but not how it is done.

There now follows a simple example, which shows the features of ADT specifications and introduces the notation.

Problem: specify a function, doubleplus, which takes an integer between 1 and 100, and returns two values, the first being double the integer, and the second (integer + 5). If the function is supplied with an integer that is less than 1, or greater than 100, a message is generated.

```NAME
doubleplus(integer)
SETS
Z set of integers
M set of messages, consisting of 'out of range'.

FUNCTIONS
doubleplus: Z → ( Z × Z ) ∪ M

SEMANTICS
pre-doubleplus(z) ::= true
post-doubleplus(z;r) ::=
if ( z ≥ 1 ) ∧ ( z ≤ 100 )
then r = (( z * 2 ),(z+5))
else r = 'out of range'
```

First comes NAME, which simply indicates that the abstract data type doubleplus is a collection of (or works with) a set of integers.

SETS, also known as SORTS, are the objects manipulated by the operations.

FUNCTIONS, also known as SYNTAX, says what can be done to the object types in SETS. In this case there is only one operation, but there can be many. The function name is followed by a colon, which acts as a delimiter between the name and the objects. Next is a list of the sets which the function uses (in this case a list of one, namely Z). The symbol → separates the input sets from the set of results returned, and the symbol ∪ represents union in set notation. Note that:

A ∪ B = one item from the union of sets A and B.
A × B = two items (an ordered pair), the first from set A, and the second from set B.

Any value that is changed or created as a result of an operation appears (as the name of the set to which it belongs) in the result component of the function definition.

So, doubleplus returns either an ordered pair of two integers ( Z × Z ), or a message (M).

SEMANTICS is the relationship between the sets and the functions which can be applied to them (in this case just one). The pre- and post-conditions represent what is true before and after an operation.

Pre-conditions show the function name (preceded by pre-), and following this, in brackets there are variables representing the source data (conventionally lowercase equivalents of the types declared in SETS). Finally there is a declaration of the condition that must be true for the operation to be valid. (The symbol ::= has the meaning 'is defined as'). In this case there are effectively no preconditions. Note that using the operation with say a character, or a real number, is not prohibited, but the results are undefined.

Post-conditions show the function name (preceded by post-). Following this, in brackets, are variables representing the source data and results. These are separated by a semi-colon. Finally there is the relationship between the source data and the result that is true after the operation has been completed.

There are two basic ways of defining ADTs. The constructive approach, (also known as the operational or abstract model), which is used in the preceding example, defines the semantics explicitly by relating each operation to an underlying model. This is in contrast to the axiomatic model which involves an implicit definition of meaning by relating operations to one another by the use of axioms. Neither method is inherently better than the other, and both methods are used in the following specifications. The axiomatic approach is used mainly with functions that manipulate strings, and the constructive approach with screen addressing functions.

One term that is used in the specification below, but not previously defined is invariant assertion. This simply states something that is always true, or holds in a given case, and can save a lot of unnecessary repetition.

This exposition of ADTs is extremely brief, and quite superficial; interested readers may wish to pursue the topic, and a suitable source is the first three chapters of Abstract Data Types [Thomas, Robinson, Emms, 1988].

## 5.2 SPECIFICATION OF FUNCTIONS

At this stage it is appropriate to mention that the ADT specification will be used in the design/implementation of the functions. For the most part, the Requirements Document and the ADT specification say the same thing, although in a different form. However, since the ADT specification is in an algebraic form, it will be a considerable aid when the detailed design of the functions takes place. This is particularly true with the more involved functions, especially the matching functions. Moreover, the Requirements Document does not go into as much detail as the ADT, it gives only the surface view, whereas the ADT is one step closer to the logic of the algorithms that will be used.

### 5.2.1 Notes about the specification

Since all pre-conditions are true, they are not shown.

States are a collection of external variables which a function can change. The screen output, as well as global constants can be considered as variables for the purposes of specification.

Dot notation is used to indicate which items of state are being accessed, for example, state.r refers to a particular value of the main window row co-ordinates. Authors cannot directly access the state; they must do so by calling a command.

underscore represents value of state prior to execution of an operation (usually represented by a line over the word or value in question. Readers familiar with VDM will know it as hook).

If a function is used within another function, then the state is not shown in the called function. Without this convention, clarity is lost. The function alpha_match contains a good example:

removeblanks(removepunct(upper(string)))

would be written:

removeblanks(removepunct(upper(string),state),state),state)

The second version is certainly not as clear as the first.

An informal description precedes each function that is not described in the Requirements Document and/or the User Manual, except where the operation of the function is self-evident.

Comments are enclosed by /* and */ .

Double quotes are used to indicate the start and end of literal strings.

All ∧ should be taken as conditional AND. In other words, if any condition is false within a given predicate, then that predicate is false, and subsequent conditions in that predicate need not be evaluated.

All ∨ should be taken as conditional OR. In other words, if any condition is true within a given predicate, then that predicate is true, and subsequent conditions in that predicate need not be evaluated.

In practice, when the specification is converted to code, the sequence of conditions tested should be followed strictly, to prevent the evaluation of conditions whose result is undefined. For example, if a string, s, is empty, there is not much point in trying to evaluate say, head(tail(s)). Some languages would raise an error in this situation.

### 5.2.2 Functions not formally defined

The following functions are not formally defined here, but they are used in the specification; informal descriptions are given.

exit is called by author_error; it simply exits the author's program, and returns control to the operating system.

goto transfers control to a specified section of the program.

head returns the first item from a list of items. If the list is empty, head(list) = NIL.

if executes a command, or set of commands, if a given condition holds.

isin takes an item and a list. It returns true if the item is in the list, or false otherwise.

keypress waits for any key to be pressed.

legalscmp checks that the match parameter list (argument) to the function setcurrentmp conforms to the syntax requirements, and is semantically meaningful. For example, the list ALL, NONE is nonsense, and would be rejected.

legalsmp checks that the match parameter list (argument) to the function set_match_params conforms to the syntax requirements, and is semantically meaningful.

length returns an integer corresponding to the number of items in a list.

linesin returns an integer corresponding to the number of screen lines that a string would occupy if it were displayed.

sequence executes a list of actions sequentially.

tail returns the tail of a list, that is, the list with the first item removed. If the list contains one item, then tail(list) = NIL, and if the list contains no items (ie NIL), then tail(list) is undefined.

write writes to the main screen area.

tokenise takes a string, and returns a list of strings, the first and last characters of each string in the list being spaces (there are no spaces within each string in the list).

Three functions that are discussed in the Requirements Document, but which are not specified (or implemented) are matchreal, matchinteger, and match_ordered. If they are to be specified at later date, then SETS will have to be altered. Specifically, number match parameters would have to be incorporated into the match parameters set, MP, and the related parts of STATE (ie DEFAULTMP and CURRENTMP). The match results set, MR, would need additional members, to cater for the results of the number match functions.

### 5.2.3 Abstract Data Type

program(state)

#### SETS

 AE set of Author Error messages = { ael, ae2, ae3, ae4, ae5, ae6, ae7, ae8, ae9, ae10, aell, ael2 } B set of boolean values, = { true, false } BLANKTEST set of blank test options, = B C set of main window cursor column positions CASETEST set of case test options, = B CHAR set of characters CURRENTMP set of current match test values = (BLANKTEST × CASETEST × PUNCTTEST × SPELLTEST) DEFAULTMP set of default match test values = (BLANKTEST × CASETEST × PUNCTTEST × SPELLTEST) HEADWIDTH set of header window widths MAINWIDTH set of main window widths MAXROWS set of possible maximum rows for main window MESSWIDTH set of message window widths MP set of match parameters { BLANK, CASE, PUNCT, SPELL, ALL, NONE, DEF } MR set of match results { MATCH, NOMATCH, NOANS, HELPREQ, OPTI, OPT2, OPT3, OPT4, OPT5, OPT6, PM1, PM2, PM3, PM4, PM5, PM6, PM7, PM8, AE } PUNCTTEST set of punctuation test options, = B R set of main window cursor row positions S set of strings = CHAR+ SPACING set of allowed blank rows between multiple-choice options, = {0, 1, 2} SPELLTEST set of spell test options, = B STATE set of states, = ( HEADER × MAIN × MESSAGE × R × C × DEFAULTMP × CURRENTMP × HEADWIDTH × MAINWIDTH × MESSWIDTH × MAXROWS × SPACING × MAXANSLEN ) WCARD set of wildcards = { * } WCHAR set of wildcharacters = { ? } Z set of integers + non-empty set of lists, thus eg MP+ = set of lists of MP, excluding the empty set

note

HEADER, MAIN, and MESSAGE are not specified further, but informally, they are what the user sees on the screen (ie the screen display).

HEADWIDTH, MAINWIDTH, and MESSWIDTH would in the implementation be less than or equal to the number of screen columns available on the monitor. The windows themselves would probably be centred, but this is not mandatory.

Since S = CHAR+, if not (tail(s) = NIL) then head(tail(s)) ∈ CHAR.

HELPKEY ∈ S. It is used in the specification to return a unique string from getanswer, which indicates that help has been requested. In practice it would consist of just one character.

##### Author Error messages

[] indicates that the content of the variable or state will be shown on the screen.

ael = Author error in clearbetween: illegal row parameters. Row values must be between 1 and [state.maxrows] inclusive, and rowl ≤ row2. You set: rowl = zl, row2 = z2.

ae2 = Author error in getanswer: length less than 1 or length+current position > border or length > maximum allowed length [maxanslen].

ae3 = Author error in header: length of text too long. Maximum [headwidth] characters allowed. The text contains [length(text)] characters.

ae4 = Author error in match_any: required number of matches is out of range (2 to 9).

ae5 = Author error in match_any: number of words in model is greater than the maximum allowed (9).

ae6 = Author error in match_any: required number of matches ([z]) is greater than the number of words ([words in model]) in the model answer.

ae7 = Author error in mc: Not enough screen lines to display all options. Use one of the clear functions to remove previous text.

ae8 = Author error in mc: length of an option is greater than maximum allowed ([state.mainwidth]).

ae9 = Author error in message: length of text too long. Maximum [state.messwidth] characters allowed. The text contains [length(s)] characters.

ae10 = Author error in putcursor: values out of range. Row must be between 1 and [state.maxrows] inclusive. Col must be between 1 and [state.mainwidth] inclusive. You set row = [zl], col = [z2].

ae11 (raised in the setcurrentmp function) = Author error in match parameter list of a matching function.

ael2 = Author error in set_match_params: illegal match parameter.

#### FUNCTIONS

alphamatch: S × S × MP+ × STATE → MR

author_error: AE × STATE → STATE

clearall: STATE → STATE

clearbetween: Z × Z × STATE → STATE ∪ (AE × STATE)

cleardisplay: STATE → STATE

compare: S × S → B

compare_without_assist: S × S → B

createprogram: → STATE

findword: S × S × MP+ × STATE → MR

getanswer: Z × STATE → (S × STATE) ∪ (AE × STATE)

header: S × STATE → STATE ∪ (AE × STATE)

matchesin: S+ × S × MP+ × Z × STATE → Z

match_any: Z × S × S × MP+ → MR

match_to_next_wild: S × S

mc2: S × S × S × STATE → (MR × STATE) ∪ (AE × STATE)

mc3: S × S × S × S × STATE → (MR × STATE) ∪ (AE × STATE)

mc4: S × S × S × S × S × STATE → (MR × STATE) ∪ (AE × STATE)

mc5: S × S × S × S × S × S × STATE → (MR × STATE) ∪ (AE × STATE)

mc6: S × S × S × S × S × S × S × STATE → (MR × STATE) ∪ (AE × STATE)

message: S × STATE → STATE ∪ (AE × STATE)

pause: STATE → STATE

putcursor: Z × Z × STATE → STATE ∪ (AE × STATE)

removeblanks: S × STATE → S

removepunct: S × STATE → S

setcurrentmp: MP+ × STATE → STATE ∪ (AB × STATE)

set_match_params: MP+ × STATE → STATE ∪ (AB × STATE)

skiptill: CHAR × S → S

test: S × S → B

upper: S × STATE → S

#### SEMANTICS

```post-alphamatch(sl, s2, mp+, state;r)::=
if length(s2) = 0 then r = NOANS
else if s2 = HELPKEY then r = HELPREQ
else sequence
(
setcurrentmp( mp+ )

(if test( removeblanks(removepunct(upper(sl))),
removeblanks(removepunct(upper(s2))) )
then r = MATCH
else r = NOMATCH )
)

```

author_error displays an error message on screen if an author makes a semantic error in his/her program code, such as trying to locate the cursor at column 75 on a 70 column window. It displays the message until the author presses a key, whereupon the screen is cleared, and the program will terminate.

```post-author_error(ae, state; r)::=
r = sequence
(
(state.main = state.main [curvearrowright] ae [curvearrowright] "Press any key to continue…"),
keypress,
(state.main = empty ∧ state.r = 1 ∧ state.c = 1),
exit
)
/* the symbol [curvearrowright] is borrowed from VDM, where it represents
concatenation. The addition of characters to a screen display can be considered
as a concatenation, hence the reason for the use of this symbol. */

```
```post-clearall(state;r)::= r =
(
state.main = empty ∧
state.message = empty ∧
state.r = 1
state.c = 1
)

```
```post-clearbetween(zl, z2,state; r)::=
if ( (zl < 1) ∨ (zl > state.maxrows) ∨
(z2 < 1) ∨ (z2 > state.maxrows) ∨ (zl > z2) )
then r = author_error(ael)
else
(
if ( (state.r ≥ z1) ∧ (state.r ≤ z2)
then r =
(state.main is cleared between rows zl and z2 inclusive
∧ state.r  = zl ∧ state.c = 1)

if ( (state.r  < zl) ∨ (state.r > z2) )
then r =
(state.main is cleared between rows zl and z2 inclusive
∧ state.r = state.r
∧ state.c = state.c)

```
```
post-cleardisplay(state;r)::= r=
(
state.main = empty ∧
state.r = ∧
state.c = 1
)

```

Compare is a boolean function that is used by the spelling assistant to determine the relationship (if any) between an answer string and a model string. Multiple numbers of wildcards and wildcharacters are allowed in the model, there being no restriction on their location in the model. One typographical error (ie insertion, omission, transposition, substitution) for each segment of a model enclosed by wildcards/characters is allowed in each matching segment of an answer. (See also compare_without_assistant).

```
post-compare(sl,s2;r)::= r <==>

/* All strings created by the tokenise function end with a space.

wchar represents a wildcharacter, wcard represents a wildcard. These may
be embedded in a model string.

Since all ∨ and ∧ are conditional, errors will not arise due to
trying to take, for example, tail(tail (string)) when length(string) = 1.
*/

(

∨  /* no error, continue */

∨  /* wildcharacter */
(
(
/* end of string, and omission, but match found */

compare(tail(sl), tail(s2))  /* continue */
)
)

∨  /* wildcard

let ch_plusrest = skiptill( head(tail(sl)), s2) in

( (  /* skip garbage and compare
not (ch_plusrest = NIL) ∧
compare(tail(sl), ch_plusrest)
)

∨
( not (head(tail(tail(sl))) = NIL) ∧
/* first char of tail(sl) is not in tail(s2)—possible
omission, so try again with next char in tail (s1). match_to_n_w
because omission is an error. All other errors catered for using
previous predicate. */

let ch_pr2 = skiptill( head(tail(tail(sl))), s2) in
(
not (ch_pr2 = NIL) ∧
match_to_next_wild( tail(sl), ch_pr2)
)
)

∨
/* first character in ch_plusrest was not the first character of
required portion of s2, but was (part of) some garbage, which is
allowed with wildcards. It could not have been a space. Find next
section of ch_plusrest which starts with h(t(sl)) and try to match.
*/

compare( sl, skiptill( head(tail(sl)), tail(ch_plusrest) ))
)
)

∨  /* substitution */
match_to_next_wild( tail(sl), tail(s2) )

∨ /* omission */
(
match_to_next_wild( tail(sl), s2)
)

∨  /* transposition */
(
match_to_next_wild(tail(tail(sl)), tail(tail(s2)) )
)

∨  /* insertion */
(
(
/* insertion at end of string, match found */
∨
match_to_next_wild( sl, tail(s2))
)
)
)
```

compare_without_assistant is a boolean function that is used to determine the relationship (if any) between an answer string and a model string. Multiple numbers of wildcards and wildcharacters are allowed in the model, there being no restriction on their location in the model. No typographical errors (ie insertion, omission, transposition, substitution) are allowed. (See also compare.)

```
post-compare_without_assistant(sl,s2;r)::= r <==>

∨  /* no error, continue */
(
compare_without_assistant( tail(sl), tail(s2))
)

∨  /* wildcharacter
(
compare_without_assistant(tail(sl), tail(s2))
)

∨  /* wildcard */
(
let ch_plusrest = skiptill( head(tail(sl)), s2) in

( (  /* skip garbage and compare */
not (ch_plusrest = NIL) ∧
compare_without_assistant( tail(sl), ch_plusrest)
)
∨
/* first character in ch_plusrest was not the first character of
required portion of s2, but was (part of) some garbage, which is
allowed with wildcards. It could not have been a space. Find next
section of ch_plusrest which starts with h(t(sl)) and try to
match. */

tail(ch_plusrest) ))

```

createprogram contains all the initialisation procedures, and sets up the required starting state.

```post-createprogram(;r)::= r = state =
(
state.main     = empty ∧
state.message  = empty ∧
state.r = 1 ∧
state.c = 1 ∧
state.defaultmp.blanktest   = false ∧
state.defaultmp.casetest    = false ∧
state.defaultmp.puncttest   = false ∧
state.defaultmp.spelltest   = false ∧
state.currentmp.blanktest   = undefined ∧
state.currentmp.casetest    = undefined ∧
state.currentmp.puncttest   = undefined ∧
state.currentmp.spelltest   = undefined ∧

/* the state variables below are allowed to take other values, but as far
as the prototype implementation of the authoring functions is concerned,
they will not. Therefore, absolute values have been assigned to them. */

state.mainwidth   = 80 ∧
state.messwidth   = 80 ∧
state.maxrows     = 23 ∧
state.spacing     =  1 ∧
state.maxanslen   = 80
)

```
```
post-findword(sl, s2, mp+, state;r)::=
if length(s2) = 0 then r = NOANS
else if s2 = HELPKEY then r = HELPREQ

else sequence
(
setcurrentmp( mp+ ),

let csl = removepunct(upper(sl)) and
cs2 = removepunct(upper(s2)) and
wordlist = tokenise(cs2) in

(if head(wordlist) = NIL then r = NOMATCH
else
if (test( csl, head(wordlist)) ) ∨
(test( csl, tail(wordlist)))
then r = MATCH )
)

```
```
if( z > 0 ∧ z < state.maxanslen ∧
z+state.c < state.mainwidth )

then
sequence(
(draw line z chars long starting at current cursor) ∧
(r = input string, or HELPKEY if Fl is pressed) )
else r = author_error(ae2)

```
```
if (length(s) < state.headwidth) ∧ (not linesin(s) > 1)
then r = (state.header = s)
else r = author_error(ae3)

```

matchesin returns the number of words in s+ that can be matched with words in s.

```post-matchesin(s+, s, mp+, z, state;r)::=
/* in implementation, the initial call to matchesin would be made with
z = 0 */
r = z = 0 <==> ( (head(s+) = NIL) ∧ (z = 0) )

r = z > 0 <==>
(
(head(s+)   NIL) ∧ (z > 0) )
∨
(findword(head(s+), s, MP+) = MATCH) ∧
matchesin(tail(s+), s, MP+, z+l) )

∨
matchesin(tail(s+), s, MP+, z)
)

```
```post-match_any(z,sl,s2,mp+,state;r)::=
let modellist = tokenise(sl) in
if ( z < 2 ) ∨ (z > 9 ) then r = author_error(ae4)
if length( modellist ) > 9 then r = author_error(ae5)
if length( modellist ) < 2 then r = author_error(ae6)
if length(s2) = 0 then r = NOANS
if s2 = HELPKEY then r = HELPREQ
let wordsfound = matchesin(modellist,s2,mp+,0) in

if wordsfound ≥ z then r = MATCH
if wordsfound =  0 then r = NOMATCH
else
(
if wordsfound  = 1 then r =  PM1
if wordsfound  = 2 then r =  PM2
if wordsfound  = 3 then r =  PM3
if wordsfound  = 4 then r =  PM4
if wordsfound  = 5 then r =  PM5
if wordsfound  = 6 then r =  PM6
if wordsfound  = 7 then r =  PM7
if wordsfound  = 8 then r =  PMB
)

```

match_to_next_wild looks for an exact match between sl and s2, and returns true if it finds one. If there is a wildcard or wildcharacter in sl, the function looks for an exact character match between the strings up to, but not including, this wildcard/character, whereupon it calls compare with the remainder of the strings.

```
post-match_to_next_wild(sl,s2;r) <==>
(
∨
match_to_next_wild(tail(sl), tail(s2)) )

∨
(head(sl) = wchar ) ∧ compare(sl,s2) )

∨
(head(sl) = wcard ) ∧ compare(sl,s2) )

/* This does stop immediately, because compare uses the tail of sl in
its comparison if head(sl) is wild */

```
```post-mc2(sl,s2,s3,state;r)::=
(
if ( (state.r + linesin(sl) + (3 * (state.spacing+l)) +1) > state.maxrows)
then author_error(ae7)

if (length(s2) > state.mainwidth) ∨
(length(s3) > state.mainwidth)
then r = author_error(ae8)

else r = sequence
(
(display stem and options),
(state.message = "Up/down arrows to move bar, Enter to select,
Fl = HELP."),
(if s2 is selected then r =
(
OPT1 ∧
(state.r = (state.r + linesin(sl) +
(3 * (state.spacing+l)) +1) ) ∧
(state.c = 1)
)

(if s3 is selected then r =
(
OPT2 ∧
(state.r = (state.r + linesin(sl) +
(3 * (state.spacing+l)) +1) ∧
(state.c =1)
)
)
)
)

```

Note: mc3, mc4, mc5, mc6 are as mc2, with the number of options in the sequence equal to the number of strings less one.

```post-message(s,state;r)::=
if (length(s) < state.messwidth) ∧ (not linesin(s) > 1)
then r = (state.message = s)
else r = author_error(ae9)

```
```post-pause(state;r)::=
r = sequence(
(state.message = "Press any key to continue…"
keypress,
(state.message = empty)

```
```post-putcursor(zl,z2,state;r)::=
if (zl < 1) ∨ (z2 > state.maxrows)
(z2 < 1) ∨ (z2 > state.mainwidth)
then r = author_error(ae10)

else r = (state.r = zl ∧ state.c = z2)

```
```post-removeblanks(s, state;r)::=
if state.currentmp.blanktest = false
then r = the string with blanks removed to leave one blank between words.

else r = s

```
```post-removepunct(s, state;r)::=
if state.currentmp.puncttest = false
then r = s without punctuation
else r = s

```

setcurrentmp is similar in action to the set_match_params function, the chief difference being that it is used to set the current match parameters, which are used by the matching functions.

```
post-setcurrentmp(mp+ ;r)::=
if legalscmp(mp+)
then
(
then r = (state.currentmp = state.defaultmp)

then r =
(state.currentmp.blanktest = true ∧
state.currentmp.casetest = true ∧
state.currentmp.puncttest = true ∧
state.currentmp.spelltest = true )

then r =
(state.currentmp.blanktest = false ∧
state.currentmp.casetest = false ∧
state.currentmp.puncttest = false ∧
state.currentmp.spelltest = false )

if ( (not head(mp+) = ALL ) ∧
(not head(mp+) = DEF ) ) )
then r =
(
if isin(BLANK, mp+)
then state.currentmp.blanktest = true
else state.currentmp.blanktest = false

∧
if isin(CASE, mp+)
then state.currentmp.casetest = true
else state.currentmp.casetest = false

∧
if isin(PUNCT, mp+)
then state.currentmp.puncttest = true
else state.currentmp.puncttest = false

∧
if isin(SPELL, mp+)
then state.currentmp.spelltest = true
else state.currentmp.spelltest = false
)
)
else r = author_error(aell)

```
```post-set_match_params(mp+;r)::=
if legalsmp(mp+)
then
(
if (head(mp+) = ALL) then r =
(state.defaultmp.blanktest  = true ∧
state.defaultmp.casetest   = true ∧
state.defaultmp.puncttest  = true ∧
state.defaultmp.spelltest  = true )

if (head(mp+) = NONE) then r =
(state.defaultmp.blanktest   = false ∧
state.defaultmp.casetest    = false ∧
state.defaultmp.puncttest   = false ∧
state.defaultmp.spelltest   = false )

then r =
(
if isin(BLANK, mp+)
then state.defaultmp.blanktest = true
else state.defaultmp.blanktest = false

∧
if isin(CASE, mp+)
then state.defaultmp.casetest = true
else state.defaultmp.casetest = false

∧
if isin(PUNCT, mp+)
then state.defaultmp.puncttest = true
else state.defaultmp.puncttest = false

∧
if isin(SPELL, mp+)
then state.defaultmp.spelltest = true
else state.defaultmp.spelltest = false
)
)
else r = author_error(ael2)

```

skiptill takes a character and a string, finds the first occurrence of the character in the string, and returns the rest of the string from that character inclusive onwards. If the character is not in the string, an empty string is returned.

```
post-skiptill(char,s;r)::=
then r = NIL

else
(
if char = head(s) then r = s
else r = (skiptill( char, tail(s)))
)

```
```
post-test(sl,s2;r = true) <==>
(
( (state.currentmp.spelltest = true) ∧
(compare_without_assistant(sl, s2) )
/* compare_without_assistant does not allow spelling errors */

∨
( (state.currentmp.spelltest = false ∧ compare(sl,s2) )
)

```
```post-upper(s, state;r)::=
if state.currentmp.casetest = false
then r = uppercase version of s
else r = s
```