-- Copyright (C) 1985, 1986  by Xerox Corporation. All rights reserved. 
-- CPrintFormat.mesa
-- NFS		19-Dec-85  9:01:50
-- MEW		 3-Mar-86 11:04:32

-- Routines for formatted printing in C library functions. 

DIRECTORY
  Ascii USING [NUL, SP],
  BucketAlloc USING [Alloc, Free],
  CFormatIOUtil USING [],
  CIOLib USING [FilePtr, fputc],
  CRuntime USING [z],
  CString USING [CString, IncrBPointer, ReadChar],
  DoubleIeee USING [LongDiv10],
  DoubleReal USING [
    Double, DoubleToPair, NumberType, SignedLongNumber, VeryLong0, VeryLongNumber],
  String USING [
    AppendChar, AppendCharAndGrow, AppendDecimal, AppendString, AppendSubString,
    SubStringDescriptor, WordsForString];

CPrintFormat: PROGRAM
  IMPORTS BucketAlloc, CIOLib, CRuntime, CString, DoubleIeee, DoubleReal, String
  EXPORTS CFormatIOUtil =
  {
  OPEN CIOLib;

  Justification: TYPE = {left, right};
  SignType: TYPE = {signed, unsigned};


  z: UNCOUNTED ZONE = CRuntime.z;

  MyStringBody: TYPE = RECORD [length: CARDINAL, maxlength: CARDINAL];
  MyString: TYPE = LONG POINTER TO MyStringBody;

  FPStyle: TYPE = {e, f, g};

  DoPrint: PUBLIC PROCEDURE [
    iop: FilePtr, fmt: CString.CString, argp: LONG POINTER]
    RETURNS [charsPrinted: INTEGER ← 0] = {

    GetArg: PROCEDURE [argSize: CARDINAL] RETURNS [arg: LONG POINTER] = INLINE {
      arg ← argp; argp ← argp + argSize; };

    NextChar: PROCEDURE RETURNS [c: CHAR] = INLINE {
      c ← CString.ReadChar[fmt];
      IF c # Ascii.NUL -- prevent addr. faults -- THEN
        fmt ← CString.IncrBPointer[fmt];
      };

    WriteC: PROCEDURE [c: CHAR] = INLINE {
      ci: INTEGER ← fputc[LOOPHOLE[c], iop];
      IF ci < 0 THEN ERROR OutputError;
      charsPrinted ← charsPrinted.SUCC;
      };

    WriteChars: PROCEDURE [s: LONG STRING] = {
      FOR i: CARDINAL IN [0..s.length) DO WriteC[s.text[i]]; ENDLOOP; };

    WritePadding: PROCEDURE [padding: CHAR, width: CARDINAL] = {
      THROUGH [0..width) DO WriteC[padding]; ENDLOOP; };

    ReadNum: PROCEDURE [firstDigit: CHAR] RETURNS [num: CARDINAL, c: CHAR] = {
      c ← NextChar[];
      num ← firstDigit - '0;
      WHILE c IN ['0..'9] DO num ← (num * 10) + (c - '0); c ← NextChar[]; ENDLOOP;
      };

    PrintAsNum: PROCEDURE [radix: CARDINAL, type: SignType] = {
      num: CARDINAL ← LOOPHOLE[GetArg[CARDINAL.SIZE], LONG POINTER TO CARDINAL]↑;
      nChars: CARDINAL;
      negative: BOOLEAN = (type = signed) AND (LOOPHOLE[num, INTEGER] < 0);
      s: LONG STRING;
      pos: CARDINAL;
      length: CARDINAL;
      IF isNum2 THEN ERROR BadFormat;
      IF negative THEN num ← -(LOOPHOLE[num, INTEGER]);
      nChars ← BiasedLog[LONG[num], radix];
      IF negative THEN nChars ← nChars + 1;
      length ← String.WordsForString[MAX[fieldMin, nChars]];
      s ← BucketAlloc.Alloc[length];
      LOOPHOLE[s, MyString]↑ ← [length: 0, maxlength: MAX[fieldMin, nChars]];
      s.length ← 0;
      Fill[s, padding];
      pos ← IF just = left THEN nChars - 1 ELSE s.maxlength - 1;
      IF num = 0 THEN s.text[pos] ← '0
      ELSE
        WHILE num # 0 DO
          newNum: CARDINAL ← num / radix;
          s.text[pos] ← LOOPHOLE[num - (newNum * radix) + ORD['0]];
          IF s.text[pos] > '9 THEN s.text[pos] ← s.text[pos] + ('A - '9) - 1;
          pos ← pos - 1;
          num ← newNum;
          ENDLOOP;
      IF negative THEN s.text[pos] ← '-;
      s.length ← s.maxlength;
      WriteChars[s];
      BucketAlloc.Free[@s, length];
      };

    PrintAsLongNum: PROCEDURE [radix: CARDINAL, type: SignType] = {
      num: LONG CARDINAL ← LOOPHOLE[GetArg[SIZE[LONG CARDINAL]], LONG POINTER TO
        LONG CARDINAL]↑;
      nChars: CARDINAL;
      negative: BOOLEAN = (type = signed) AND (LOOPHOLE[num, INT] < 0);
      s: LONG STRING;
      pos: CARDINAL;
      length: CARDINAL;
      IF isNum2 THEN ERROR BadFormat;
      IF negative THEN num ← -(LOOPHOLE[num, INT]);
      nChars ← BiasedLog[num, radix];
      IF negative THEN nChars ← nChars + 1;
      length ← String.WordsForString[MAX[fieldMin, nChars]];
      s ← BucketAlloc.Alloc[length];
      LOOPHOLE[s, MyString]↑ ← [length: 0, maxlength: MAX[fieldMin, nChars]];
      Fill[s, padding];
      pos ← IF just = left THEN nChars - 1 ELSE s.maxlength - 1;
      IF num = 0 THEN s.text[pos] ← '0
      ELSE
        WHILE num # 0 DO
          newNum: LONG CARDINAL ← num / LONG[radix];
          s.text[pos] ← LOOPHOLE[INTEGER[
            num - (newNum * radix.LONG) + LONG[ORD['0]]]];
          IF s.text[pos] > '9 THEN s.text[pos] ← s.text[pos] + ('A - '9) - 1;
          pos ← pos - 1;
          num ← newNum;
          ENDLOOP;
      IF negative THEN s.text[pos] ← '-;
      s.length ← s.maxlength;
      WriteChars[s];
      BucketAlloc.Free[@s, length];
      };

    PrintAsChar: PROCEDURE = {
      arg: CHAR;
      s: LONG STRING;
      length: CARDINAL = String.WordsForString[MAX[1, fieldMin]];
      IF long OR isNum2 THEN ERROR BadFormat;
      s ← BucketAlloc.Alloc[length];
      LOOPHOLE[s, MyString]↑ ← [length: 0, maxlength: MAX[1, fieldMin]];
      Fill[s, padding];
      arg ← LOOPHOLE[GetArg[INTEGER.SIZE], LONG POINTER TO CHAR]↑;
      s[IF just = left THEN 0 ELSE s.maxlength - 1] ← arg;
      s.length ← s.maxlength;
      WriteChars[s];
      BucketAlloc.Free[@s, length];
      };

    PrintAsString: PROCEDURE [maxChars: CARDINAL] = {
      s: LONG STRING;
      arg: CString.CString;
      stringLength, length: CARDINAL;
      pos: CARDINAL;
      IF long THEN ERROR BadFormat;
      arg ← LOOPHOLE[GetArg[SIZE[LONG POINTER]], LONG POINTER TO CString.CString]↑;
      stringLength ← MIN[GetStringLength[arg], maxChars];
      length ← String.WordsForString[MAX[fieldMin, stringLength]];
      s ← BucketAlloc.Alloc[length];
      LOOPHOLE[s, MyString]↑ ← [length: 0, maxlength: MAX[fieldMin, stringLength]];
      Fill[s, padding];
      pos ← IF just = left THEN 0 ELSE s.maxlength - stringLength;
      THROUGH [1..stringLength] DO
        s.text[pos] ← CString.ReadChar[arg];
        arg ← CString.IncrBPointer[arg];
        pos ← pos + 1;
        ENDLOOP;
      s.length ← s.maxlength;
      WriteChars[s];
      BucketAlloc.Free[@s, length];
      };

    PrintAsFloat: PROCEDURE [style: FPStyle] = {
      OPEN DoubleReal;
      num: SignedLongNumber;
      exp10: INTEGER;
      type: NumberType;
      precision: CARDINAL ← IF isNum2 THEN num2 ELSE 6;
      arg: Double;
      IF long THEN ERROR BadFormat;
      arg ← LOOPHOLE[GetArg[Double.SIZE], LONG POINTER TO Double]↑;
      [type: type, fr: num, exp10: exp10] ← DoubleToPair[arg, 16];
      SELECT type FROM
        nan => PrintNaN[];
        infinity => PrintInfinity[num.sign];
        normal, zero => {
          intS: LONG STRING ← ReverseStringForMag[num.mag];
          SELECT style FROM
            e => PrintAsEFloat[intS, exp10, num.sign, precision];
            f => PrintAsFFloat[intS, exp10, num.sign, precision];
            g =>
              IF (exp10 + LOOPHOLE[intS.length, INTEGER] - 1) IN
                [-4..LOOPHOLE[precision, INTEGER]] THEN
                PrintAsFFloat[intS, exp10, num.sign, precision]
              ELSE PrintAsEFloat[intS, exp10, num.sign, precision];
            ENDCASE;
          };
        ENDCASE;
      };


    PrintAsEFloat: PROCEDURE [
      intS: LONG STRING, exp10: INTEGER, negative: BOOLEAN, precision: CARDINAL] =
      {
      OPEN String;
      RoundUpE: PROCEDURE = {
        i: CARDINAL ← printS.length - 1;
        DO
          SELECT printS.text[i] FROM
            '9 => printS.text[i] ← '0;
            IN ['0..'8] => {printS.text[i] ← printS.text[i].SUCC; EXIT; };
            '. => NULL;
            ENDCASE;
          IF i # 0 THEN i ← i - 1
          ELSE {
            exp10 ← exp10.SUCC;
            printS.text[IF negative THEN 1 ELSE 0] ← '1;
            EXIT;
            };
          ENDLOOP;
        };
      -- PrintAsEFloat starts here.
      length: CARDINAL = String.WordsForString[precision + 8];  -- longest needed.
      printS: LONG STRING ← BucketAlloc.Alloc[length];
      expS: LONG STRING;
      expLength: CARDINAL;
      extraSpace: INTEGER;
      LOOPHOLE[printS, MyString]↑ ← [length: 0, maxlength: precision + 8];
      IF negative THEN AppendChar[printS, '-];
      AppendChar[printS, intS.text[intS.length - 1]];  -- digit left of decimal pt.
      IF precision # 0 THEN String.AppendChar[printS, '.];
      -- Append digits right of decimal pt.
      FOR i: CARDINAL IN [1..precision] DO
        IF i >= intS.length THEN AppendChar[printS, '0]
        ELSE AppendChar[printS, intS.text[intS.length - 1 - i]];
        ENDLOOP;
      -- Round up if necessary.
      IF intS.length > precision + 1
        AND intS.text[intS.length - precision - 2] >= '5 THEN RoundUpE[];
      exp10 ← exp10 + intS.length - 1;
      AppendChar[printS, 'E];
      SELECT exp10 FROM
        > 0 => AppendChar[printS, '+];
        < 0 => AppendChar[printS, '-];
        ENDCASE => NULL;
      expLength ← BiasedLog[exp10, 10];
      expS ← BucketAlloc.Alloc[String.WordsForString[expLength]];
      LOOPHOLE[expS, MyString]↑ ← [length: 0, maxlength: expLength];
      String.AppendDecimal[s: expS, n: exp10.ABS];
      AppendString[to: printS, from: expS];
      extraSpace ← fieldMin - printS.length;
      IF extraSpace > 0 THEN {
        IF just = right THEN {
          WritePadding[padding, extraSpace]; WriteChars[printS]; }
        ELSE {WriteChars[printS]; WritePadding[padding, extraSpace]; }}
      ELSE WriteChars[printS];
      z.FREE[@intS];
      BucketAlloc.Free[@printS, length];
      BucketAlloc.Free[@expS, String.WordsForString[expLength]];
      };


    PrintAsFFloat: PROCEDURE [
      intS: LONG STRING, exp10: INTEGER, negative: BOOLEAN, precision: CARDINAL] =
      {
      OPEN String;
      RoundUpF: PROCEDURE = {
        i: CARDINAL ← printS.length - 1;
        DO
          SELECT printS.text[i] FROM
            '9 => printS.text[i] ← '0;
            IN ['0..'8] => {printS.text[i] ← printS.text[i].SUCC; EXIT; };
            '. => NULL;
            ENDCASE;
          IF i # 0 THEN i ← i.PRED
          ELSE {
            newS: LONG STRING ← BucketAlloc.Alloc[
              String.WordsForString[printS.length + 1]];
            zeros: SubStringDescriptor;
            LOOPHOLE[newS, MyString]↑ ← [length: 0, maxlength: printS.length + 1];
            zeros ← [
              base: printS, offset: IF negative THEN 1 ELSE 0,
              length: IF negative THEN printS.length - 1 ELSE printS.length];
            newS.length ← 0;
            IF negative THEN AppendChar[newS, '-];
            AppendChar[newS, '1];
            AppendSubString[to: newS, from: @zeros];
            z.FREE[@printS];
            printS ← newS;
            EXIT;
            };
          ENDLOOP;
        };
      -- PrintAsFFloat starts here. 
      printS: LONG STRING ← z.NEW[StringBody [precision + 8]];  -- May need to grow.
      extraSpace, intSIndex: INTEGER;
      printS.length ← 0;
      IF negative THEN AppendChar[printS, '-];
      -- Append digits left of decimal pt.
      IF -exp10 >= LOOPHOLE[intS.length, INTEGER] THEN AppendChar[printS, '0];
      intSIndex ← intS.length - 1;
      THROUGH [1..exp10 + intS.length] DO
        IF intSIndex < 0 THEN AppendCharAndGrow[to: @printS, c: '0, z: z]
        ELSE {
          AppendCharAndGrow[to: @printS, c: intS.text[intSIndex], z: z];
          intSIndex ← intSIndex.PRED;
          };
        ENDLOOP;
      IF precision # 0 THEN AppendCharAndGrow[to: @printS, c: '., z: z];
      -- Append digits right of decimal pt.
      FOR i: INTEGER IN [0..LOOPHOLE[precision]) DO
        IF intSIndex < 0 OR -(exp10 + intS.length) > i THEN
          AppendCharAndGrow[to: @printS, c: '0, z: z]
        ELSE {
          AppendCharAndGrow[to: @printS, c: intS.text[intSIndex], z: z];
          intSIndex ← intSIndex.PRED;
          };
        ENDLOOP;
      -- Round up if necessary.
      IF intSIndex >= 0 AND intS.text[intSIndex] >= '5
        AND -(exp10 + intS.length) <= LOOPHOLE[precision, INTEGER] THEN RoundUpF[];
      extraSpace ← fieldMin - printS.length;
      IF extraSpace > 0 THEN {
        IF just = right THEN {
          WritePadding[padding, extraSpace]; WriteChars[printS]; }
        ELSE {WriteChars[printS]; WritePadding[padding, extraSpace]; }}
      ELSE WriteChars[printS];
      z.FREE[@intS];
      z.FREE[@printS];
      };

    PrintNaN: PROCEDURE = {
      -- Prints asterisks to indicate attempt to print a NaN.
      THROUGH [0..MAX[4, fieldMin]) DO WriteC['*]; ENDLOOP; };

    PrintInfinity: PROCEDURE [negative: BOOLEAN] = {
      s: LONG STRING ← BucketAlloc.Alloc[String.WordsForString[MAX[fieldMin, 2]]];
      pos: CARDINAL;
      LOOPHOLE[s, MyString]↑ ← [length: 0, maxlength: MAX[fieldMin, 2]];
      pos ← IF just = right THEN s.maxlength - 2 ELSE 0;
      Fill[s, padding];
      s.text[pos] ← s.text[pos + 1] ← IF negative THEN '- ELSE '+;
      s.length ← s.maxlength;
      WriteChars[s];
      BucketAlloc.Free[@s, String.WordsForString[MAX[fieldMin, 2]]];
      };


    PrintArg: PROCEDURE = {
      -- Vars. isNum2, long, just, padding, and first partially determine the state.
      c: CHAR ← NextChar[];
      first: BOOLEAN ← TRUE;
      padding ← Ascii.SP;
      isNum2 ← FALSE;
      just ← right;
      long ← FALSE;
      fieldMin ← 0;
      DO
        SELECT c FROM
          'd => {
            IF long THEN PrintAsLongNum[10, signed] ELSE PrintAsNum[10, signed];
            EXIT;
            };
          'o => {
            IF long THEN PrintAsLongNum[8, unsigned] ELSE PrintAsNum[8, signed];
            EXIT;
            };
          'x => {
            IF long THEN PrintAsLongNum[16, unsigned]
            ELSE PrintAsNum[16, unsigned];
            EXIT;
            };
          'u => {
            IF long THEN PrintAsLongNum[10, unsigned]
            ELSE PrintAsNum[10, unsigned];
            EXIT;
            };
          'c => {PrintAsChar[]; EXIT; };
          's => {PrintAsString[IF isNum2 THEN num2 ELSE CARDINAL.LAST]; EXIT; };
          'e => {PrintAsFloat[e]; EXIT; };
          'f => {PrintAsFloat[f]; EXIT; };
          'g => {PrintAsFloat[g]; EXIT; };
          'l => {long ← TRUE; first ← FALSE; c ← NextChar[]; };
          '- => {just ← left; first ← FALSE; c ← NextChar[]; };
          '. => {
            IF isNum2 THEN ERROR BadFormat;
            c ← NextChar[];
            IF NOT c IN ['0..'9] THEN ERROR BadFormat;
            isNum2 ← TRUE;
            [num2, c] ← ReadNum[c];
            first ← FALSE;
            };
          '0 => {padding ← '0; [fieldMin, c] ← ReadNum[c]; first ← FALSE; };
          IN ['1..'9] => {[fieldMin, c] ← ReadNum[c]; first ← FALSE; };
          ENDCASE => IF first THEN {WriteC[c]; EXIT; } ELSE ERROR BadFormat;
        ENDLOOP;
      };

    -- DoPrint begins here.
    BadFormat: ERROR = CODE;
    OutputError: ERROR = CODE;
    padding: CHAR;
    fieldMin: CARDINAL;
    long: BOOLEAN;
    just: Justification;
    num2: CARDINAL;
    isNum2: BOOLEAN;
    DO
      ENABLE OutputError => {charsPrinted ← -1; EXIT; };
      c: CHAR ← NextChar[];
      savePos: CString.CString ← fmt;
      SELECT c FROM
        Ascii.NUL => EXIT;
        '% => PrintArg[ ! BadFormat => {WriteC['%]; fmt ← savePos; CONTINUE}];
        ENDCASE => WriteC[c];
      ENDLOOP;

    };

  ReverseStringForMag: PROCEDURE [num: DoubleReal.VeryLongNumber]
    RETURNS [digits: LONG STRING] = {
    -- Creates a digit string from a VeryLongNumber with digits in reverse order.
    initialLength: CARDINAL = 8;
    digits ← z.NEW[StringBody [initialLength]];
    digits.length ← 0;
    IF num = DoubleReal.VeryLong0 THEN String.AppendChar[digits, '0];
    WHILE num # DoubleReal.VeryLong0 DO
      remainder: CARDINAL;
      [quo: num, rem: remainder] ← DoubleIeee.LongDiv10[num];
      String.AppendCharAndGrow[to: @digits, c: '0 + remainder, z: z];
      ENDLOOP;
    };


  Fill: PROCEDURE [s: LONG STRING, padding: CHAR] = {
    FOR i: CARDINAL IN [0..s.maxlength) DO s[i] ← padding; ENDLOOP; };

  BiasedLog: PROCEDURE [num: LONG CARDINAL, base: CARDINAL]
    RETURNS [log: CARDINAL ← 1] = {
    UNTIL num < base.LONG DO num ← num / base.LONG; log ← log + 1; ENDLOOP; };

  GetStringLength: PROCEDURE [s: CString.CString] RETURNS [length: CARDINAL ← 0] =
    {
    WHILE CString.ReadChar[s] # Ascii.NUL DO
      length ← length.SUCC; s ← CString.IncrBPointer[s]; ENDLOOP;
    };


  }.