############################################################################
#
#       File:     lsystem.icn
#
#	Subject:  Procedures for Lindenmayer systems support
#
#	Author:   Stephen B. Wampler
#
#	Date:     May 2, 2001
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#	Version:  1.0
#
############################################################################
#
#   Comments: This package is the collection of routines
#      developed to facilitate experiments with L-systems,
#      including the interpretation of strings as turtle
#      graphics commands.
#
#      Only rudimentary L-systems are currently implemented.
#	users are encouraged to extend this system.
#
############################################################################
#
#	Requires:  Version 9 graphics, co-expressions (for glib.icn)
#
############################################################################
#
#	Links: glib
#
############################################################################

link glib
record Lsys(order, dist, delta, axiom, rewrite)

# lsmap(s1,T) - replace, in s1, occurrences of character key values in T
#     with assigned value for that key.  (Suitable for l-system rules!)
#
procedure lsmap(s1,T)
   local s

   if type(T) ~== "table" then
      stop("lsmap() - second argument not a table!")

   s := ""
   s1 ? while s ||:= (\T[move(1)] | move(1))

   return s
end

# mk_map(L) - build a rewriting map table from list L
#
procedure mk_map(L)
   local a, t

   t := table()
   every a := !L do {
      t[a[1]] := a[2]
      }

   return t
end
      
# read_Lsystem(f) - read in an L system from a file...
#
#      Form for an L_system:
#
#	order: n
#	delta: angle
#	axiom: string
#	map:   c = string
#
procedure read_Lsystem(f)
   local ls, line, next_token

   ls := Lsys(0,10,90,"",table())

   while line := read(f) do {
      next_token := create gen_tokens(line)

      case map(@next_token) of {
         "order:": ls.order := integer(@next_token)
         "dist:" : ls.dist  := integer(@next_token)
         "delta:": ls.delta := numeric(@next_token)
	 "axiom:": ls.axiom := @next_token
	 "map:"  : ls.rewrite[@next_token] := (@next_token, @next_token)
         }
      }

   return ls
end


# write_Lsystem(ls) - display L-system ls (for debugging, mainly)
#
procedure  write_Lsystem(ls)
   write("L-system:")
   write("\torder: ",ls.order)
   write("\t dist: ",ls.dist)
   write("\tdelta: ",ls.delta)
   write("\taxiom: ",ls.axiom)
   every key := key(ls.rewrite) do
       write("\t  map: ",key," -> ",ls.rewrite[key])
   return
end


# build_cmd(ls) - return the command string for
#	l-system ls
#
procedure build_cmd(ls)
   local s

   s := ls.axiom
   every 1 to ls.order do
      s := lsmap(s, ls.rewrite)
   return s

end

# eval_cmd(s) - apply turtle t to command string
#
procedure eval_cmd(t,s,dist,delta)

   s ? while obey(t,move(1), dist, delta)

   return
end
         

# eval_lsys(t,ls,dist,delta) - apply turtle t directly to
#    an Lsystem avoids constructing full Lsystem string
#    at once (i.e. no need to call build_cmd).
#
procedure eval_lsys(t,ls)
   evaluate(t,ls.axiom, ls.rewrite, ls.order, ls.delta, ls.dist)
end

# evaluate(t,s, Ls_map, n, delta, dist) - recursive l-system evaluation
#   (avoids building entire command string)
procedure evaluate(t, s, Ls_map, n, delta, dist)

    if n = 0 then return eval_cmd(t,s,dist,delta)

    s ? while evaluate(t, lsmap(move(1), Ls_map), Ls_map, n-1, delta, dist)
    return
end

# obey(t, c, dist, delta) - execute the appropriate turtle command
#      using turtle t.   (INCOMPLETE) (this is where L-systems could
#      be greatly extended.)
procedure obey(t, c, dist, delta)

    case c of {
       "f" : Move_Forward(t, dist)
       "+" : Left(t, delta)
       "-" : Right(t, delta)
       default: Line_Forward(t, dist)
       }

   return
end

# get_tokens(s) - suspend the tokens in string s
#
procedure gen_tokens(s, ws)
   local nws
   
   /ws := ' \t'
   nws := ~ws

   s ? while tab(upto(nws)) do
          suspend tab(many(nws)) \ 1

end
