#########################################################################
#
#	File:     algae.icn
#
#	Subject:  Program to show expression evaluation as ``algae''
#
#	Author:   Clinton Jeffery
#
#	Date:     November 22, 1997
#
#########################################################################
#
#   This file is in the public domain.
#
############################################################################
#
# Press ESC or q to quit
# Left mouse assigns specific (row,column) break "points"
# Middle mouse assigns absolute depth and width break lines
# Right button erases assigned break "points"
#
# When paused due to a break, you can:
#
# c to continue
# s to single step
# C to clear one point and continue
# " " to clear everything and continue
#
#########################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  Includes:  evdefs.icn
#
############################################################################

$include "evdefs.icn"

link evinit
link evutils
link options
link optwindw
link hexlib
link evaltree

global scale,          # cell (hexagon or square) size
   step,                 # single step mode
   numrows,         # number of cell rows
   numcols,          # number of cell columns
   spot,                 # cell-fill procedure (hex or square)
   mouse,             # cell-mouse-locator procedure
   Visualization,    # the window
   wHexOutline,    # binding for drawing cell outlines
   depthbound,     # call-depth on which to break
   breadthbound, # suspension-width on which to break
   hotspots            # table of individual cells on which to break

record algae_activation(node, row, column, parent, children, color)


#
# main() - program entry point.  The main loop is in evaltree().
#
procedure main(av)
   local codes, algaeoptions
   #
   # pull off algae options (don't consume child's options in this call
   #  to options()).
   #
   algaeoptions := []
   while av[1][1] == "-" do {
      put(algaeoptions, pop(av))
      if algaeoptions[-1] == "-f" then put(algaeoptions, pop(av))
      }
   EvInit(av) | stop("Can't EvInit ",av[1])
   codes := algae_init(algaeoptions)
   evaltree(codes, algae_callback, algae_activation)
   WAttrib("windowlabel=Algae: finished")
   EvTerm(&window)
end

#
# algae_init() - initialization and command-line processing.
#  This procedure supplies default behavior and handles options.
#
procedure algae_init(algaeoptions)
   local t, position, geo, codes, i, cb, coord, e, s, x, y, m, row, column
   t := options(algaeoptions,
		winoptions() || "P:S:-geo:-square!-func!-scan!-op!-noproc!")
   /t["L"] := "Algae"
   /t["B"] := "cyan"
   scale := \t["S"] | 12
   if \t["square"] then {
      spot := square_spot
      mouse := square_mouse
      }
   else {
      scale /:= 4
      spot := hex_spot
      mouse := hex_mouse
      }
   codes := cset(E_MXevent)
   if /t["noproc"] then codes ++:= ProcMask
   if \t["scan"]   then codes ++:= ScanMask
   if \t["func"]   then codes ++:= FncMask
   if \t["op"]     then codes ++:= OperMask
   hotspots := table()
   &window := Visualization := optwindow(t) | stop("no window")
   numrows := (XHeight() / (scale * 4))
   numcols := (XWidth() / (scale * 4))
   wHexOutline := Color("white") # used by the hexagon library
   if /t["square"] then starthex(Color("black"))
   return codes
end

#
# algae_callback() - evaltree callback procedure for algae.
#  Called for each event, it updates the screen to correspond
#  to the change in the activation tree.
#
procedure algae_callback(new, old)
   local coord, e
   initial {
      old.row := old.parent.row := 0; old.column := old.parent.column := 1
      }
   case &eventcode of {
      !CallCodes: {
	 new.column := (old.children[-2].column + 1 | computeCol(old)) | stop("eh?")
	 new.row := old.row + 1
	 new.color := Color(&eventcode)
	 spot(\old.color, old.row, old.column)
	 }
      !ReturnCodes |
      !FailCodes: spot(Color("light blue"), old.row, old.column)
      !SuspendCodes |
      !ResumeCodes: spot(old.color, old.row, old.column)
      !RemoveCodes: {
	 spot(Color("black"), old.row, old.column)
	 WFlush(Color("black"))
	 delay(100)
	 spot(Color("light blue"), old.row, old.column)
	 }
      E_MXevent: do1event(&eventvalue, new)
      }
   spot(Color("yellow"), new.row, new.column)
   coord := location(new.column, new.row)
   if \step | (\breadthbound <= new.column) | (\depthbound <= new.row) |
      \ hotspots[coord] then {
      step := &null
      WAttrib("windowlabel=Algae stopped: (s)tep (c)ont ( )clear ")
      while e := Event() do
	 if do1event(e, new) then break
      WAttrib("windowlabel=Algae")
      if \ hotspots[coord] then spot(Color("light blue"), new.row, new.column)
      }
end


#
# procedures for the "-square" option, display Algae using squares
# instead of hexagons.
#

# Draw a square at (row, column)
procedure square_spot(w, row, column)
   FillRectangle(w, (column - 1) * scale, (row - 1) * scale, scale, scale)
end


# encode a location value (base 1) for a given x and y pixel
procedure square_mouse(y, x)
   return location(x / scale + 1, y / scale + 1)
end

#
# clearspot() removes a "breakpoint" at (x,y)
#
procedure clearspot(spot)
   local x, y, s2, x2, y2

   hotspots[spot] := &null
   y := vertical(spot)
   x := horizontal(spot)
   every s2 := \!hotspots do {
      x2 := horizontal(s2)
      y2 := vertical(s2)
   }
   spot(Visualization, y, x)
end

#
# setspot() sets a breakpoint at (x,y) and marks it orange
#
procedure setspot(loc)
   local x, y

   hotspots[loc] := loc
   y := vertical(loc)
   x := horizontal(loc)
   spot(Color("orange"), y, x)
end

#
# do1event() processes a single user input event.
#
procedure do1event(e, new)
   local m, xbound, ybound, row, column, x, y, s, p

   case e of {
      "q" |
      "\e": exit()
      "s": { # execute a single step
	 step := 1
	 return
	 }
      "C": { # clear a single break point
	 clearspot(location(new.column, new.row))
	 return
	 }
      " ": { # space character: clear all break points
	 if \depthbound then {
	    every y := 1 to numcols do {
	       if not who_is_at(depthbound, y, new) then
		  spot(Visualization, depthbound, y)
	       }
	    }
	 if \breadthbound then {
	    every x := 1 to numrows do {
	       if not who_is_at(x, breadthbound, new) then
		  spot(Visualization, x, breadthbound)
	       }
	    }
	 every s := \!hotspots do {
	    x := horizontal(s)
	    y := vertical(s)
	    spot(Visualization, y, x)
	    }
	 hotspots := table()
	 depthbound := breadthbound := &null
	 return
	 }
      &mpress | &mdrag: { # middle button: set bound box break lines
	 if m := mouse(&y, &x) then {
	    row := vertical(m)
	    column := horizontal(m)
	    if \depthbound then {       # erase previous bounding box, if any
	       every spot(Visualization, depthbound, 1 to breadthbound)
	       every spot(Visualization, 1 to depthbound, breadthbound)
	       }
	    depthbound := row
	    breadthbound := column
	    #
	    # draw new bounding box
	    #
	    every x := 1 to breadthbound do {
	       if not who_is_at(depthbound, x, new) then
		  spot(Color("orange"), depthbound, x)
	       }
	    every y := 1 to depthbound - 1 do {
	       if not who_is_at(y, breadthbound, new) then
		  spot(Color("orange"), y, breadthbound)
	       }
	    }
	 }
      &lpress | &ldrag: { # left button: toggle single cell breakpoint
	 if m := mouse(&y, &x) then {
	    xbound := horizontal(m)
	    ybound := vertical(m)
	    if hotspots[m] === m then
	       clearspot(m)
	    else
	       setspot(m)
	    }
	 }
      &rpress | &rdrag: { # right button: report node at mouse location
	 if m := mouse(&y, &x) then {
	    column := horizontal(m)
	    row := vertical(m)
	    if p := who_is_at(row, column, new) then
	       WAttrib("windowlabel=Algae " || image(p.node))
	    }
	 }
      }
end

#
# who_is_at() - find the activation tree node at a given (row, column) location
#
procedure who_is_at(row, col, node)
   while node.row > 1 & \node.parent do
      node := node.parent
   return sub_who(row, col, node)		# search children
end

#
# sub_who() - recursive search for the tree node at (row, column)
#
procedure sub_who(row, column, p)
   local k
   if p.column === column & p.row === row then return p
   else {
      every k := !p.children do
	 if q := sub_who(row, column, k) then return q
      }
end

#
# computeCol() - determine the correct column for a new child of a node.
#
procedure computeCol(parent)
   local col, x, node
   node := parent
   while \node.row > 1 do	# find root
      node := \node.parent
   if node === parent then return parent.column
   if col := subcompute(node, parent.row + 1) then {
      return max(col, parent.column)
      }
   else return parent.column
end

#
# subcompute() - recursive search for the leftmost tree node at depth row
#
procedure subcompute(node, row)
   # check this level for correct depth
   if \node.row = row then return node.column + 1
   # search children from right to left
   return subcompute(node.children[*node.children to 1 by -1], row)
end

#
# Color(s) - return a binding of &window with foreground color s;
#  allocate at most one binding per color.
#
procedure Color(s)
  static t, magenta
  initial {
     magenta := Clone(&window, "fg=magenta") | stop("no magenta")
     t := table()
     /t[E_Fcall] := Clone(&window, "fg=red") | stop("no red")
     /t[E_Ocall] := Clone(&window, "fg=chocolate") | stop("no chocolate")
     /t[E_Snew] :=  Clone(&window, "fg=purple") | stop("no purple")
     }
  if *s > 1 then
     / t[s] := Clone(&window, "fg=" || s) | stop("no ",image(s))
  else
     / t[s] := magenta
  return t[s]
end

procedure max(x,y)
   if x < y then return y else return x
end
