module: format author: Gwydion Project synopsis: This file implements a simple mechanism for formatting output. copyright: See below. rcs-header: $Header: /scm/cvs/src/common/format/format.dylan,v 1.7 2003/06/02 07:16:55 housel Exp $ ///====================================================================== /// /// 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, and the Gwydion Dylan Maintainers. /// /// 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 should be sent to ; questions, /// comments and suggestions are welcome at . /// Also, see http://www.gwydiondylan.org/ for updates and documentation. /// ///====================================================================== /// /// ### Instances of 'as( /***/' should be changed to 'as(' /// when d2c can deal. /// /// This code was modified at Harlequin, Inc. to work with the new Streams /// Library designed by Harlequin 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: 200), 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 method print-message (object :: , stream :: ) => (); let name = object.object-class.class-name; if (name) format(stream, "{an instance of %s}", name); else print-message("{an instance of something}", stream); end if; end method print-message; define method print-message (class :: , stream :: , #next next-method) => (); let name = class.class-name; if (name) write(stream, name); else next-method(); end if; end method print-message; define sealed method print-message (object :: , stream :: ) => (); // Buffer-do-dispatch hand codes the sealed semantics of this method and // the write method on and to maximize buffer // handling efficiency. Any changes in these sealed semantics should be // reflected in buffer-do-dispatch. write(stream, object); end method; define sealed method print-message (object :: , stream :: ) => (); // Buffer-do-dispatch hand codes the sealed semantics of this method and // the write method on and to maximize buffer // handling efficiency. Any changes in these sealed semantics should be // reflected in buffer-do-dispatch. write-element(stream, object); end method; define sealed method print-message (object :: , stream :: ) => (); report-condition(object, stream); end method; define sealed method print-message (object :: , stream :: ) => (); write(stream, as(, object)); end method; #if(~mindy) define sealed method print-message (object :: , stream :: ) => (); format(stream, "%x", as(, )); end method print-message; #endif /// 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; block (exit) let start :: = 0; let arg-i :: = 0; // Ensure all output is contiguous at stream's destination. lock-stream(stream); 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 write(stream, control-string, start: start, end: i); 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 if; end while; cleanup unlock-stream(stream); end; 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 function do-dispatch (char :: , stream :: , arg) => consumed-arg? :: ; select (char by \==) ('s'), ('S'), ('c'), ('C') => print-message(arg, stream); #t; ('=') => print(arg, stream); #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 function; /// 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 function 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 function; define constant $digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; /// format-integer -- internal. /// 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); // remainder is prob. an , even though it's small. let digits = pair($digits[as(, 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, '-'); repeat(-arg, #()); else repeat(arg, #()); end; end method format-integer; /// format-integer -- internal. /// 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 format-integer; // borrowed from FD version // define method format-integer (arg :: , radix :: limited(, min: 2, max: 36), stream :: ) => (); //--- Should we really be this compulsive? unless (radix == 10) error("Can only print floats in base 10"); end; print(arg, stream) end method; // Condition-Format and Condition-Force-Output methods. // // Condition-Format and Condition-Force-Output are generic functions that are // called by the condition system to service its output needs. We supply // method on s that call the specific functions. // define method condition-format (stream :: , string :: , #rest args) => (); apply(format, stream, string, args); end method condition-format; // define method condition-force-output (stream :: ) => (); force-output(stream); end method condition-force-output;