############################################################################
#
#	File:     image.icn
#
#	Subject:  Procedures to produce images of Icon values
#
#	Authors:  Michael Glass, Ralph E. Griswold, and David Yost
#
#	Date:     May 2, 2001
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#  
#  The procedure Image(x,style) produces a string image of the value x.
#  The value produced is a generalization of the value produced by
#  the Icon function image(x), providing detailed information about
#  structures. The value of style determines the formatting and
#  order of processing:
#
#     1   indented, with ] and ) at end of last item (default)
#     2   indented, with ] and ) on new line
#     3   puts the whole image on one line
#     4   as 3, but with structures expanded breadth-first instead of
#         depth-first as for other styles.
#  
############################################################################
#
#     Tags are used to uniquely identify structures. A tag consists
#  of a letter identifying the type followed by an integer. The tag
#  letters are L for lists, R for records, S for sets, and T for
#  tables. The first time a structure is encountered, it is imaged
#  as the tag followed by a colon, followed by a representation of
#  the structure. If the same structure is encountered again, only
#  the tag is given.
#  
#     An example is
#  
#     a := ["x"]
#     push(a,a)
#     t := table()
#     push(a,t)
#     t[a] := t
#     t["x"] := []
#     t[t] := a
#     write(Image(t))
#  
#  which produces
#  
#  T1:[
#    "x"->L1:[],
#    L2:[
#      T1,
#      L2,
#      "x"]->T1,
#    T1->L2]
#
#  On the other hand, Image(t,3) produces
#
#     T1:["x"->L1:[],L2:[T1,L2,"x"]->T1,T1->L2]
#  
#  Note that a table is represented as a list of entry and assigned
#  values separated by ->.
#  
############################################################################
#
#  Problem:
#
#     The procedure here really is a combination of an earlier version and
#  two modifications to it.  It should be re-organized to combine the
#  presentation style and order of expansion.
#
#  Bug:
#
#     Since the table of structures used in a call to Image is local to
#  that call, but the numbers used to generate unique tags are static to
#  the procedures that generate tags, the same structure gets different
#  tags in different calls of Image.
#
############################################################################

procedure Image(x,style,done,depth,nonewline)
   local retval

   if style === 4 then return Imageb(x)	# breadth-first style

   /style := 1
   /done := table()
   if /depth then depth := 0
   else depth +:= 2
   if (style ~= 3 & depth > 0 & /nonewline) then
      retval := "\n" || repl(" ",depth)
   else retval := ""
   if match("record ",image(x)) then retval ||:= Rimage(x,done,depth,style)
   else {
      retval ||:=
      case type(x) of {
	 "list":  Limage(x,done,depth,style)
	 "table": Timage(x,done,depth,style)
	 "set":   Simage(x,done,depth,style)
	 default: image(x)
	 }
   }
   depth -:= 2
   return retval
end

#  list image
#
procedure Limage(a,done,depth,style)
   static i
   local s, tag
   initial i := 0
   if \done[a] then return done[a]
   done[a] := tag := "L" || (i +:= 1)
   if *a = 0 then s := tag || ":[]" else {
      s := tag || ":["
      every s ||:= Image(!a,style,done,depth) || ","
      s[-1] := endof("]",depth,style)
      }
   return s
end

#  record image
#
procedure Rimage(x,done,depth,style)
   static i
   local s, tag
   initial i := 0
   s := image(x)
					#  might be record constructor
   if match("record constructor ",s) then return s
   if \done[x] then return done[x]
   done[x] := tag := "R" || (i +:= 1)
   s ?:=  (="record " & (":" || (tab(upto('(') + 1))))
   if *x = 0 then s := tag || s || ")" else {
      s := tag || s
      every s ||:= Image(!x,style,done,depth) || ","
      s[-1] := endof(")",depth,style)
      }
   return s
end

# set image
#
procedure Simage(S,done,depth,style)
   static i
   local s, tag
   initial i := 0
   if \done[S] then return done[S]
   done[S] := tag := "S" || (i +:= 1)
   if *S = 0 then s := tag || ":[]" else {
      s := tag || ":["
      every s ||:= Image(!S,style,done,depth) || ","
      s[-1] := endof("]",depth,style)
      }
   return s
end

#  table image
#
procedure Timage(t,done,depth,style)
   static i
   local s, tag, a, a1
   initial i := 0
   if \done[t] then return done[t]
   done[t] := tag := "T" || (i +:= 1)
   if *t = 0 then s := tag || ":[]" else {
      a := sort(t,3)
      s := tag || ":["
      while s ||:= Image(get(a),style,done,depth) || "->" ||
		   Image(get(a),style,done,depth,1) || ","
      s[-1] := endof("]",depth,style)
      }
   return s
end

procedure endof (s,depth,style)
   if style = 2 then return "\n" || repl(" ",depth) || "]"
   else return "]"
end

############################################################################
#
#  What follows is the breadth-first expansion style
#

procedure Imageb(x, done, tags)
   local t

   if /done then {
      done := [set()]  # done[1] actually done; done[2:0] pseudo-done
      tags := table()    # unique label for each structure
      }

   if member(!done, x) then return tags[x]

   t := tagit(x, tags)     # The tag for x if structure; image(x) if not

   if /tags[x] then
      return t                       # Wasn't a structure
   else {
      insert(done[1], x)             # Mark x as actually done
      return case t[1] of {
         "R":  rimageb(x, done, tags)     # record
         "L":  limageb(x, done, tags)     # list
         "T":  timageb(x, done, tags)     # table
         "S":  simageb(x, done, tags)     # set
         }
      }
end


#  Create and return a tag for a structure, and save it in tags[x].
#  Otherwise, if x is not a structure, return image(x).
#
procedure tagit(x, tags)
   local ximage, t, prefix
   static serial
   initial serial := table(0)

   if \tags[x] then return tags[x]

   if match("record constructor ", ximage := image(x)) then
      return ximage                # record constructor

   if match("record ", t := ximage) |
      ((t := type(x)) == ("list" | "table" | "set")) then {
         prefix := map(t[1], "rlts", "RLTS")
         return tags[x] := prefix || (serial[prefix] +:=1)
         }                        # structure

   else return ximage             # anything else
end


#  Every component sub-structure of the current structure gets tagged
#  and added to a pseudo-done set.
#
procedure defer_image(a, done, tags)
   local x, t
   t := set()
   every x := !a do {
      tagit(x, tags)
      if \tags[x] then insert(t, x)  # if x actually is a sub-structure
      }
   put(done, t)
   return
end


#  Create the image of every component of the current structure.
#  Sub-structures get deleted from the local pseudo-done set before
#  we actually create their image.
#
procedure do_image(a, done, tags)
   local x, t
   t := done[-1]
   suspend (delete(t, x := !a), Imageb(x, done, tags))
end


#  list image
#
procedure limageb(a, done, tags)
   local s
   if *a = 0 then s := tags[a] || ":[]" else {
      defer_image(a, done, tags)
      s := tags[a] || ":["
      every s ||:= do_image(a, done, tags) || ","
      s[-1] := "]"
      pull(done)
      }
   return s
end

#  record image
#
procedure rimageb(x, done, tags)
   local s
   s := image(x)
   s ?:=  (="record " & (":" || (tab(upto('(') + 1))))
   if *x = 0 then s := tags[x] || s || ")" else {
      defer_image(x, done, tags)
      s := tags[x] || s
      every s ||:= do_image(x, done, tags) || ","
      s[-1] := ")"
      pull(done)
      }
   return s
end

# set image
#
procedure simageb(S, done, tags)
   local s
   if *S = 0 then s := tags[S] || ":[]" else {
      defer_image(S, done, tags)
      s := tags[S] || ":["
      every s ||:= do_image(S, done, tags) || ","
      s[-1] := "]"
      pull(done)
      }
   return s
end

#  table image
#
procedure timageb(t, done, tags)
   local s, a
   if *t = 0 then s := tags[t] || ":[]" else {
      a := sort(t,3)
      defer_image(a, done, tags)
      s := tags[t] || ":["
      while s ||:= do_image([get(a)], done, tags) || "->" ||
                   do_image([get(a)], done, tags) || ","
      s[-1] := "]"
      pull(done)
      }
   return s
end
