UNSAFE MODULEBoolean, character values -----------------------------------------------; IMPORT Text, Text8, Text8Short, Word, Long, Convert, FmtBuf, FmtBufF; IMPORT Real AS R, LongReal AS LR, Extended AS ER; IMPORT RealFloat, LongFloat, ExtendedFloat; Fmt
PROCEDUREInteger, unsigned values ------------------------------------------------Bool (b: BOOLEAN): TEXT = CONST Map = ARRAY BOOLEAN OF TEXT { "FALSE", "TRUE" }; BEGIN RETURN Map[b]; END Bool; PROCEDUREChar (c: CHAR): TEXT = BEGIN RETURN Text.FromChar(c); END Char;
CONST
SmallInts = ARRAY [-50..100] OF TEXT {
"-50","-49","-48","-47","-46","-45","-44","-43","-42","-41",
"-40","-39","-38","-37","-36","-35","-34","-33","-32","-31",
"-30","-29","-28","-27","-26","-25","-24","-23","-22","-21",
"-20","-19","-18","-17","-16","-15","-14","-13","-12","-11",
"-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
"20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
"30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
"40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
"50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
"60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
"70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
"80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
"90", "91", "92", "93", "94", "95", "96", "97", "98", "99",
"100"
};
PROCEDURE Int (n: INTEGER; base: Base := 10): TEXT =
BEGIN
IF FIRST(SmallInts) <= n AND n <= LAST(SmallInts) AND base = 10
THEN RETURN SmallInts[n]
ELSE RETURN AnyInt(n, base)
END
END Int;
PROCEDURE AnyInt (n: INTEGER; base: Base := 10): TEXT =
<* FATAL Convert.Failed *>
VAR chars: ARRAY [0..BITSIZE(INTEGER)] OF CHAR; used: INTEGER; BEGIN
used := Convert.FromInt(chars, n, base, prefix := FALSE);
RETURN Text.FromChars(SUBARRAY(chars, 0, used))
END AnyInt;
PROCEDURE Unsigned (n: Word.T; base: Base := 10): TEXT =
BEGIN
IF 0 <= n AND n <= LAST(SmallInts) AND base = 10
THEN RETURN SmallInts[n]
ELSE RETURN AnyUnsigned (n, base)
END
END Unsigned;
PROCEDURE AnyUnsigned (n: Word.T; base: Base := 10): TEXT =
<* FATAL Convert.Failed *>
VAR chars: ARRAY [0..BITSIZE(INTEGER)-1] OF CHAR; used: INTEGER; BEGIN
used := Convert.FromUnsigned (chars, n, base, prefix := FALSE);
RETURN Text.FromChars(SUBARRAY(chars, 0, used))
END AnyUnsigned;
PROCEDURE LongInt (n: LONGINT; base: Base := 10): TEXT =
BEGIN
IF VAL(FIRST(SmallInts), LONGINT) <= n AND n <= VAL(LAST(SmallInts), LONGINT) AND base = 10
THEN RETURN SmallInts[VAL(n, INTEGER)]
ELSE RETURN AnyLongInt(n, base)
END
END LongInt;
PROCEDURE AnyLongInt (n: LONGINT; base: Base := 10): TEXT =
<* FATAL Convert.Failed *>
VAR chars: ARRAY [0..BITSIZE(LONGINT)] OF CHAR; used: INTEGER; BEGIN
used := Convert.FromLongInt(chars, n, base, prefix := FALSE);
RETURN Text.FromChars(SUBARRAY(chars, 0, used))
END AnyLongInt;
PROCEDURE LongUnsigned (n: Long.T; base: Base := 10): TEXT =
BEGIN
IF 0L <= n AND n <= VAL(LAST(SmallInts), LONGINT) AND base = 10
THEN RETURN SmallInts[VAL(n, INTEGER)]
ELSE RETURN AnyLongUnsigned (n, base)
END
END LongUnsigned;
PROCEDURE AnyLongUnsigned (n: Long.T; base: Base := 10): TEXT =
<* FATAL Convert.Failed *>
VAR chars: ARRAY [0..BITSIZE(LONGINT)-1] OF CHAR; used: INTEGER; BEGIN
used := Convert.FromLongUnsigned (chars, n, base, prefix := FALSE);
RETURN Text.FromChars(SUBARRAY(chars, 0, used))
END AnyLongUnsigned;
Floating-point values ---------------------------------------------------
PROCEDUREThe following procedure is implemented using theReal (x: REAL; style := Style.Auto; prec: CARDINAL := R.MaxSignifDigits - 1; literal := FALSE): TEXT = CONST RealMin = MAX(6 + R.MaxExpDigits, 12); VAR da := RealFloat.ToDecimal(x); bufSz := RealMin + prec; num: FmtBufF.NumAttr; BEGIN num.class := FmtBufF.ClassMapReal[da.class]; num.kind := FmtBufF.IEEEKind.Single; num.maxExpDigits := R.MaxExpDigits; num.sign := da.sign; IF num.class = FmtBufF.Class.Number THEN num.len := da.len; num.exp := da.exp; num.errorSign := da.errorSign; INC(bufSz, MAX(1, da.exp)) END; RETURN Float(bufSz, num, da.digits, FmtBufF.FmtRec{style, prec, literal}) END Real; PROCEDURELongReal (x: LONGREAL; style := Style.Auto; prec: CARDINAL := LR.MaxSignifDigits - 1; literal := FALSE): TEXT = CONST LongMin = MAX(6 + LR.MaxExpDigits, 12); VAR da := LongFloat.ToDecimal(x); bufSz := LongMin + prec; num: FmtBufF.NumAttr; BEGIN num.class := FmtBufF.ClassMapLong[da.class]; num.kind := FmtBufF.IEEEKind.Double; num.maxExpDigits := LR.MaxExpDigits; num.sign := da.sign; IF num.class = FmtBufF.Class.Number THEN num.len := da.len; num.exp := da.exp; num.errorSign := da.errorSign; INC(bufSz, MAX(1, da.exp)) END; RETURN Float(bufSz, num, da.digits, FmtBufF.FmtRec{style, prec, literal}) END LongReal; PROCEDUREExtended (x: EXTENDED; style := Style.Auto; prec: CARDINAL := ER.MaxSignifDigits - 1; literal := FALSE): TEXT = CONST ExtdMin = MAX(6 + ER.MaxExpDigits, 12); VAR da := ExtendedFloat.ToDecimal(x); bufSz := ExtdMin + prec; num: FmtBufF.NumAttr; BEGIN num.class := FmtBufF.ClassMapExtd[da.class]; num.kind := FmtBufF.IEEEKind.Extended; num.maxExpDigits := ER.MaxExpDigits; num.sign := da.sign; IF num.class = FmtBufF.Class.Number THEN num.len := da.len; num.exp := da.exp; num.errorSign := da.errorSign; INC(bufSz, MAX(1, da.exp)) END; RETURN Float(bufSz, num, da.digits, FmtBufF.FmtRec{style, prec, literal}) END Extended; CONST StackBufSz = 100;
Float procedure in the
FmtBufF interface. That interface requires the caller to pass a character
buffer. To avoid an unnecessary allocation, these routines pass a
stack-based buffer of size StackBufSz in the fast case. Otherwise, they
allocate a sufficiently large buffer.
The analysis in the FmtBufF interface concludes the the buffer
requirements are bounded from above as follows:
Style.Sci: width <= MAX(5 + MAX(prec, 1) + T.MaxExpDigits, 12)
Style.Fix: width <= MAX(4 + MAX(prec, 1) + MAX(exp, 1), 12)
Since prec is a cardinal, we have MAX(prec, 1) <= 1 + prec. Hence, we
will use the overall conservative bound of:
All cases: width <= MAX(6 + prec + T.MaxExpDigits + MAX(exp, 1), 12)
<= MAX(6 + T.MaxExpDigits, 12) + prec + MAX(exp, 1)
The first element of this sum can be computed statically.
PROCEDUREPadding routines --------------------------------------------------------Float ( bufSz: CARDINAL; READONLY num: FmtBufF.NumAttr; VAR (*IN*) digits: FmtBufF.Digits; READONLY fmt: FmtBufF.FmtRec) : TEXT = VAR res: TEXT; BEGIN IF bufSz <= StackBufSz THEN VAR buf: ARRAY [0..StackBufSz-1] OF CHAR; cnt := FmtBufF.Float(buf, num, digits, fmt); BEGIN res := Text.FromChars(SUBARRAY(buf, 0, cnt)) END ELSE VAR buf := NEW(UNTRACED REF FmtBuf.T, bufSz); cnt := FmtBufF.Float(buf^, num, digits, fmt); BEGIN res := Text.FromChars(SUBARRAY(buf^, 0, cnt)); DISPOSE(buf) END END; RETURN res END Float;
PROCEDUREPad ( text: TEXT; length: CARDINAL; padChar: CHAR := ' '; align : Align := Align.Right) : TEXT = VAR buff: ARRAY [0..99] OF CHAR; len, padLen: INTEGER; pad: TEXT; BEGIN len := length - Text.Length(text); IF len <= 0 THEN RETURN text END; padLen := MIN(NUMBER(buff), len); FOR i := 0 TO padLen - 1 DO buff[i] := padChar END; pad := Text.FromChars(SUBARRAY(buff, 0, padLen)); WHILE len >= padLen DO IF align = Align.Right THEN text := pad & text ELSE text := text & pad END; DEC(len, padLen) END; IF len > 0 THEN IF align = Align.Right THEN text := Text.Sub(pad, 0, len) & text ELSE text := text & Text.Sub(pad, 0, len) END END; RETURN text END Pad; PROCEDUREF (fmt: TEXT; t1, t2, t3, t4, t5: TEXT := NIL): TEXT =
Construct an array of texts not including NIL texts in the suffix, and call
FN with the constructed array.
VAR
a := ARRAY [0..4] OF TEXT {t1, t2, t3, t4, t5};
pos: INTEGER := LAST(a);
BEGIN
WHILE pos >= 0 AND a[pos] = NIL DO DEC(pos) END;
RETURN FN(fmt, SUBARRAY(a, 0, pos + 1))
END F;
PROCEDURE FN (fmt: TEXT; READONLY texts: ARRAY OF TEXT): TEXT =
VAR len := Text.Length (fmt);
BEGIN
TYPECASE fmt OF
| Text8.T(t) =>
RETURN FNBuf (fmt, SUBARRAY (t.contents^, 0, len), texts);
| Text8Short.T(t) =>
RETURN FNBuf (fmt, SUBARRAY (t.contents, 0, len), texts);
(******
| Text8Literal.T(t) =>
RETURN FNBuf (fmt, SUBARRAY (t.contents, 0, len), texts);
******)
ELSE
IF (len <= 128) THEN
VAR chars: ARRAY [0..127] OF CHAR; BEGIN
Text.SetChars (chars, fmt);
RETURN FNBuf (fmt, SUBARRAY (chars, 0, len), texts);
END;
ELSE
VAR chars := NEW (REF ARRAY OF CHAR, len); BEGIN
Text.SetChars (chars^, fmt);
RETURN FNBuf (fmt, chars^, texts);
END;
END;
END;
END FN;
TYPE
FormatSpec = RECORD
start : CARDINAL; (* offset of the specifier in the format string *)
length : CARDINAL; (* length of the specifier in the format string *)
padWidth : CARDINAL;
padAlign : Align;
padChar : CHAR;
END;
PROCEDURE FNBuf (fmtTxt : TEXT;
READONLY fmt : ARRAY OF CHAR; (* == contents of fmtTxt *)
READONLY texts : ARRAY OF TEXT): TEXT =
VAR n := NUMBER(texts); specs: ARRAY [0..19] OF FormatSpec;
BEGIN
IF n <= NUMBER (specs)
THEN RETURN DoFN (fmtTxt, fmt, texts, SUBARRAY (specs, 0, n));
ELSE RETURN DoFN (fmtTxt, fmt, texts, NEW (REF ARRAY OF FormatSpec, n)^);
END;
END FNBuf;
PROCEDURE DoFN (fmtTxt : TEXT;
READONLY fmt : ARRAY OF CHAR;
READONLY texts : ARRAY OF TEXT;
VAR specs : ARRAY OF FormatSpec): TEXT =
<* FATAL Convert.Failed *>
VAR cnt := ReadSpecs(fmt, specs);
BEGIN
IF cnt # NUMBER(texts) THEN RAISE Convert.Failed; END;
IF cnt = 0 THEN RETURN fmtTxt; END; (* handle the null case *)
RETURN ConstructResult (fmt, texts, specs);
END DoFN;
PROCEDURE ReadSpecs (READONLY fmt : ARRAY OF CHAR;
VAR(*OUT*) specs : ARRAY OF FormatSpec): CARDINAL =
Scansfmtfor format specifiers, setsspecsto any that are found, and returns the number found.
VAR cnt: CARDINAL := 0; cursor := 0; ignore: FormatSpec;
BEGIN
LOOP
WHILE (cursor < NUMBER(fmt)) AND (fmt[cursor] # '%') DO INC(cursor); END;
IF (cursor >= NUMBER(fmt)) THEN RETURN cnt; END;
IF (cnt < NUMBER(specs)) THEN
IF ReadSpec(fmt, cursor, specs[cnt]) THEN INC(cnt); END;
ELSIF ReadSpec(fmt, cursor, ignore) THEN
RETURN cnt+1; (* too many *)
END;
END;
END ReadSpecs;
PROCEDURE ReadSpec (READONLY fmt : ARRAY OF CHAR;
VAR(*IN/OUT*) cursor : INTEGER;
VAR(*OUT*) spec : FormatSpec): BOOLEAN =
Reads a format specifier fromfmtbeginning atcursor. Updatescursorto reflect the characters consumed fromfmtand setsspecto the corresponding specifier. ReturnsTRUEif a complete specifier was parsed.
VAR ch: CHAR; len := NUMBER(fmt);
BEGIN
spec.start := cursor;
spec.length := 0;
spec.padAlign := Align.Right;
spec.padWidth := 0;
spec.padChar := ' ';
ch := fmt[cursor]; INC(cursor);
<*ASSERT ch = '%'*>
IF (cursor >= len) THEN RETURN FALSE; END;
ch := fmt[cursor]; INC(cursor);
(* Alignment *)
IF ch = '-' THEN
spec.padAlign := Align.Left;
IF (cursor >= len) THEN RETURN FALSE; END;
ch := fmt[cursor]; INC(cursor);
END;
(* Pad character *)
IF ch = '0' THEN
spec.padChar := '0';
IF (cursor >= len) THEN RETURN FALSE; END;
ch := fmt[cursor]; INC(cursor);
END;
(* Field width *)
WHILE '0' <= ch AND ch <= '9' DO
spec.padWidth := spec.padWidth * 10 + ORD(ch) - ORD('0');
IF (cursor >= len) THEN RETURN FALSE; END;
ch := fmt[cursor]; INC(cursor);
END;
(* terminating 's' *)
IF ch # 's' THEN RETURN FALSE; END;
spec.length := cursor - spec.start;
RETURN TRUE;
END ReadSpec;
PROCEDURE ConstructResult (READONLY fmt : ARRAY OF CHAR;
READONLY texts : ARRAY OF TEXT;
VAR specs : ARRAY OF FormatSpec): TEXT =
Allocate and return a string formed from fmt, texts and specs.
VAR
res: Text8.T;
buf: REF ARRAY OF CHAR;
fPos, rPos : INTEGER := 0;
len, argLen, pad: INTEGER;
arg: TEXT;
BEGIN
<*ASSERT NUMBER(texts) = NUMBER(specs)*>
(* first, size and allocate the result *)
len := NUMBER(fmt);
FOR i := FIRST(specs) TO LAST(specs) DO
WITH s = specs[i] DO
argLen := Text.Length(texts[i]);
INC(len, MAX(argLen, s.padWidth) - s.length);
END;
END;
res := Text8.Create(len);
buf := res.contents;
FOR i := FIRST(specs) TO LAST(specs) DO
WITH s = specs[i] DO
(* copy section of 'fmt' between this and the last spec *)
len := s.start - fPos;
IF (len > 0) THEN
SUBARRAY(buf^, rPos, len) := SUBARRAY(fmt, fPos, len);
INC(rPos, len)
END;
fPos := s.start + s.length; (* skip over the specifier *)
(* copy padded argument *)
arg := texts[i];
len := Text.Length (arg);
pad := s.padWidth - len;
IF s.padAlign = Align.Right THEN
WHILE pad > 0 DO buf[rPos] := s.padChar; INC(rPos); DEC(pad); END;
END;
IF len > 0 THEN
Text.SetChars (SUBARRAY(buf^, rPos, len), arg); INC(rPos, len);
END;
IF s.padAlign = Align.Left THEN
WHILE pad > 0 DO buf[rPos] := s.padChar; INC(rPos); DEC(pad); END;
END;
END; (*WITH*)
END; (* FOR *)
(* copy tail of format string *)
len := NUMBER(fmt) - fPos;
IF (len > 0) THEN
SUBARRAY(buf^, rPos, len) := SUBARRAY(fmt, fPos, len);
INC(rPos, len)
END;
RETURN res
END ConstructResult;
BEGIN
END Fmt.