Module: common-dylan-internals Author: Andy Armstrong Synopsis: Implementations of format-to-string and format-out Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// String buffers //---*** Oh for a stretchy string... define constant = limited(, of: ); //---*** Is there a more efficient way to do this? define function print-string (buffer :: , string :: ) => () for (character :: in string) add!(buffer, character) end end function print-string; define function buffer-contents (buffer :: ) => (contents :: ) as(, buffer) end function buffer-contents; /// User assertions (maybe export this?) define macro user-assert { user-assert(?value:expression, ?format-string:expression, ?format-arguments:*) } => { unless (?value) user-assertion-error(?format-string, ?format-arguments) end } end macro user-assert; define class () end class ; define function user-assertion-error (format-string :: , #rest format-arguments) error(make(, format-string: format-string, format-arguments: format-arguments)) end function user-assertion-error; /// format-to-string define function print-format (buffer :: , format-string :: , #rest format-arguments) => () let found-percent? = #f; let argument-index :: = 0; let no-of-arguments = size(format-arguments); local method argument (char :: , class :: ) => (argument) let current-index = argument-index; argument-index := argument-index + 1; user-assert(current-index < no-of-arguments, "Too few arguments for format string %=: %=", format-string, format-arguments); let argument = format-arguments[current-index]; user-assert(class == | instance?(argument, class), "Format argument for directive '%%%c' not of class %s: %=", char, object-name(class) | class, argument); argument end; local method collect (string :: ) => () print-string(buffer, string) end method collect; local method collect-character (character :: ) => () add!(buffer, character) end method collect-character; for (char :: in format-string) if (found-percent?) select (as-uppercase(char)) 'D' => collect(number-to-string(argument(char, ))); 'B' => collect(integer-to-string(argument(char, ), base: 2)); 'O' => collect(integer-to-string(argument(char, ), base: 8)); 'X' => collect(integer-to-string(argument(char, ), base: 16)); 'C' => collect-character(argument(char, )); 'S' => print-pretty-name(buffer, argument(char, )); '=' => print-unique-name(buffer, argument(char, )); '%' => collect-character('%'); otherwise => error("Invalid format directive '%s' in \"%s\"", char, format-string); end; found-percent? := #f; else if (char == '%') found-percent? := #t; else collect-character(char) end end end; user-assert(~found-percent?, "Incomplete format directive in \"%s\"", format-string); end function print-format; define function format-to-string (format-string :: , #rest format-arguments) => (string :: ) let buffer :: = make(); apply(print-format, buffer, format-string, format-arguments); buffer-contents(buffer) end function format-to-string; /// Basic object printing define function object-name (object :: ) => (name :: ) let maybe-name = debug-name(object); if (maybe-name) as(, maybe-name); else "???" end; end function object-name; define method print-pretty-name (buffer :: , object :: ) => () let name = primitive-name(object); case name => print-string(buffer, name); otherwise => print-unique-name(buffer, object); end end method print-pretty-name; define function object-class-name (object :: ) => (name :: false-or()) select (object by instance?) => ""; otherwise => object-name(object-class(object)) end end function object-class-name; define function print-basic-name (buffer :: , #key object :: , name :: false-or() = primitive-name(object), class-name :: = object-class-name(object)) => () if (name) print-format(buffer, "{%s: %s}", class-name, name) else print-format(buffer, "{%s}", class-name) end end function print-basic-name; define method print-unique-name (buffer :: , object :: ) => () local method symbol-name (symbol :: ) => (name :: ) as-lowercase(as(, symbol)) end method symbol-name; select (object by instance?) => print-format(buffer, "\"%s\"", object); => print-format(buffer, "#\"%s\"", symbol-name(object)); => print-format(buffer, "'%c'", object); => print-collection(buffer, object); => print-string(buffer, if (object) "#t" else "#f" end); => print-string(buffer, integer-to-string(object)); => print-string(buffer, float-to-string(object)); => print-string(buffer, machine-word-to-string(object)); => print-method(buffer, object); otherwise => print-basic-name(buffer, object: object); end end method print-unique-name; define function object-unique-name (object :: ) => (name :: ) let buffer :: = make(); print-unique-name(buffer, object); buffer-contents(buffer) end function object-unique-name; define function primitive-name (object :: ) => (name :: false-or()) select (object by instance?) => object; => make(, size: 1, fill: object); => condition-to-string(object); => as(, object); => object-name(object); => object-name(object); otherwise => #f; end end function primitive-name; /// Types define method print-unique-name (buffer :: , union :: ) => () local method print-union-type (buffer :: , type :: ) => (object :: ) select (type by instance?) => print-unique-name(buffer, singleton-object(type)); => print-pretty-name(buffer, type); otherwise => print-unique-name(buffer, type); end end method print-union-type; print-format(buffer, "{%s: ", object-class-name(union)); print-union-type(buffer, union-type1(union)); print-string(buffer, ", "); print-union-type(buffer, union-type2(union)); print-string(buffer, "}") end method print-unique-name; define method print-unique-name (buffer :: , singleton :: ) => () print-format(buffer, "{%s: ", object-class-name(singleton)); print-unique-name(buffer, singleton-object(singleton)); print-string(buffer, "}") end method print-unique-name; /// Number/string conversion define constant $number-characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; define inline function integer-to-character (integer :: ) => (character :: ) $number-characters[integer] end function integer-to-character; define inline function character-to-integer (character :: ) => (integer :: false-or()) //---*** We might want to make this more efficient... position($number-characters, character) end function character-to-integer; define function integer-to-string (integer :: , #key base :: = 10, size: string-size :: false-or(), fill :: = '0') => (string :: ) user-assert(2 <= base & base <= 36, "Base %d is not between 2 and 36", base); let negative-integer? = negative?(integer); let number-size = if (negative-integer?) // We have to be careful not to overflow here, since // -$minimum-integer > $maximum-integer. let quotient = truncate/(integer, base); if (zero?(quotient)) 1 else floor(logn(as(, -quotient), base)) + 2 end else floor(logn(as(, max(integer, 1)), base)) + 1 end; let sign-size = if (negative-integer?) 1 else 0 end; let buffer-size = max(string-size | number-size, number-size) + sign-size; let buffer :: = make(, size: buffer-size, fill: fill); if (negative-integer?) buffer[0] := '-'; end; let last-index :: = buffer-size - 1; buffer[last-index] := '0'; if (negative-integer?) // Do the first digit by hand to avoid overflows when printing // $minimum-integer, since -$minimum-integer > $maximum-integer. let (quotient, remainder :: ) = truncate/(integer, base); buffer[last-index] := integer-to-character(-remainder); last-index := last-index - 1; integer := -quotient end; until (zero?(integer)) let (quotient, remainder :: ) = truncate/(integer, base); buffer[last-index] := integer-to-character(remainder); last-index := last-index - 1; integer := quotient end; buffer end function integer-to-string; // Given a string, parse an integer from it. Skips left whitespace. define function string-to-integer (string :: , #key base :: = 10, start :: = 0, end: stop :: false-or(), default = $unsupplied) => (n :: , next-key :: ) let actual-start :: = start; let string-length :: = size(string); user-assert(start == 0 | (start > 0 & start < string-length), "Start: %d is out of range [0, %d] for string %=", start, string-length, string); if (stop) user-assert(stop >= start & stop <= string-length, "Stop: %d is out of range [0, %d] for string %=", stop, string-length, string); else stop := string-length end; user-assert(2 <= base & base <= 36, "Base %d is not between 2 and 36", base); while (start < stop & member?(string[start], #(' ', '\n', '\r', '\f', '\t'))) start := start + 1 end; let sign :: = select (start < stop & string[start]) '+' => start := start + 1; 1; '-' => start := start + 1; -1; otherwise => 1; end; let value :: = 0; // Shifting digits into here let valid? :: = #f; let next-key :: = block (return) for (i :: from start below stop) let ch :: = as-uppercase(string[i]); let digit = character-to-integer(ch); when (~digit | digit >= base) return(i) end; valid? := #t; value := value * base + digit * sign end; stop end; if (valid?) values(value, next-key) else when (unsupplied?(default)) error("Reached end of %s without finding an integer", string) end; values(default, actual-start) end end function string-to-integer; //---*** NOTE: The following function is a kludge that's needed until //---*** the necessary primitives are added and the Dylan library provides //---*** an interface. Unfortunately, this kludge relies on a code generation //---*** bug which causes all NaNs to compare equal to any other value. //---*** When that bug is fixed, we'll need a new way to check for NaNs. define inline-only function nan? (float :: ) => (nan? :: ) float = 0.0 & float = 1.0 end function nan?; //---*** NOTE: The following function is a kludge that's needed until //---*** the necessary primitives are added and the Dylan library provides //---*** an interface. define inline-only function infinity? (float :: ) => (infinity? :: ) ~zero?(float) & (float / 2.0) = float end function infinity?; define function float-to-string (float :: , #key decimal-points :: false-or() = #f) => (string :: ) if (decimal-points & zero?(decimal-points)) integer-to-string(round(float)) else let class = object-class(float); //---*** Until this code can be rewritten to not integer overflow, //---*** keep the number of digits down to 7 for all types of float. let (max-digits, marker, force-marker?) = select (float by instance?) => values(7, 's', #f); => values(7, 'd', #t); // values(15, 'd', #t); => values(7, 'x', #t); // values(34, 'x', #t); end; let digits = min(decimal-points | max-digits, max-digits); case nan?(float) => // The sign of a NaN is meaningless ... if (class == ) "{NaN}" else format-to-string("{NaN}%c0", marker) end; infinity?(float) => let sign :: = if (negative?(float)) '-' else '+' end; if (class == ) format-to-string("%c{infinity}", sign) else format-to-string("%c{infinity}%c0", sign, marker) end; zero?(float) => if (class == ) "0.0" else format-to-string("0.0%c0", marker) end; negative?(float) => let positive-string = float-to-string(negative(float)); format-to-string("-%s", positive-string); otherwise => let buffer :: = make(); let ten = as(class, 10.0); let dec-point :: = digits - 1; local method sub-print (mantissa :: , count :: ) let (quotient :: , remainder :: ) = truncate/(mantissa, 10); unless (count = digits) // Recurse until you have all the digits pushed on stack sub-print(quotient, count + 1) end unless; // Then as each recursive call unwinds, turn the digit (in // remainder) into a character and output the character. if (count = dec-point) add!(buffer, '.') end if; add!(buffer, as(, as(, '0') + remainder)) end method; let scale :: = truncate/(log(float), log(ten)); if (scale > 0 & scale <= dec-point) dec-point := dec-point - scale; end; let sub-float :: = float; // let tens :: = 1; let tens :: = ten / ten; for (i :: from 1 to abs(digits - scale)) if (tens < 100000000) tens := ten * tens; else // make sure tens dont go bignum: sub-float := if (digits < scale) sub-float / tens else sub-float * tens end; tens := ten; end if; end for; if (digits < scale) sub-print(round/(sub-float, tens), 0); else sub-print(round(sub-float * tens), 0); end; if (force-marker? | (scale ~= 0 & dec-point = digits - 1)) add!(buffer, marker); if (dec-point = digits - 1) for (character in integer-to-string(scale)) add!(buffer, character) end else // If the scale was small enough, we moved the decimal point // instead so don't print a bogus exponent ... add!(buffer, '0') end end; buffer-contents(buffer); end end end function float-to-string; define open generic number-to-string (number :: ) => (string :: ); define method number-to-string (number :: ) => (string :: ) object-unique-name(number) end method number-to-string; define method number-to-string (float :: ) => (string :: ) float-to-string(float) end method number-to-string; define method number-to-string (integer :: ) => (string :: ) integer-to-string(integer, base: 10) end method number-to-string; /// Machine-word/string conversion define function machine-word-to-string (mw :: , #key prefix :: false-or() = "#x") => (string :: ) let halfword-size = ash($machine-word-size, -1); let digits-per-halfword = ash(halfword-size, -2); let high = coerce-machine-word-to-integer (machine-word-unsigned-shift-right(mw, halfword-size)); let low = coerce-machine-word-to-integer (machine-word-unsigned-shift-right (machine-word-unsigned-shift-left(mw, halfword-size), halfword-size)); concatenate-as(, prefix | "", integer-to-string(high, base: 16, size: digits-per-halfword), integer-to-string(low, base: 16, size: digits-per-halfword)) end function machine-word-to-string; define function string-to-machine-word (str :: , #key start :: = 0, default = $unsupplied, end: stop :: false-or()) => (n :: , next-key :: ) let string-length :: = size(str); user-assert(start >= 0 & start < string-length, "Start: %d is out of range [0, %d] for string %s", start, string-length, str); if (stop) user-assert(stop >= start & stop <= string-length, "Stop: %d is out of range [0, %d] for string %s.", stop, string-length, str); else stop := size(str) end; while (start < stop & member?(str[start], #(' ', '\n', '\r', '\f', '\t'))) start := start + 1 end; // Remove common prefixes (#x, 0x) ... if ((start < stop - 2) &((str[start] = '#' & str[start + 1] = 'x') | (str[start] = '0' & str[start + 1] = 'x'))) start := start + 2 end; let value :: = as(, 0); let next-key = block (return) for (i from start below stop) let ch :: = as-uppercase(str[i]); let digit = character-to-integer(ch); when (~digit | digit >= 16) return(i > start & i) end; //--- andrewa: trick the typist into knowing that this is //--- an integer, so inlining works. Hopefully the new //--- typist will be able to work this out for itself. let integer-digit :: = digit; value := machine-word-logior (machine-word-unsigned-shift-left(value, 4), coerce-integer-to-machine-word(integer-digit)); end; stop end; unless (next-key) when (unsupplied?(default)) error("Reached end of %s without finding an integer", str) end; value := default; next-key := start end; values(value, next-key) end function string-to-machine-word; /// Condition/string conversion define open generic condition-to-string (condition :: ) => (string :: false-or()); define method condition-to-string (condition :: ) => (string :: false-or()) #f end method condition-to-string; define method condition-to-string (condition :: ) => (string :: ) apply(format-to-string, condition-format-string(condition), condition-format-arguments(condition)) end method condition-to-string; define method condition-to-string (error :: ) => (string :: ) format-to-string("%= is not of type %=", type-error-value(error), type-error-expected-type(error)) end method condition-to-string; define method print-pretty-name (buffer :: , condition :: ) => () let message = condition-to-string(condition); if (message) print-string(buffer, message) else print-format(buffer, "Condition of class %s occurred", object-class-name(condition)) end end method print-pretty-name; /// Collection printing define constant $collection-empty-text = "size 0"; define variable *collection-print-length* = 10; define method print-collection (buffer :: , collection :: , #key print-length :: false-or() = *collection-print-length*) => () print-string(buffer, "{"); print-string(buffer, object-class-name(collection)); print-string(buffer, ": "); print-collection-contents(buffer, collection, print-length: print-length); print-string(buffer, "}"); end method print-collection; define method print-collection-contents (buffer :: , collection :: , #key print-length :: false-or() = *collection-print-length*) => () ignore(print-length); print-format(buffer, "size %d", size(collection)) end method print-collection-contents; define method print-collection-contents (buffer :: , array :: , #key print-length :: false-or() = *collection-print-length*) => () let dimensions = dimensions(array); print-elements(buffer, dimensions, print-length: print-length, separator: " x ") end method print-collection-contents; define method print-collection-contents (buffer :: , range :: , #key print-length :: false-or() = *collection-print-length*) => () ignore(print-length); local method print-range (buffer :: , from :: , to :: , by :: ) => () select (by) 1 => print-format(buffer, "%d to %d", from, to); otherwise => print-format(buffer, "%d to %d by %d", from, to, by); end end method print-range; let range-size = size(range); if (range-size = 0) print-string(buffer, $collection-empty-text) else let from = range[0]; let by = if (~range-size | range-size > 1) range[1] - from else 1 end; select (range-size) 1 => print-range(buffer, from, from, by); #f => print-format(buffer, "%d by %d", from, by); otherwise => print-range(buffer, from, range[range-size - 1], by); end end end method print-collection-contents; define method print-collection-contents (buffer :: , sequence :: , #key print-length :: false-or() = *collection-print-length*) => () if (empty?(sequence)) print-string(buffer, $collection-empty-text) else print-elements(buffer, sequence, print-length: print-length) end end method print-collection-contents; //---*** Needed to override the array method... can we avoid this? define method print-collection-contents (buffer :: , sequence :: , #key print-length :: false-or() = *collection-print-length*) => () if (empty?(sequence)) print-string(buffer, $collection-empty-text) else print-elements(buffer, sequence, print-length: print-length) end end method print-collection-contents; define method print-collection-contents (buffer :: , pair :: , #key print-length :: false-or() = *collection-print-length*) => () let tail-object = tail(pair); if (instance?(tail-object, )) next-method() else print-format(buffer, "%=, %=", head(pair), tail-object) end end method print-collection-contents; define function print-elements (buffer :: , sequence :: , #key print-length = *collection-print-length*, separator = ", ", print-function) => () let current-separator = ""; let sequence-size = size(sequence); let print-length = print-length | sequence-size; for (element in sequence, count from 0 below print-length) print-string(buffer, current-separator); current-separator := separator; case print-function => print-function(buffer, element); instance?(element, ) & ~instance?(element, ) => print-basic-name(buffer, object: element, name: #f); otherwise => print-unique-name(buffer, element); end end; if (sequence-size > print-length) print-string(buffer, separator); print-string(buffer, "...") end end function print-elements; /// Method printing define function print-method (buffer :: , object :: ) => () print-string(buffer, "{"); print-string(buffer, object-class-name(object)); print-string(buffer, ": "); print-string(buffer, primitive-name(object)); let specializers = function-specializers(object); print-string(buffer, " ("); unless (empty?(specializers)) print-elements(buffer, specializers, print-function: print-specializer) end; print-string(buffer, ")}"); end function print-method; define method print-specializer (buffer :: , type :: ) => () print-unique-name(buffer, type) end method print-specializer; define method print-specializer (buffer :: , object :: ) => () print-pretty-name(buffer, object) end method print-specializer; define method print-specializer (buffer :: , type :: ) => () print-string(buffer, "singleton("); print-unique-name(buffer, singleton-object(type)); print-string(buffer, ")") end method print-specializer; define method print-specializer (buffer :: , type :: ) => () print-string(buffer, "subclass("); print-pretty-name(buffer, subclass-class(type)); print-string(buffer, ")") end method print-specializer;