Module:    print-internals
Author:    Gwydion Project
Synopsis:  This file implements object printing.
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.
///


// Print-length holds the maximum number of elements the user wants a
// sequence to be printed.  This does not apply to some sequences, such as
// strings.
define variable *default-length* :: false-or(<integer>) = #f;
define thread variable *print-length* :: false-or(<integer>) = *default-length*;

// Print-level holds the maximum depth to which the user wants recursive
// printing to go.
define variable *default-level* :: false-or(<integer>) = #f;
define thread variable *print-level* :: false-or(<integer>) = *default-level*;

// Print-circle? holds whether the user wants pretty printing.
define variable *default-circle?* :: <boolean> = #f;
define thread variable *print-circle?* :: <boolean> = *default-circle?*;

// Print-pretty? holds whether the user wants pretty printing.
define variable *default-pretty?* :: <boolean> = #f;
define thread variable *print-pretty?* :: <boolean> = *default-pretty?*;

// Print-escape? holds whether the user wants to print as strings or objects
// #t => print as structure, #f => print as string
define variable *default-escape?* :: <boolean> = #t;
define thread variable *print-escape?* :: <boolean> = *default-escape?*;

// Print-depth holds the current level of printing.  When incremeting this
// slot causes the depth to exceed print-level, then the print function
// only outputs $print-level-exceeded-string.
define thread variable *print-depth* :: <integer> = -1; 

/// <circular-print-stream> Class -- Internal.
///
/// These streams hold print state so that the print function can do most
/// of the work maintaining circular printing state.
///
define sealed class <circular-print-stream> (<wrapper-stream>)
  // Circular-first-pass? indicates to the print function whether it is on
  // the first pass of printing, in which it just builds a table of objects
  // referenced during the printing.  On the second pass of printing, print
  // actually generates output.
  slot circular-first-pass? :: <boolean> = #t;
  //
  // Circular-references is a table of objects referenced during printing
  // when print-circle? is #t.
  slot circular-references :: false-or(<object-table>) = #f;
  //
  // Circular-next-id holds the next ID to use when printing circularly.
  // Each time print sees an object for a second time during the first
  // printing pass, print assigns as the object's ID the current value of
  // this slot.
  slot circular-next-id :: <integer> = 0;
end class;

define method circular-first-pass? (stream :: <stream>) => (first? :: <boolean>)
  #f
end method;

/*
define sealed domain make (singleton(<circular-print-stream>));
define sealed domain initialize (<circular-print-stream>);
*/


/// <print-reference> Class.
///

/// <print-reference> Class -- Internal.
///
/// These objects hold information about object references encountered when
/// print-circle? is #t.  The print function creates these objects in a fake
/// first printing pass, and then it uses these objects during a real second
/// printing pass to determine whether the object needs to be tagged,
/// printed normally, or printed by reference to the objects circular ID to
/// avoid infinite recursive printing.
///
define sealed class <print-reference> (<object>)
  //
  // This slot holds the object referenced during printing.
  constant slot print-reference-object, required-init-keyword: object:;
  //
  // This slot holds the object's ID for circular references.  The object
  // prints as its ID after the first time.  Before the first time the object
  // is printed, this slot is #f.
  slot print-reference-id :: false-or(<byte-string>) = #f;
  //
  // This slot counts the number of references to the object.
  slot print-reference-count :: <integer> = 0;
end class;

/*
define sealed domain make (singleton(<print-reference>));
define sealed domain initialize (<print-reference>);
*/


/// Print-reference routines.
///

/// print-reference -- Internal Interface.
///
/// This function returns the print-reference object associated with object.
/// If none exists, then this creates a print-reference and installs it in
/// the circular-references table.
///
define method print-reference
    (object, stream :: <circular-print-stream>)
 => (ref :: <print-reference>)
  let table = stream.circular-references;
  let ref = element(table, object, default: #f);
  if (ref)
    ref;
  else
    let ref = make(<print-reference>, object: object);
    element(table, object) := ref;
  end;
end method;

/// new-print-reference-id -- Internal Interface.
///
/// This function gets the next circular print reference ID, assigns it to ref,
/// and updates the stream so that it doesn't return the same ID again.
///
define method new-print-reference-id
    (stream :: <circular-print-stream>, ref :: <print-reference>)
 => (ID :: <byte-string>)
  let id = stream.circular-next-id;
  stream.circular-next-id := id + 1;
  ref.print-reference-id := integer-to-string(id);
end method;




/// Print and global defaults.
///

/// What to print when the current depth exceeds the users requested print
/// level limit.
///
define constant $print-level-exceeded-string :: <byte-string> = "#";

/// What to print before a circular print ID.
///
define constant $circular-id-prestring :: <byte-string> = "#";

/// What to print after a circular print ID.
///
define constant $circular-id-poststring :: <byte-string> = "#";


/// Print -- Exported.
///
define generic print (object, stream :: <stream>,
		      #key level, length, circle?, pretty?, escape?) => ();


define constant <boolean-or-unsupplied> = <object>;  // !@#$ HACK can't deal
//  = type-union(<boolean>, singleton($unsupplied));
define constant <integer-or-false-or-unsupplied> = <object>; // !@#$ HACK ditto
//  = type-union(<integer>, one-of(#f, $unsupplied)); 

/// Print -- Method for Exported Interface.
///
/// This method must regard the values of the keywords and construct a
/// <print-stream> to hold the values for the requested print operation.
///
define method print (object, stream :: <stream>,
		     #key level :: <integer-or-false-or-unsupplied> = $unsupplied,
		          length :: <integer-or-false-or-unsupplied> = $unsupplied,
		          circle? :: <boolean-or-unsupplied> = $unsupplied,
		          pretty? :: <boolean-or-unsupplied> = $unsupplied,
		          escape? :: <boolean-or-unsupplied> = $unsupplied) => ()
  block ()
    //
    // Lock the stream so that all the calls to print-object build output
    // contiguously, without intervening threads screwing up the print
    // request.
    lock-stream(stream);
    //
    // Set slots with those values supplied by the user.
    dynamic-bind (*print-length*  = if (supplied?(length))  length  else *print-length* end,
		  *print-level*   = if (supplied?(level))   level   else *print-level* end,
		  *print-circle?* = if (supplied?(circle?)) circle? else *print-circle?* end,
		  *print-pretty?* = if (supplied?(pretty?)) pretty? else *print-pretty?* end,
		  *print-escape?* = if (supplied?(escape?)) escape? else *print-escape?* end)
      //
      // Make the stream defaulting the slots to the global default values for
      // the keyword arguments.  No need to lock this stream because only this
      // thread should have any references to it ... barring extreme user
      // silliness.
      let p-stream = if (*print-circle?*)
		       make(<circular-print-stream>, inner-stream: stream)
		     else
		       stream
		     end;
      //
      // When printing circularly, we first print to a "null stream" so that we
      // can find the circular references.
      if (*print-circle?*)
	start-circle-printing(object, p-stream);
      end;
      //
      // Determine whether, and how, to print object.
      maybe-print-object(object, p-stream);
    end
  cleanup
    unlock-stream(stream);
  end;
end method;

define method print (object, stream :: <circular-print-stream>,
		     #key level :: <integer-or-false-or-unsupplied> = $unsupplied,
		          length :: <integer-or-false-or-unsupplied> = $unsupplied,
		          circle? :: <boolean-or-unsupplied> = $unsupplied,
		          pretty? :: <boolean-or-unsupplied> = $unsupplied,
		          escape? :: <boolean-or-unsupplied> = $unsupplied) => ()
  dynamic-bind (*print-length*  = if (supplied?(length))  length  else *print-length* end,
		*print-level*   = if (supplied?(level))   level   else *print-level* end,
		*print-pretty?* = if (supplied?(pretty?)) pretty? else *print-pretty?* end,
		*print-escape?* = if (supplied?(escape?)) escape? else *print-escape?* end)
    maybe-print-object(object, stream);
  end
end method;

/// start-circle-printing -- Internal.
///
/// This function makes sure the stream has a circular-references table,
/// makes sure object has a print-reference, checks for circular references
/// within object, and considers what sort of output may be necessary to
/// define a tag for object or print object's tag.
///
/// This function is called both from the very first call to print and
/// recursive calls to print.  The calls to start-circle-printing within
/// recursive calls to print occur when the original call to print had
/// circular printing turned off, and the recursive calls to print turn
/// circular printing on.  Because of this function's use within recursive
/// calls to print, it cannot make certain assumptions:
///    Whether stream already has a circular-references table.
///    Whether there already is a print-reference for object.
///    What print-reference-count is for object.
///    Whether to do a first pass on object looking for circular references.
///    Whether object already has a print-reference-id.
///
/// Recursive calls to print cannot turn off circular printing, so we don't
/// have to account for that.
///
define method start-circle-printing (object, stream :: <circular-print-stream>) => ()
  let table = stream.circular-references;
  if (~ table)
    table := make(<object-table>);
    stream.circular-references := table;
  end;
  let ref = print-reference(object, stream);
  let count :: <integer> = (ref.print-reference-count + 1);
  ref.print-reference-count := count;
  if (count = 1)
    // If this is the first time we've seen this object, then dive into it
    // looking for circular references.
    stream.circular-first-pass? := #t;
    print-object(object, stream);
    stream.circular-first-pass? := #f;
  end;
end method;

/// maybe-print-object -- Internal.
///
/// This function increments print-depth and regards print-level to see
/// whether it should print object.  If it should print object, then it
/// regards print-circle? and does the right thing.
///
define method maybe-print-object (object, stream :: <stream>)
  let depth :: <integer> = (*print-depth* + 1);
  dynamic-bind (*print-depth* = depth)
    let requested-level :: false-or(<integer>) = *print-level*;
    case
      (requested-level & (depth > requested-level)) =>
	write(stream, $print-level-exceeded-string);
      (~ *print-circle?*) =>
	print-object(object, stream);
      (stream.circular-first-pass?) =>
	// When printing circularly, we first print to a "null stream" so
	// that we can find the circular references.
	let ref = print-reference(object, stream);
	let ref-count = (ref.print-reference-count + 1);
	ref.print-reference-count := ref-count;
	if (ref-count = 1)
	  // If ref-count is already greater than one, then there's
	  // no reason to go further into the object gathering references.
	  print-object(object, stream);
	end;
      otherwise
	output-print-reference(print-reference(object, stream), stream);
    end case;
  end;
end method;

/// output-print-reference -- Internal.
///
/// This function determines how to output a print-reference for circular
/// printing.
///
define method output-print-reference (ref :: <print-reference>, stream :: <stream>) => ()
  let ref-id = ref.print-reference-id;
  case
    (ref.print-reference-count = 1) =>
      print-object(ref.print-reference-object, stream);
    (~ ref-id) =>
      write(stream, $circular-id-prestring);
      write(stream, new-print-reference-id(stream, ref));
      write(stream, $circular-id-poststring);
      write(stream, "=");
      print-object(ref.print-reference-object, stream);
    otherwise =>
      write(stream, $circular-id-prestring);
      write(stream, ref-id);
      write(stream, $circular-id-poststring);
  end;
end method;


/// Print-object generic function and its default method.
///

/// print-object -- Exported.
///
define open generic print-object (object, stream :: <stream>)
    => ();

/// Any object.
///
define method print-object (object :: <object>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "{", suffix: "}")
    let obj-class = object.object-class;
    let name = obj-class.debug-name;
    if (name)
      write(stream, "instance of ");
      write(stream, as-lowercase(as(<byte-string>, name)));
    else
      write(stream, "instance of ");
      print(obj-class, stream);
    end if
  end;
end method;

/*---*** This doesn't seem to be used anymore... anyone care to flush it?
define method print-object-slots 
    (object :: <object>, stream :: <stream>)
 => ()
  printing-logical-block (stream, prefix: "{", suffix: "}")
    let obj-class = object.object-class;
    write-class-name(obj-class, stream);
    write(stream, " instance");
    let descriptors = obj-class.slot-descriptors;
    if (~ (descriptors = #()))
      write(stream, ", ");
      pprint-indent(#"block", 2, stream);
      pprint-newline(#"linear", stream);
      // Print slot names and values.
      printing-logical-block (stream, prefix: #f)
	block (exit)
	  let length :: false-or(<integer>) = *print-length*;
	  for (desc in descriptors,
	       // Count each slot name and value as two
	       // for considerations of print-length.
	       count = 0 then (count + 2))
	    if (count ~= 0)
	      write(stream, ", ");
	      pprint-newline(#"linear", stream);
	    end if;
	    if (length & (count >= length))
	      write(stream, "...");
	      exit();
	    end if;
	    write(stream,
		  as(<byte-string>, desc.debug-name));
	    write(stream, ": ");
	    // pprint-tab(#"section-relative", 0, 0, stream);
	    pprint-newline(#"fill", stream);
	    let (value, win?) = slot-value(desc, object);
	    if (win?)
	      print(value, stream);
	    else
	      write(stream, "{UNINITIALIZED}");
	    end if;
	  end for;
	end block;
      end;
    end if;
  end
end method print-object;
*/


/// Print-object <byte-string>, <unicode-string> and <character> methods.
///

/// Characters.
///
define sealed method print-object (char :: <character>, stream :: <stream>) => ()
  if (*print-escape?*)
    write-element(stream, '\'');
    write-char-maybe-escape(stream, char, '\'');
    write-element(stream, '\'')
  else
    write-element(stream, char)
  end
end method print-object;

/// write-char-maybe-escape -- Internal.
///
/// Utility routine used for printing characters appropriately escaped.
///
define method write-char-maybe-escape
    (stream :: <stream>, char :: <character>, _quote :: one-of('\'', '"')) 
 => ()
  case
    char < ' ' =>
      select (char)
	'\0' => write(stream, "\\0");
	'\a' => write(stream, "\\a");
	'\b' => write(stream, "\\b");
	'\t' => write(stream, "\\t");
	'\f' => write(stream, "\\f");
	'\r' => write(stream, "\\r");
	'\n' => write(stream, "\\n");
	'\e' => write(stream, "\\e");
	otherwise =>
	  write(stream, "\\<");
	  write(stream, integer-to-string(as(<integer>, char), base: 16));
	  write-element(stream, '>');
      end select;
    char == _quote =>
      write-element(stream, '\\');
      write-element(stream, char);
    char == '\\' =>
      write(stream, "\\\\");
    char > '~' =>
      write(stream, "\\<");
      write(stream, integer-to-string(as(<integer>, char), base: 16));
      write-element(stream, '>');
    otherwise =>
      write-element(stream, char);
  end case;
end method write-char-maybe-escape;


/// strings.
///
define method print-object (object :: <string>, stream :: <stream>) => ()
  if (*print-escape?*)
    write-string-escaped(stream, object)
  else
    write-text(stream, object)
  end
end method print-object;


/// write-string-escaped -- Internal Interface.
///
/// Utility used by <byte-string>, <unicode-string>, and <symbol> print-object
/// methods to print the string with appropriate characters escaped.
///
define generic write-string-escaped (stream :: <stream>, object :: <string>)
    => ();

/// write-string-escaped -- Method for Internal Interface.
///
/// We try to write as much of the string as possible at once in order to
/// keep from having to make lots of extra calls to write.  We scan the
/// string for the next character that required special processing and then
/// write all the skipped over characters in one chunk.
///
define method write-string-escaped
    (stream :: <stream>, object :: <byte-string>) => ()
  let len :: <integer> = object.size;
  local

    method find-next-break (index :: <integer>)
	=> (next-break :: <integer>, char :: <byte-character>)
      if (index == len)
	// It doesn't matter what character we return, we just need to
	// return some character so the type matches.
	values(index, 'x');
      else
	let char = object[index];
	if (char < ' ' | char == '"' | char == '\\' | char > '~')
	  values(index, char);
	else
	  find-next-break(index + 1);
	end if;
      end if;
    end method find-next-break,

    method write-guts (from :: <integer>) => ()
      let (next-break, char) = find-next-break(from);
      unless (from == next-break)
	write(stream, object, start: from, end: next-break);
      end unless;
      unless (next-break == len)
	write-char-maybe-escape(stream, char, '"');
	write-guts(next-break + 1);
      end unless;
    end method write-guts;

  write-element(stream, '"');
  write-guts(0);
  write-element(stream, '"');
end method write-string-escaped;

/// write-string-escaped -- Method for Internal Interface.
///
/// We can't write chunks of the string at once, so just pay the cost and
/// write each character individually.
///
define method write-string-escaped
    (stream :: <stream>, object :: <string>) => ()
  write-element(stream, '"');
  for (char in object)
    write-char-maybe-escape(stream, char, '"');
  end for;
  write-element(stream, '"');
end method write-string-escaped;



/// Print-object <list> method.
///

/// For circular printing to be correct, we need to count references to the
/// tail pointers as well as the head pointers.  Because we do not print lists
/// by calling print on the tail of each pair, we need to specially handle
/// the tail pointers in this method.  The object passed in and all head
/// pointers are handled naturally via calls to print.
///
define sealed method print-object (object :: <list>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "#(", suffix: ")")
    if (~ (object == #()))
      print-list(object, stream);
    end
  end
end method;

define method print-list (object :: <list>, stream :: <stream>) => ()
  block(exit)
    let length :: false-or(<integer>) = *print-length*;
    if (length & (length <= 0))
      write(stream, "...");
    else
      print(object.head, stream);
      let circle? = *print-circle?*;
      let first-pass? = stream.circular-first-pass?;
      for (remaining = object.tail then remaining.tail,
	   count = 1 then (count + 1),
	   until: (remaining == #()))
	if (~ instance?(remaining, <list>))
	    // Object was not a proper list, so print dot notation.
	    write(stream, " . ");
	    pprint-newline(#"fill", stream);
	    print(remaining, stream);
	    exit();
	end if;
	write(stream, ", ");
	pprint-newline(#"fill", stream);
	case
	  (length & (count >= length)) =>
	    // We've exceeded print-length for this print request.
	    write(stream, "...");
	    exit();
	  (~ circle?) =>
	    // No circular printing, so this is the simple and normal case.
	    print(remaining.head, stream);
	  (first-pass?) =>
	    // Get or create the print-reference for the remaining pointer.
	    let ref = print-reference(remaining, stream);
	    let ref-count = (ref.print-reference-count + 1);
	    ref.print-reference-count := ref-count;
	    if (ref-count = 1)
	      // First time through, so keep gathering references.
	      print(remaining.head, stream);
	    else
	      // If ref-count is already greater than one, then we've seen
	      // everything once.  Stop iterating.
	      exit();
	    end;
	  otherwise =>
	    // Circular printing on the second pass.
	    let ref = print-reference(remaining, stream);
	    let ref-id = ref.print-reference-id;
	    case
	      (ref.print-reference-count = 1) =>
		// Only one reference to the rest of the list, so print the
		// remaining elements normally.
		print(remaining.head, stream);
	      (~ ref-id) =>
		// Print the tag and its value with dot notation so that
		// the rest of the list does not appear to be a single
		// element of the list (that is, a nested list).
		write(stream, ". ");
		pprint-newline(#"fill", stream);
		write(stream, $circular-id-prestring);
		write(stream, new-print-reference-id(stream, ref));
		write(stream, $circular-id-poststring);
		write(stream, "=");
		print(remaining, stream);
	      otherwise =>
		// Print the tag with dot notation.  See previous cases's
		// comment.
		write(stream, ". ");
		pprint-newline(#"fill", stream);
		write(stream, $circular-id-prestring);
		write(stream, ref-id);
		write(stream, $circular-id-poststring);
		exit();
	    end case;
	end case;
      end for;
    end if;
  end block;
end method;


/// Print-object <simple-object-vector> method.
///

/// Vectors.
///
define sealed method print-object
    (object :: <simple-object-vector>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "#[", suffix: "]")
    print-items(object, print, stream)
  end
end method;


/// Print-object <function> method.
///

/// Functions.
///
define sealed method print-object
    (object :: <generic-function>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "{", suffix: "}")
    write(stream, "generic function");
    let name = debug-name(object);
    if (name)
      write-element(stream, ' ');
      pprint-newline(#"fill", stream);
      write(stream, as-lowercase(as(<byte-string>, name)));
    end;
  end
end method;

define sealed method print-object (object :: <method>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "{", suffix: "}")
    write(stream, "method");
    let name = debug-name(object);
    if (name)
      write-element(stream, ' ');
      pprint-newline(#"fill", stream);
      write(stream, as(<byte-string>, name));
    end;
    let specializers = function-specializers(object);
    write-element(stream, ' ');
    pprint-newline(#"fill", stream);
    printing-logical-block (stream, prefix: "(", suffix: ")")
      print-function-specializers(object, stream)
    end
  end
end method;

define method print-function-specializers
    (object :: <function>, stream :: <stream>) => ();
  let specializers = function-specializers(object);
  if (~ (specializers = #()))
    write-element(stream, ' ');
    pprint-newline(#"fill", stream);
    printing-logical-block (stream, prefix: "(", suffix: ")")
      print-items(specializers, print-specializer, stream)
    end
  end if;
end method print-function-specializers;

/// print-items -- Internal Interface.
///
/// This function prints each element of items, separated by commas, using
/// print-fun.  This function also regards print-length.  Stream must be a
/// pretty printing stream or a <print-stream> whose target is a pretty
/// printing stream, so this function is basically good for use in body:
/// methods passed to pprint-logical-block.
///
/// DO NOT use this function for collections that may be tail-circular; it
/// will not terminate.
///
define method print-items
    (items :: <collection>, print-fun :: <function>, stream :: <stream>) => ()
  block (exit)
    let length :: false-or(<integer>) = *print-length*;
    for (x in items,
	 count = 0 then (count + 1))
      if (count ~= 0)
	write(stream, ", ");
	pprint-newline(#"fill", stream);
      end;
      if (length & (count = length))
	write(stream, "...");
	exit();
      end;
      print-fun(x, stream);
    end for;
  end block;
end method;



/// Print-specializer generic function and methods.
///

/// This function is used in printing methods.
///

define sealed generic print-specializer (type :: <type>, stream :: <stream>)
    => ();

define method print-specializer (type :: <type>, stream :: <stream>) => ()
  // write(stream, "{unknown type}")
  print(type, stream)
end method;

define method print-specializer (type :: <class>, stream :: <stream>) => ()
  let name = type.debug-name;
  if (name)
    write(stream, as-lowercase(as(<byte-string>, name)));
  else
    print(type, stream);
  end if;
end method;

define method print-specializer (type :: <singleton>, stream :: <stream>) => ()
  write(stream, "singleton(");
  print(type.singleton-object, stream);
  write(stream, ")");
end method;

define method print-specializer (type :: <subclass>, stream :: <stream>) => ()
  write(stream, "subclass(");
  print-specializer(type.subclass-class, stream);
  write(stream, ")");
end method;

define method print-specializer (type :: <limited-integer>, stream :: <stream>)
    => ();
  write(stream, "{Limited integer ");
  // write-class-name(type.limited-integer-class, stream);
  // write-element(stream, ' ');
  print(type.limited-integer-min, stream);
  write(stream, "..");
  print(type.limited-integer-max, stream);
  write(stream, "}");
end method print-specializer;

define method print-specializer (type :: <union>, stream :: <stream>)
    => ();
  printing-logical-block (stream, prefix: "{", suffix: "}")
    write(stream, "Union ");
    pprint-newline(#"fill", stream);
    // print(type.union-members, stream);
    print(union-type1(type), stream);
    write(stream, ", ");
    pprint-newline(#"linear", stream);
    print(union-type2(type), stream);
  end
end method print-specializer;


/// Print-object methods for <type> and its subclasses.
///

// General method for lots of different kinds of types.  We don't
// specialize on <type>, because if print-specializer doesn't know how to
// print the type, it will call print, and we would just end up back here.
// Instead, we carefully enumerate the types that print-specializer can
// deal with.
// 

/*
// Kludge: unions don't work in the emulator, so split this up
define sealed method print-object
    (object :: type-union(<singleton>, <limited-integer>, <union>), stream :: <stream>) => ()
  pprint-logical-block
    (stream,
     prefix: "{Type ",
     body: method (stream :: <stream>) => ()
	     print-specializer(object, stream);
	   end method,
     suffix: "}");
end method print-object;
*/

define function print-type-object (object, stream :: <stream>) => ()
  pprint-logical-block
    (stream,
     prefix: "{Type ",
     body: method (stream :: <stream>) => ()
	     print-specializer(object, stream);
	   end method,
     suffix: "}");
end;

define sealed method print-object
    (object :: <singleton>, stream :: <stream>) => ()
  print-type-object(object, stream);
end method print-object;

define sealed method print-object
    (object :: <limited-integer>, stream :: <stream>) => ()
  print-type-object(object, stream);
end method print-object;

define sealed method print-object
    (object :: <union>, stream :: <stream>) => ()
  print-type-object(object, stream);
end method print-object;

/// For classes, we just print the class name if there is one.
/// 
define sealed method print-object (object :: <class>, stream :: <stream>) => ();
  write(stream, "{class ");
  write-class-name(object, stream);
  write(stream, "}");
end method print-object;

/// write-class-name -- Internal Interface.
///
/// This function writes the name of the class or "<UNNAMED-CLASS>" to stream.
/// It does not output any curly braces, the word "class", or anything else.

define method write-class-name (object :: <class>, stream :: <stream>)
    => ();
  let name = debug-name(object);
  if (name)
    write(stream, as-lowercase(as(<byte-string>, name)));
  else
    write(stream, "<unnamed-class>");
  end if;
end method write-class-name;


/// Print-object miscellaneous methods.
///

define method print-object
    (deque :: <deque>, stream :: <stream>) => ()
  if (empty?(deque))
    write(stream, "{empty deque}")
  else
    printing-logical-block (stream, prefix: "{deque of ", suffix: "}")
      print-list(as(<list>, deque), stream)
    end
  end
end method;

define method print-object
    (r :: <range>, stream :: <stream>) => ()
  let s = r.size;
  if (s & zero?(s))
    write(stream, "{empty range}")
  else
    let f = range-from(r);
    let b = range-by(r);
    printing-logical-block (stream, prefix: "{range ", suffix: "}")
      if (s)
	print-object(f, stream);
	write(stream, " through ");
	print-object(last(r), stream);
	write(stream, " by ");
	print-object(b, stream);
      else
	print-object(f, stream);
	write(stream, " by ");
	print-object(b, stream);
      end
    end
  end
end method;

define method print-object
    (object :: <stretchy-vector>, stream :: <stream>) => ()
  printing-logical-block (stream, prefix: "{stretchy vector ", suffix: "}")
    print-items(object, print, stream)
  end
end method;

/// #t.
///
define sealed method print-object
    (object :: singleton(#t), stream :: <stream>) => ()
  write(stream, "#t");
end method;

/// #f.
///
define sealed method print-object
    (object :: singleton(#f), stream :: <stream>) => ()
  write(stream, "#f");
end method;

/// Symbols.
///
define sealed method print-object
    (object :: <symbol>, stream :: <stream>) => ()
  if (*print-escape?*)
    write-element(stream, '#');
    write-string-escaped(stream, as-lowercase(as(<byte-string>, object)))
  else
    write(stream, as-lowercase(as(<byte-string>, object)))
  end
end method;

/// Integers.
///
///---*** NOTE: When division is implemented for <big-integer>s, change
///---*** the specializer here to <abstract-integer> and rely on
///---*** integer-to-string being defined for <big-integer> as well as <integer>
define sealed method print-object
    (object :: <integer>, stream :: <stream>) => ()
  write(stream, integer-to-string(object));
end method;

/// Ratios.
///
//define sealed method print-object
//    (object :: <ratio>, stream :: <stream>) => ()
//  write(stream, integer-to-string(object.numerator));
//  write-element(stream, '/');
//  write(stream, integer-to-string(object.denominator));
//end;

/// Machine-Words
///
define sealed method print-object
    (object :: <machine-word>, stream :: <stream>) => ()
  write(stream, machine-word-to-string(object))
end method;


/// Float printing.
///

define sealed method print-object
    (float :: <float>, stream :: <stream>) => ()
  write(stream, float-to-string(float))
end;


/// Locator printing.
///

define sealed method print-object
    (locator :: <locator>, stream :: <stream>) => ()
  if (*print-escape?*)
    next-method()
  else
    write(stream, as(<string>, locator))
  end
end method print-object;


/// print-to-string -- Exported.
///
define generic print-to-string (object, #rest args,
				#key level, length, circle?, pretty?, escape?)
    => (result :: <string>);

define method print-to-string
    (object, #rest args,
     #key level :: false-or(<integer>), length :: false-or(<integer>),
          circle? :: <boolean>, pretty? :: <boolean>, escape? :: <boolean>)
 => (result :: <byte-string>);
  // Assume it is a small amount of printing.
  let s = make(<byte-string-stream>, contents: "", direction: #"output");
  apply(print, object, s, args);
  s.stream-contents
end method;



/// Output methods for <circular-print-stream>s.
///

define method write-element
    (stream :: <circular-print-stream>, ele :: <object>) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    write-element(stream.inner-stream, ele);
  end;
end method;

define method write
    (stream :: <circular-print-stream>, seq :: <sequence>,
     #key start :: <integer> = 0, end: stop :: <integer> = seq.size) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    write(stream.inner-stream, seq, start: start, end: stop);
  end;
end method;

define method force-output
    (stream :: <circular-print-stream>, #key synchronize? :: <boolean>) => ()
  ignore(synchronize?);
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    force-output(stream.inner-stream);
  end;
end method;

define method synchronize-output
    (stream :: <circular-print-stream>) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    synchronize-output(stream.inner-stream);
  end;
end method;

define method
    discard-output (stream :: <circular-print-stream>) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    discard-output(stream.inner-stream);
  end;
end method;

define method write-line
    (stream :: <circular-print-stream>, string :: <string>,
     #key start :: <integer> = 0, end: stop :: <integer> = string.size) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    write-line(stream.inner-stream, string, start: start, end: stop);
  end;
end method;

define method new-line
    (stream :: <circular-print-stream>) => ()
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    new-line(stream.inner-stream);
  end;
end method;

define method stream-open?
    (stream :: <circular-print-stream>) => (open? :: <boolean>)
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    stream-open?(stream.inner-stream);
  end;
end method;

/*--- andrewa: This doesn't type check, and seems bogus
define method stream-element-type
    (stream :: <circular-print-stream>) => (type :: <type>)
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    stream-element-type(stream.inner-stream);
  end;
end method;
*/

define method stream-at-end?
    (stream :: <circular-print-stream>) => (at-end? :: <boolean>)
  if (~ (*print-circle?* & (stream.circular-first-pass?)))
    stream-at-end?(stream.inner-stream);
  end;
end method;


/// Pretty-printer support.

/// The methods on this page extend the pprint interface to <print-stream>s.
/// Doing this allows users to write print-object methods that attempt to do
/// pretty printing, but when print is called with pretty?: #f, all the
/// pretty printing directions in the print-object method become no-ops.
///

/// pprint-logical-block -- Method for Exported Interface.
///
/// When we first enter this method, we pass the print-target of the
/// <print-stream> to the recursive call to pprint-logical-block.  This
/// causes pprint-logical-block to wrap a pretty printing stream around the
/// target.  Then, when pprint-logical-block calls the body: method defined
/// here, the body: method wraps the <print-stream> around the newly created
/// pretty printing stream, nesting the ultimate target stream twice.  This
/// allows printing to continue with the print function handling all the
/// stuff for the user as it is supposed to do, but as output is passed on
/// to the print-stream's target, it gets pretty print processed before
/// going onto the ultimate target.
///
/// Since the <print-stream> passed into this method is wrapped around the
/// outside of the pretty printing stream, should some print-object method
/// call ppring-logical-block, this method executes again.  However, during
/// this recursive execution, this method invokes pprint-logical-block on
/// the print-target of <print-stream>, which the pretty printing stream.
/// That means the value of pretty-stream in our body: method below is == to
/// print-target of our <print-stream>.  When this is true, we do not need
/// to do any extra wrapping because we already have the three streams
/// (print stream, pretty stream, and the target) nested just the way we
/// want them.
///
define sealed method pprint-logical-block
    (stream :: <circular-print-stream>,
     #key column :: <integer> = 0, prefix :: false-or(<byte-string>),
          per-line-prefix :: false-or(<byte-string>),
	  body :: <function>, suffix :: false-or(<byte-string>)) => ()
  if (prefix & per-line-prefix)
    error("Can't specify both a prefix: and a per-line-prefix:");
  end;
  case
    (*print-circle?* & (stream.circular-first-pass?)) => #f;
    (*print-pretty?*) =>
      let target = stream.inner-stream;
      pprint-logical-block(target,
			   column: column,
			   prefix: prefix,
			   per-line-prefix: per-line-prefix,
			   body: method (pretty-stream)
				   if (pretty-stream == target)
				     body(stream);
				   else
				     let orig-target = stream.inner-stream;
				     stream.inner-stream := pretty-stream;
				     body(stream);
				     stream.inner-stream := orig-target;
				   end;
				 end,
			   suffix: suffix);
    otherwise =>
      if (prefix | per-line-prefix)
	write(stream, (prefix | per-line-prefix));
      end;
      body(stream);
      if (suffix) write(stream, suffix) end;
  end case;
end method;

/// pprint-newline -- Method for Exported Interface.
///
define sealed method pprint-newline
    (kind :: <pretty-newline-kind>, stream :: <stream>)
 => ()
  case
    ((~ (*print-circle?* & (stream.circular-first-pass?)))
       & *print-pretty?*) =>
      pprint-newline(kind, stream.inner-stream);
    (kind == #"mandatory") =>
      new-line(stream);
  end;
end;

define sealed method pprint-indent
    (relative-to :: <indentation-kind>, n :: <integer>, stream :: <stream>)
 => ()
  if ((~ (*print-circle?* & (stream.circular-first-pass?)))
	& *print-pretty?*)
    pprint-indent(relative-to, n, stream.inner-stream);
  end;
end;

define sealed method pprint-tab
    (kind :: <tab-kind>, colnum :: <integer>, colinc :: <integer>,
     stream :: <stream>)
 => ()
  if ((~ (*print-circle?* & (stream.circular-first-pass?)))
	& *print-pretty?*)
    pprint-tab(kind, colnum, colinc, stream.inner-stream);
  end;
end;


/// 'printing-object'

define macro printing-object
  { printing-object (?object:expression, ?stream:name, #rest ?options:expression)
      ?:body
    end }
    => { begin
           let print-object-body = method (?stream) ?body end;
           do-printing-object(?object, ?stream, print-object-body, ?options)
         end }
end macro printing-object;

define method do-printing-object 
    (object, stream :: <stream>, continuation :: <function>,
     #key type? = #t, identity? = #t) => ()
  let class = object.object-class;
  printing-logical-block (stream, prefix: "{", suffix: "}")
    case
      type? & identity? =>
        write(stream, as-lowercase(as(<byte-string>, class.debug-name)));
        write(stream, " ");
        continuation(stream);
        //--- write(stream, " ");
        //--- write(stream, integer-to-string(object-address(object), base: 16));
      type? =>
        write(stream, as-lowercase(as(<byte-string>, class.debug-name)));
        write(stream, " ");
        continuation(stream);
      identity? =>
        continuation(stream);
        //--- write(stream, " ");
        //--- write(stream, integer-to-string(object-address(object), base: 16));
      otherwise =>
        continuation(stream);
    end
  end
end method do-printing-object;