PreviousUpNext

15.4.1006  src/lib/src/sfprintf.pkg

## sfprintf.pkg -- support for printf/fprintf/sprintf functionality.

# Compiled by:
#     src/lib/std/standard.lib

# TODO  XXX BUGGO FIXME
#   - field widths in scan
#   - add PREC of (Int * format_item) constructor to allow dynamic control of
#     precision.
#   - precision in %d, %s, ...
#   - * flag in scan (checks, but doesn't scan input)
#   - %n specifier in scan

stipulate
    package f8b =  eight_byte_float;                                    # eight_byte_float      is from   src/lib/std/eight-byte-float.pkg
    package fil =  file__premicrothread;                                # file__premicrothread  is from   src/lib/std/src/posix/file--premicrothread.pkg
herein

    package   sfprintf
    : (weak)  Sfprintf                                                  # Sfprintf              is from   src/lib/src/sfprintf.api
    {
        package ss = substring;                                         # substring             is from   src/lib/std/substring.pkg
        package sc = number_string;                                     # number_string         is from   src/lib/std/src/number-string.pkg
                                                                        # printf_field          is from   src/lib/src/printf-field.pkg
        include package   printf_field;

        exception BAD_FORMAT_LIST;

        fun pad_left  (str, pad) =  sc::pad_left  ' ' pad str;
        fun pad_right (str, pad) =  sc::pad_right ' ' pad str;
        fun zero_lpad (str, pad) =  sc::pad_left  '0' pad str;
        fun zero_rpad (str, pad) =  sc::pad_right '0' pad str;

        # Int to string conversions (for positive integers only):
        #
        stipulate

            my (max_int2, max_int8, max_int10, max_int16)
                =
                case large_int::max_int
                    #              
                    THE n
                        =>
                        {   max_p1 =  large_unt::from_multiword_int n + 0u1;
                            #
                            ( large_unt::format sc::BINARY  max_p1,
                              large_unt::format sc::OCTAL   max_p1,
                              large_unt::format sc::DECIMAL max_p1,
                              large_unt::format sc::HEX     max_p1
                            );
                        };

                    NULL =>   ("", "", "", "");
                esac;
        herein
            # MAX_INT is used to represent the absolute value
            # of the largest representable negative integer.
            #
            Posint
                = POS_INT  large_int::Int
                | MAX_INT
                ;

            fun int_to_binary MAX_INT     =>  max_int2;
                int_to_binary (POS_INT i) =>  large_int::format sc::BINARY i;
            end;

            fun int_to_octal MAX_INT     =>  max_int8;
                int_to_octal (POS_INT i) =>  large_int::format sc::OCTAL i;
            end;

            fun int_to_string MAX_INT     =>  max_int10;
                int_to_string (POS_INT i) =>  large_int::to_string i;
            end;

            fun int_to_hex MAX_INT     =>  max_int16;
                int_to_hex (POS_INT i) =>  large_int::format sc::HEX i;
            end;

            fun int_to_he_x i
                =
                string::implode (
                    vector_of_chars::fold_backward (\\ (c, l) = char::to_upper c ! l) [] (int_to_hex i)
                );
        end;                            # stipulate



        # Unt to string conversions:
        #
        word_to_binary =  large_unt::format  sc::BINARY;
        word_to_octal  =  large_unt::format  sc::OCTAL;
        word_to_string =  large_unt::format  sc::DECIMAL;
        word_to_hex    =  large_unt::format  sc::HEX;
        #
        fun word_to_he_x i
            =
            string::map char::to_upper (word_to_hex i);


        # Accept a printf-style format string  like "This is %d %6.2f".
        # Return a matching list of printf_field::Printf_Field records -- see src/lib/src/printf-field.pkg
        #
        fun parse_format_string_into_printf_field_list
                format_string
            =
            loop (ss::from_string  format_string,   [])
            where

                # Define a predicate true on every char but '%',
                # for splitting up the format string:
                #
                split =  ss::split_off_prefix  {. #c != '%'; };

                fun loop (input_string, resultlist)
                    =
                    if   (ss::is_empty input_string)
                         reverse resultlist;
                    else
                         my  ( leading_literal,                 # Everything up to the first '%' in 'input_string'
                               rest_of_string                   # Everything from  the first '%' in 'input_string' on.
                             )
                             =
                             split input_string;

                                                                # scan_field    we get from printf_field.
                                                                # printf_field          is from   src/lib/src/printf-field.pkg

                         case (ss::getc  rest_of_string)

                              THE ('%', rest_of_string')
                                  =>
                                  {   my (field', left_to_do)
                                          =
                                          scan_field  rest_of_string';

                                      loop (left_to_do,   field' ! (RAW leading_literal) ! resultlist);
                                  };

                              _   => reverse ((RAW leading_literal) ! resultlist);
                         esac;
                    fi;
            end;                                                # fun parse_format_string_into_printf_field_list


        fun sprintf'
                format_string                                   # Printf-style format string like   "This is %d %2.3g"
            =
            \\ args
                =
                do_args (fields, args, [])
                where

                    fields                                      # List of printf_field::Printf_Field records -- see src/lib/src/printf-field.pkg
                        =
                        parse_format_string_into_printf_field_list
                            format_string;



                    # Apply one Printf_Field FIELD to a value.
                    #
                    # The first three args are the FIELD fields,
                    #     digested from some spec like "%6.2f".
                    #
                    # The fourth argument is the value being formatted,
                    # represented as a Printf_Arg -- something like FLOAT f.
                    #
                    fun do_field (flags, width, printf_field_type, arg)
                        =
                        {   fun pad_fn  string
                                =
                                case (flags.left_justify, width)
                                    #
                                    (_,     NO_PAD)   =>  string;
                                    (FALSE, WIDTH i)  =>  pad_left  (string, i);
                                    (TRUE,  WIDTH i)  =>  pad_right (string, i);
                                esac;

                            fun zero_pad_fn (sign, s)
                                =
                                case width
                                    NO_PAD   =>  raise exception BAD_FORMAT "NO_PAD not allowed here";
                                    WIDTH i  =>  zero_lpad (s, i - (string::length_in_bytes sign));
                                esac;

                            fun negate i
                                =
                                (POS_INT(-i))
                                except
                                    _ =  MAX_INT;

                            fun do_sign i
                                =
                                case (i < 0, flags.sign, flags.neg_char)
                                    #
                                    (FALSE, ALWAYS_SIGN, _) =>  ("+", POS_INT i);
                                    (FALSE, BLANK_SIGN,  _) =>  (" ", POS_INT i);
                                    (FALSE, _,           _) =>  ("",  POS_INT i);
                                    (TRUE,  _,  TILDE_SIGN) =>  ("~", negate i);
                                    (TRUE,  _,  _         ) =>  ("-", negate i);
                                esac;

                            fun do_real_sign sign
                                =
                                case (sign, flags.sign, flags.neg_char)
                                    #
                                    (FALSE, ALWAYS_SIGN, _) =>  "+";
                                    (FALSE, BLANK_SIGN,  _) =>  " ";
                                    (FALSE, _,           _) =>  "";
                                    (TRUE, _,   TILDE_SIGN) =>  "~";
                                    (TRUE, _,            _) =>  "-";
                                esac;

                            fun do_exp_sign (exp, is_cap)
                                =
                                {   e =  if is_cap      "E";
                                         else           "e";
                                         fi;

                                    fun mk_expression e
                                        =
                                        zero_lpad (int::to_string e, 2);

                                    case (exp < 0, flags.neg_char)
                                        #
                                        (FALSE, _        ) =>  [e,      mk_expression  exp ];
                                        (TRUE, TILDE_SIGN) =>  [e, "~", mk_expression(-exp)];
                                        (TRUE, _         ) =>  [e, "-", mk_expression(-exp)];
                                    esac;
                                };

                            fun binary i
                                =
                                {   (do_sign i) ->  (sign, i);
                                    #
                                    sign =  if flags.base      sign + "0";
                                            else               sign;
                                            fi;

                                    s =  int_to_binary i;

                                    if flags.zero_pad        sign + zero_pad_fn (sign, s);
                                    else                                 pad_fn (sign + s);
                                    fi;
                                };

                            fun octal i
                                =
                                {   (do_sign i) ->  (sign, i);
                                    #
                                    sign =  if flags.base      sign + "0";
                                            else               sign;
                                            fi;

                                    s =  int_to_octal i;

                                    if flags.zero_pad        sign + zero_pad_fn (sign, s);
                                    else                                 pad_fn (sign + s);
                                    fi;
                                };

                            fun decimal i
                                =
                                {   (do_sign i) ->  (sign, i);
                                    #
                                    s = int_to_string i;

                                    if  flags.zero_pad  sign + zero_pad_fn (sign, s);
                                    else                            pad_fn (sign + s);
                                    fi;
                                };

                            fun hexidecimal i
                                =
                                {   (do_sign i) ->  (sign, i);
                                    #
                                    sign =  if   flags.base      sign + "0x";
                                            else                 sign;
                                            fi;

                                    s =  int_to_hex i; 

                                    if flags.zero_pad                    sign + zero_pad_fn (sign, s);
                                    else                                             pad_fn (sign + s);
                                    fi;
                                };

                            fun cap_hexidecimal i
                                =
                                {   (do_sign i) ->  (sign, i);
                                    #
                                    sign = if flags.base  sign + "0X"; else sign;fi;
                                    s = int_to_he_x i; 

                                    if flags.zero_pad                    sign + zero_pad_fn (sign, s);
                                    else                                             pad_fn (sign + s);
                                    fi;
                                };


                            # Unt formatting:
                            #
                            fun do_unt_sign ()
                                =
                                case flags.sign
                                    #
                                    ALWAYS_SIGN =>   "+";
                                    BLANK_SIGN  =>   " ";
                                    _           =>   "";
                                esac;


                            fun binary_w i
                                =
                                {   sign =  do_unt_sign ();
                                    #
                                    sign =  if flags.base      sign + "0";
                                            else               sign;
                                            fi;

                                    s =  word_to_binary i;

                                    if flags.zero_pad                   sign + zero_pad_fn (sign, s);
                                    else                                            pad_fn (sign + s);
                                    fi;
                                };


                            fun octal_w i
                                =
                                {   sign =  do_unt_sign ();
                                    #
                                    sign =  if flags.base       sign + "0";
                                            else                sign;
                                            fi;

                                    s =  word_to_octal i;

                                    if flags.zero_pad                   sign + zero_pad_fn (sign, s);
                                    else                                            pad_fn (sign + s);
                                    fi;
                                };

                            fun decimal_w i
                                =
                                {   sign =  do_unt_sign ();
                                    #
                                    s =  word_to_string i;

                                    if flags.zero_pad            sign + zero_pad_fn (sign, s);
                                    else                                     pad_fn (sign + s);
                                    fi;
                                };

                            fun hexidecimal_w i
                                =
                                {   sign =  do_unt_sign ();
                                    #
                                    sign = if flags.base      sign + "0x";
                                           else               sign;
                                           fi;

                                    s =  word_to_hex i; 

                                    if flags.zero_pad                    sign + zero_pad_fn (sign, s);
                                    else                                             pad_fn (sign + s);
                                    fi;
                                };

                            fun cap_hexidecimal_w i
                                =
                                {   sign =  do_unt_sign ();
                                    #
                                    sign = if flags.base      sign + "0X";
                                           else               sign;
                                           fi;

                                    s =  word_to_he_x i; 

                                    if flags.zero_pad    sign + zero_pad_fn (sign, s);
                                    else                             pad_fn (sign + s);
                                    fi;
                                };

                            case (printf_field_type, arg)
                                #
                                # NB: If you change this caselist,
                                # be sure to also update
                                #     fun printf_field_type_to_printf_arg_list.

                                (BINARY_FIELD, LINT  i) =>   binary i;
                                (BINARY_FIELD, INT   i) =>   binary (int::to_multiword_int i);
                                (BINARY_FIELD, UNT   w) =>   binary_w (unt::to_large_unt w);
                                (BINARY_FIELD, LUNT  w) =>   binary_w w;
                                (BINARY_FIELD, UNT8  w) =>   binary_w (one_byte_unt::to_large_unt w);

                                (OCTAL_FIELD, LINT  i) =>   octal i;
                                (OCTAL_FIELD, INT   i) =>   octal (int::to_multiword_int i);
                                (OCTAL_FIELD, UNT   w) =>   octal_w (unt::to_large_unt w);
                                (OCTAL_FIELD, LUNT  w) =>   octal_w w;
                                (OCTAL_FIELD, UNT8  w) =>   octal_w (one_byte_unt::to_large_unt w);

                                (INT_FIELD, LINT  i) =>   decimal i;
                                (INT_FIELD, INT   i) =>   decimal (int::to_multiword_int i);
                                (INT_FIELD, UNT   w) =>   decimal_w (unt::to_large_unt w);
                                (INT_FIELD, LUNT  w) =>   decimal_w w;
                                (INT_FIELD, UNT8  w) =>   decimal_w (one_byte_unt::to_large_unt w);

                                (HEX_FIELD, LINT  i) =>  hexidecimal i;
                                (HEX_FIELD, INT   i) =>  hexidecimal (int::to_multiword_int i);
                                (HEX_FIELD, UNT   w) =>  hexidecimal_w (unt::to_large_unt w);
                                (HEX_FIELD, LUNT  w) =>  hexidecimal_w w;
                                (HEX_FIELD, UNT8  w) =>  hexidecimal_w (one_byte_unt::to_large_unt w);

                                (CAP_HEX_FIELD, LINT  i) =>  cap_hexidecimal i;
                                (CAP_HEX_FIELD, INT   i) =>  cap_hexidecimal (int::to_multiword_int i);
                                (CAP_HEX_FIELD, UNT   w) =>  cap_hexidecimal_w (unt::to_large_unt w);
                                (CAP_HEX_FIELD, LUNT  w) =>  cap_hexidecimal_w w;
                                (CAP_HEX_FIELD, UNT8  w) =>  cap_hexidecimal_w (one_byte_unt::to_large_unt w);

                                (CHAR_FIELD, CHAR c) =>  pad_fn (string::from_char c);

                                (BOOL_FIELD, BOOL FALSE) =>  pad_fn "FALSE";
                                (BOOL_FIELD, BOOL TRUE ) =>  pad_fn "TRUE";

                                (STRING_FIELD, QUICKSTRING s) =>  pad_fn (quickstring__premicrothread::to_string s);
                                (STRING_FIELD, STRING      s) =>  pad_fn s;

                                (FLOAT_FIELD { prec, format }, FLOAT r)
                                    =>
                                    if (f8b::is_finite  r)
                                         #
                                                                 # float_format is from   src/lib/src/float-format.pkg
                                         case format
                                             #
                                             F_FORMAT
                                                 =>
                                                 {   my { sign, mantissa }
                                                         =
                                                         float_format::float_fformat (r, prec);

                                                     sign =  do_real_sign sign;

                                                     if   (prec == 0   and  flags.base)
                                                          pad_fn (cat [sign, mantissa, "."]);
                                                     else pad_fn (cat [sign, mantissa     ]);
                                                     fi;
                                                 };

                                             E_FORMAT is_cap
                                                 =>
                                                 {   my { sign, mantissa, exp }
                                                         =
                                                         float_format::float_eformat (r, prec);

                                                     sign =  do_real_sign sign;

                                                     exp_string =  do_exp_sign (exp, is_cap);

                                                     if   (prec == 0   and   flags.base)
                                                          pad_fn (cat (sign ! mantissa ! "." ! exp_string));
                                                     else pad_fn (cat (sign ! mantissa !        exp_string));
                                                     fi;
                                                 };


                                             G_FORMAT is_cap
                                                 =>
                                                 {   prec =  if   (prec == 0   )   1;
                                                                              else   prec;   fi;

                                                     (float_format::float_gformat (r, prec))
                                                         ->
                                                         { sign, whole, frac, exp };


                                                     sign =  do_real_sign sign;

                                                     exp_string
                                                         =
                                                         case exp

                                                              THE e =>  do_exp_sign (e, is_cap);
                                                              NULL  =>  [];
                                                         esac;

                                                     num =
                                                         if   flags.base

                                                              diff =  prec - ((size whole) + (size frac));

                                                              if   (diff > 0)
                                                                   zero_rpad (frac, (size frac) + diff);
                                                              else
                                                                   frac;
                                                              fi;
                                                         else
                                                              if   (frac == "")
                                                                   "";
                                                              else
                                                                   ("." + frac);
                                                              fi;
                                                         fi;

                                                     pad_fn (cat (sign ! whole ! num ! exp_string));
                                                 };
                                         esac;

                                    else
                                        if (f8b::(====) (f8b::neg_inf, r))
                                            #
                                            do_real_sign TRUE + "inf";
                                        else
                                            if (f8b::(====) (f8b::pos_inf, r))
                                                #       
                                                do_real_sign FALSE + "inf";
                                            else
                                                "nan";
                                            fi;
                                        fi;
                                    fi;

                                (_, LEFT (w, arg))
                                    =>
                                    {   flags = { sign         =>  flags.sign,
                                                  neg_char     =>  flags.neg_char,
                                                  zero_pad     =>  flags.zero_pad,
                                                  base         =>  flags.base,
                                                  left_justify =>  TRUE,
                                                  large        =>  FALSE
                                                };

                                        do_field (flags, WIDTH w, printf_field_type, arg);
                                    };

                                (_, RIGHT (w, arg))
                                    =>
                                    do_field (flags, WIDTH w, printf_field_type, arg);

                                _ => raise exception BAD_FORMAT_LIST;
                            esac;
                        };                                                                                      # fun do_field

                    # First arg is list of vals to print, say   [ INT 12, STRING "funky", FLOAT 1.3 ]
                    # Second arg is the "%3.sf" style format string digested into
                    #            a list of printf_field::Printf_Field records -- see src/lib/src/printf-field.pkg
                    # Third arg is our result accumulator:
                    #
                    fun do_args ([], [], resultlist)
                            =>
                            ss::cat (reverse resultlist);

                        do_args ((RAW s) ! remaining_fields, args, resultlist)
                            =>
                            do_args (remaining_fields, args, s ! resultlist);

                        do_args
                            ( FIELD (flags, width, printf_field_type)  ! remaining_fields,
                              arg                                      ! remaining_args,
                              resultlist
                            )
                            =>
                            do_args
                                ( remaining_fields,
                                  remaining_args,
                                  ss::from_string (do_field (flags, width, printf_field_type, arg)) ! resultlist
                                );

                        do_args _
                            =>
                            raise exception BAD_FORMAT_LIST;
                    end;
                end;                                            # fun sprintf'


        fun fnprintf' consumer
            =
            \\ format
                =
                \\ args
                    =
                    consumer (sprintf' format args);


        fun fprintf' stream
            =
            \\ format
                =
                \\ args
                    =
                    {   fil::write  (stream,  sprintf' format args);
                        fil::flush stream;                              # Default to occasional inefficiency rather than occasional mysterious lockups.
                    };


        printf' =  fprintf'  fil::stdout;




                                            # printf_field              is from   src/lib/src/printf-field.pkg

        # In conjunction with
        #     fun parse_format_string_into_printf_field_list,
        # this function can be used to mechanically
        # synthesize an appropriate arglist from a
        # sfprintf format string like "%d %6.2f\n":
        #
        fun printf_field_type_to_printf_arg_list  f
            =
            {   u0  = unt::from_int 0;
                li0 = large_int::from_int 0;
                lu0 = large_unt::from_int 0;
                b0  = one_byte_unt::from_int 0;

                case f
                    #   
                    # Here we give, for each printf_field::Printf_Field_Type,
                    # the list of printf_field::Printf_Arg constructors.
                    #
                    # The values are dummies, we're
                    # only interested in the constructors.
                    #
                    # Order is significant in that caller
                    # expects the most vanilla altenative
                    # will be first in the returned list:
                    #   
                    BINARY_FIELD  =>  [INT 0, UNT u0,  LINT li0,  LUNT lu0,  UNT8 b0];
                    OCTAL_FIELD   =>  [INT 0, UNT u0,  LINT li0,  LUNT lu0,  UNT8 b0];
                    INT_FIELD     =>  [INT 0, UNT u0,  LINT li0,  LUNT lu0,  UNT8 b0];
                    HEX_FIELD     =>  [INT 0, UNT u0,  LINT li0,  LUNT lu0,  UNT8 b0];
                    CAP_HEX_FIELD =>  [INT 0, UNT u0,  LINT li0,  LUNT lu0,  UNT8 b0];
                    CHAR_FIELD    =>  [CHAR 'a'];
                    BOOL_FIELD    =>  [BOOL FALSE];
                    STRING_FIELD  =>  [STRING ""];
                    FLOAT_FIELD _ =>  [FLOAT 0.0];
                esac;
            };

    };                                                          # pkg sfprintf
end;


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext