#
# $Id: decode.icn,v 1.3 2010-05-26 00:58:50 jeffery Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package lang

import util

#
# Recreate an object from the encoded string.
#
procedure decode(s)
   local d
   d := Decode(s)
   return d.decode()
end

#
# This class is used to decode a string created by the encode process, and
# reproduce the object.
#
# Classes to be decoded must subclass ClassCoding, which allows specification
# of how to recreate the class instance.
#
class Decode : Object(
   tag_count,
   seen,
   buff
   )

   method decode_string(s)
      local buff
      static esc
      initial
         esc := ~'\\'

      buff := StringBuff()
      s ? {
         repeat {
            buff.add(::tab(::many(esc)))
            if ::pos(0) then
               return buff.get_string()
            ::move(1)
            buff.add(::char(::move(3)))
         }
      }
   end

   method line_in()
      local s
      buff ? {
         if s := ::tab(::upto('|')) then {
            ::move(1)
            buff := ::tab(0)
            return s
         }
      }
   end

   method decode_record()
      local rname, n, res, i
      (rname := line_in() &
       n := ::integer(line_in())) | fail
      res := ::proc(rname)()
      seen[tag_count +:= 1] := res
      every i := 1 to n do
         res[i] := decode() | fail

      return res
   end

   method decode()
      local i, key, p, n, rname, t, res, def, val

      t := line_in() | fail

      if i := ::integer(t) then
         return \seen[i]

      case t of {
         "null" :
            return

         "procedure" :
            return ::proc(line_in())

         "record" :
            return decode_record()

         "class" :
            return decode_class()

         "string" :
            return decode_string(line_in())

         "integer" :
            return ::integer(line_in())

         "real" :
            return ::real(line_in())

         "cset" :
            return ::cset(decode_string(line_in()))

         "list" : {
            n := ::integer(line_in()) | fail
            res := []
            seen[tag_count +:= 1] := res
            every 1 to n do
               ::put(res, decode()) | fail
            return res
         }

         "set" : {
            n := ::integer(line_in()) | fail
            res := ::set([])
            seen[tag_count +:= 1] := res
            every 1 to n do
               ::insert(res, decode()) | fail
            return res
         }

         "table" : {
            def := decode() | fail
            res := ::table(def)
            n := ::integer(line_in()) | fail
            seen[tag_count +:= 1] := res
            every 1 to n do {
               (key := decode() &
               val := decode()) | fail
               res[key] := val
            }
            return res
         }

         default :
            return []
      }
   end

   method decode_class()
      local res, cname, n, e, t, m, v, f, p

      cname := line_in() | fail

      #
      # Create an instance
      #
      p := ::proc(cname) | fail
      res := p()
      seen[tag_count +:= 1] := res

      res.decode_obj(self) | fail
      res.post_decode()

      return res
   end

   initially(s)
      tag_count := 0
      seen := ::table()
      buff := s
end
