Module: format-internals Author: Gwydion Project Synopsis: This file implements a simple mechanism for formatting output. Copyright: See below. ///====================================================================== /// /// Copyright (c) 1994 Carnegie Mellon University /// All rights reserved. /// /// Use and copying of this software and preparation of derivative /// works based on this software are permitted, including commercial /// use, provided that the following conditions are observed: /// /// 1. This copyright notice must be retained in full on any copies /// and on appropriate parts of any derivative works. /// 2. Documentation (paper or online) accompanying any system that /// incorporates this software, or any part of it, must acknowledge /// the contribution of the Gwydion Project at Carnegie Mellon /// University. /// /// This software is made available "as is". Neither the authors nor /// Carnegie Mellon University make any warranty about the software, /// its performance, or its conformity to any specification. /// /// Bug reports, questions, comments, and suggestions should be sent by /// E-mail to the Internet address "gwydion-bugs@cs.cmu.edu". /// ///====================================================================== /// /// This code was modified at Functional Objects, Inc. to work with the new Streams /// Library designed by Functional Objects and CMU. /// /// format-to-string. /// /// format-to-string -- Exported. /// define generic format-to-string (control-string :: , #rest args) => result :: ; define method format-to-string (control-string :: , #rest args) => result :: ; // Format-to-string is typically used for small amounts of output, so // use a smaller string to collect the contents. let s :: = make(, contents: make(, size: 32), direction: #"output"); apply(format, s, control-string, args); s.stream-contents end method; /// Print-message. /// /// print-message -- Exported. /// define open generic print-message (object :: , stream :: ) => (); define /* sealed */ method print-message (object :: , stream :: ) => (); dynamic-bind (*print-escape?* = #f) // print as a string print-object(object, stream) end end method; define /* sealed */ method print-message (object :: , stream :: ) => (); //---*** This method is broken right now. It should just call report-condition. //---*** If this interim bit of code is not good enough, then we'll have to do //---*** something better (assuming we can't just fix report-condition). //---*** report-condition(object, stream); dynamic-bind (*print-escape?* = #f) // print as a string print-object(object, stream) end end method; define sealed method print-message (object :: , stream :: ) => (); write-text(stream, object); end method; define sealed method print-message (object :: , stream :: ) => (); write-element(stream, object); end method; define sealed method print-message (object :: , stream :: ) => (); write(stream, as(, object)); end method; /// Format. /// define constant $dispatch-char = '%'; define constant char-classes :: = make(, size: 256, fill: #f); /// for (i from as(, '0') below (as(, '9') + 1)) char-classes[i] := #"digit"; end; char-classes[as(, '-')] := #"digit"; define generic format (stream :: , control-string :: , #rest args) => (); define method format (stream :: , control-string :: , #rest args) => (); let control-len :: = control-string.size; with-stream-locked (stream) block (exit) let start :: = 0; let arg-i :: = 0; while (start < control-len) // Skip to dispatch char. for (i :: = start then (i + 1), until: ((i == control-len) | (control-string[i] == $dispatch-char) | (control-string[i] == '\n'))) finally if (i ~== start) write(stream, control-string, start: start, end: i); end; if (i == control-len) exit(); else start := i + 1; end; end for; if (control-string[start - 1] == '\n') new-line(stream) else // Parse for field within which to pad output. let (field, field-spec-end) = if (char-classes[as(, control-string[start])] == #"digit") parse-integer(control-string, start); end; if (field) // Capture output in string and compute padding. // Assume the output is very small in length. let s :: = make(, contents: make(, size: 80), direction: #"output"); if (do-dispatch(control-string[field-spec-end], s, element(args, arg-i, default: #f))) arg-i := arg-i + 1; end; let output :: = s.stream-contents; let output-len :: = output.size; let padding :: = (abs(field) - output-len); case (padding < 0) => write(stream, output); (field > 0) => write(stream, make(, size: padding, fill: ' ')); write(stream, output); otherwise => write(stream, output); write(stream, make(, size: padding, fill: ' ')); end; start := field-spec-end + 1; // Add one to skip dispatch char. else if (do-dispatch(control-string[start], stream, element(args, arg-i, default: #f))) arg-i := arg-i + 1; end; start := start + 1; // Add one to skip dispatch char. end end end while; end block; end with-stream-locked; end method; /// do-dispatch -- Internal. /// /// This function dispatches on char, which should be a format directive. /// The return value indicates whether to consume one format argument; /// otherwise, consume none. /// define method do-dispatch (char :: , stream :: , arg) => consumed-arg? :: ; select (char by \==) ('s'), ('S'), ('c'), ('C') => print-message(arg, stream); #t; ('=') => dynamic-bind (*print-escape?* = #t) // print as an object print-object(arg, stream) end; #t; ('d'), ('D') => format-integer(arg, 10, stream); #t; ('b'), ('B') => format-integer(arg, 2, stream); #t; ('o'), ('O') => format-integer(arg, 8, stream); #t; ('x'), ('X') => format-integer(arg, 16, stream); #t; ('m'), ('M') => apply(arg, list(stream)); #t; ('%') => write-element(stream, '%'); #f; otherwise => error("Unknown format dispatch character, %c", char); end; end method; /// parse-integer -- Internal. /// /// This function reads an integer from input starting at index. Index must /// be at the first digit or a leading negative sign. This function reads /// decimal representation, and it stops at the first character that is not /// a decimal digit. It returns the integer parsed and the index /// immediately following the last decimal digit. /// define method parse-integer (input :: , index :: ) => (result :: false-or(), index :: ); let result :: = 0; let negative? = if (input[index] = '-') index := index + 1; end; for (i :: = index then (i + 1), len :: = input.size then len, ascii-zero :: = as(, '0') then ascii-zero, until: ((i = len) | (~ (char-classes[as(, input[i])] == #"digit")))) result := ((result * 10) + (as(, input[i]) - ascii-zero)); finally if (result = 0) values(#f, index); else values(if (negative?) (- result) else result end, i); end; end; end method; define constant $digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// format-integer -- internal. /// ///---*** KLUDGE: Temporary method until division is implemented for ///---*** Once division is available, remove this method! define method format-integer (arg :: , radix :: limited(, min: 2, max: 36), stream :: ) => () print(arg, stream) end method; define method format-integer (arg :: , radix :: limited(, min: 2, max: 36), stream :: ) => () // Define an iteration that collects the digits for the print // representation of arg. local method repeat (arg :: , digits :: ) let (quotient :: , remainder :: ) = floor/(arg, radix); let digits = pair($digits[remainder], digits); if (zero?(quotient)) for (digit in digits) write-element(stream, digit) end else repeat(quotient, digits) end end; // Set up for the iteration. if (negative?(arg)) write-element(stream, '-'); // Pick off one digit before beginning the iteration to ensure that we // don't need Generic-arithmetic. If arg were the mininum signed // machine word, and we simply negated it and called repeat, then it // would turn into an integer that was one larger than the maximum // signed integer. let (quotient :: , remainder :: ) = truncate/(arg, radix); if (~ zero?(quotient)) repeat(- quotient, list($digits[- remainder])) else write-element(stream, $digits[- remainder]) end else repeat(arg, #()) end end method; define method format-integer (arg :: , radix :: limited(, min: 2, max: 36), stream :: ) => () //--- Should we really be this compulsive? assert(radix = 10, "Can only print floats in base 10"); print(arg, stream) end method;