1(*	$Id: IntConv.Mod,v 1.6 2002/05/26 12:15:17 mva Exp $	*)
2MODULE IntConv;
3(*
4    IntConv -  Low-level integer/string conversions.
5    Copyright (C) 2000, 2002 Michael van Acken
6    Copyright (C) 1995 Michael Griebling
7
8    This module is free software; you can redistribute it and/or modify
9    it under the terms of the GNU Lesser General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12
13    This module is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU Lesser General Public License for more details.
17
18    You should have received a copy of the GNU Lesser General Public
19    License along with this program; if not, write to the Free Software
20    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
22*)
23
24IMPORT
25  Char := CharClass, Conv := ConvTypes;
26
27TYPE
28  ConvResults* = Conv.ConvResults;
29  (**One of @oconst{strAllRight}, @oconst{strOutOfRange},
30     @oconst{strWrongFormat}, or @oconst{strEmpty}.  *)
31
32CONST
33  strAllRight*=Conv.strAllRight;
34  (**The string format is correct for the corresponding conversion.  *)
35  strOutOfRange*=Conv.strOutOfRange;
36  (**The string is well-formed but the value cannot be represented.  *)
37  strWrongFormat*=Conv.strWrongFormat;
38  (**The string is in the wrong format for the conversion.  *)
39  strEmpty*=Conv.strEmpty;
40  (**The given string is empty.  *)
41
42
43VAR
44  W, S, SI: Conv.ScanState;
45  minInt, maxInt: ARRAY 11 OF CHAR;
46
47CONST
48  maxDigits = 10;                        (* length of minInt, maxInt *)
49
50
51(* internal state machine procedures *)
52
53PROCEDURE WState(inputCh: CHAR; VAR chClass: Conv.ScanClass; VAR nextState: Conv.ScanState);
54BEGIN
55  IF Char.IsNumeric(inputCh) THEN chClass:=Conv.valid; nextState:=W
56  ELSE chClass:=Conv.terminator; nextState:=NIL
57  END
58END WState;
59
60
61PROCEDURE SState(inputCh: CHAR; VAR chClass: Conv.ScanClass; VAR nextState: Conv.ScanState);
62BEGIN
63  IF Char.IsNumeric(inputCh) THEN chClass:=Conv.valid; nextState:=W
64  ELSE chClass:=Conv.invalid; nextState:=S
65  END
66END SState;
67
68
69PROCEDURE ScanInt*(inputCh: CHAR; VAR chClass: Conv.ScanClass; VAR nextState: Conv.ScanState);
70 (**Represents the start state of a finite state scanner for signed whole
71    numbers---assigns class of @oparam{inputCh} to @oparam{chClass} and a
72    procedure representing the next state to @oparam{nextState}.
73
74    The call of @samp{ScanInt(inputCh,chClass,nextState)} shall assign values
75    to @oparam{chClass} and @oparam{nextState} depending upon the value of
76    @oparam{inputCh} as shown in the following table.
77
78    @example
79    Procedure       inputCh         chClass         nextState (a procedure
80                                                    with behaviour of)
81    ---------       ---------       --------        ---------
82    ScanInt         space           padding         ScanInt
83                    sign            valid           SState
84                    decimal digit   valid           WState
85                    other           invalid         ScanInt
86    SState          decimal digit   valid           WState
87                    other           invalid         SState
88    WState          decimal digit   valid           WState
89                    other           terminator      --
90    @end example
91
92    NOTE 1 -- The procedure @oproc{ScanInt} corresponds to the start state of a
93    finite state machine to scan for a character sequence that forms a signed
94    whole number.  It may be used to control the actions of a finite state
95    interpreter.  As long as the value of @oparam{chClass} is other than
96    @oconst{Conv.terminator} or @oconst{Conv.invalid}, the
97    interpreter should call the procedure whose value is assigned to
98    @oparam{nextState} by the previous call, supplying the next character from
99    the sequence to be scanned.  It may be appropriate for the interpreter to
100    ignore characters classified as @oconst{Conv.invalid}, and proceed
101    with the scan.  This would be the case, for example, with interactive
102    input, if only valid characters are being echoed in order to give
103    interactive users an immediate indication of badly-formed data.  If the
104    character sequence end before one is classified as a terminator, the
105    string-terminator character should be supplied as input to the finite state
106    scanner.  If the preceeding character sequence formed a complete number,
107    the string-terminator will be classified as @oconst{Conv.terminator},
108    otherwise it will be classified as @oconst{Conv.invalid}.  *)
109BEGIN
110  IF Char.IsWhiteSpace(inputCh) THEN chClass:=Conv.padding; nextState:=SI
111  ELSIF (inputCh="+") OR (inputCh="-") THEN chClass:=Conv.valid; nextState:=S
112  ELSIF Char.IsNumeric(inputCh) THEN chClass:=Conv.valid; nextState:=W
113  ELSE chClass:=Conv.invalid; nextState:=SI
114  END
115END ScanInt;
116
117
118PROCEDURE FormatInt*(str: ARRAY OF CHAR): ConvResults;
119(**Returns the format of the string value for conversion to LONGINT.  *)
120VAR
121  ch: CHAR;
122  index, start: INTEGER;
123  state: Conv.ScanState;
124  positive: BOOLEAN;
125  prev, class: Conv.ScanClass;
126
127PROCEDURE LessOrEqual (VAR high: ARRAY OF CHAR; start, end: INTEGER): BOOLEAN;
128  VAR
129    i: INTEGER;
130  BEGIN  (* pre: index-start = maxDigits *)
131    i := 0;
132    WHILE (start # end) DO
133      IF (str[start] < high[i]) THEN
134        RETURN TRUE;
135      ELSIF (str[start] > high[i]) THEN
136        RETURN FALSE;
137      ELSE  (* str[start] = high[i] *)
138        INC (start); INC (i);
139      END;
140    END;
141    RETURN TRUE;                       (* full match *)
142  END LessOrEqual;
143
144BEGIN
145  index:=0; prev:=Conv.padding; state:=SI; positive:=TRUE; start := -1;
146  LOOP
147    ch:=str[index];
148    state.p(ch, class, state);
149    CASE class OF
150    | Conv.padding: (* nothing to do *)
151
152    | Conv.valid:
153        IF ch="-" THEN positive:=FALSE
154        ELSIF ch="+" THEN positive:=TRUE
155        ELSIF (start < 0) & (ch # "0") THEN
156          start := index;
157        END
158
159    | Conv.invalid:
160      IF (prev = Conv.padding) & (ch = 0X) THEN
161        RETURN strEmpty;
162      ELSE
163        RETURN strWrongFormat;
164      END;
165
166    | Conv.terminator:
167      IF (ch = 0X) THEN
168        IF (index-start < maxDigits) OR
169           (index-start = maxDigits) &
170            (positive & LessOrEqual (maxInt, start, index) OR
171             ~positive & LessOrEqual (minInt, start, index)) THEN
172          RETURN strAllRight;
173        ELSE
174          RETURN strOutOfRange;
175        END;
176      ELSE
177        RETURN strWrongFormat;
178      END;
179    END;
180    prev:=class; INC(index)
181  END;
182END FormatInt;
183
184
185PROCEDURE ValueInt*(str: ARRAY OF CHAR): LONGINT;
186(**Returns the value corresponding to the signed whole number string value
187   @oparam{str} if @oparam{str} is well-formed.  Otherwise, result is
188   undefined.  *)
189VAR
190  i: INTEGER;
191  int: LONGINT;
192  positive: BOOLEAN;
193BEGIN
194  IF FormatInt(str)=strAllRight THEN
195    (* here holds: `str' is a well formed string and its value is in range *)
196
197    i:=0; positive:=TRUE;
198    WHILE (str[i] < "0") OR (str[i] > "9") DO    (* skip whitespace and sign *)
199      IF (str[i] = "-") THEN
200        positive := FALSE;
201      END;
202      INC (i);
203    END;
204
205    int := 0;
206    IF positive THEN
207      WHILE (str[i] # 0X) DO
208        int:=int*10 + (ORD(str[i]) - ORD("0"));
209        INC (i);
210      END;
211    ELSE
212      WHILE (str[i] # 0X) DO
213        int:=int*10 - (ORD(str[i]) - ORD("0"));
214        INC (i);
215      END;
216    END;
217    RETURN int;
218  ELSE                                   (* result is undefined *)
219    RETURN 0;
220  END
221END ValueInt;
222
223
224PROCEDURE LengthInt*(int: LONGINT): INTEGER;
225(**Returns the number of characters in the string representation of
226   @oparam{int}.  This value corresponds to the capacity of an array @samp{str}
227   which is of the minimum capacity needed to avoid truncation of the result in
228   the call @samp{IntStr.IntToStr(int,str)}.  *)
229VAR
230  cnt: INTEGER;
231BEGIN
232  IF int=MIN(LONGINT) THEN
233    RETURN maxDigits+1;
234  ELSE
235    IF int<=0 THEN int:=-int; cnt:=1
236    ELSE cnt:=0
237    END;
238    WHILE int>0 DO INC(cnt); int:=int DIV 10 END;
239    RETURN cnt;
240  END;
241END LengthInt;
242
243BEGIN
244  (* kludge necessary because of recursive procedure declaration *)
245  NEW(S); NEW(W); NEW(SI);
246  S.p:=SState; W.p:=WState; SI.p:=ScanInt;
247  minInt := "2147483648";
248  maxInt := "2147483647";
249END IntConv.
250