#! /usr/bin/tclsh8.0



package require Tcl 8
package provide Tre 2.1

# begin the teaRTE namespace
namespace eval ::teaRTE {
  variable finalflag 0

  # mod constants
  variable ABSTRACT 1
  variable FINAL 2
  variable INTERFACE 4
  variable NATIVE 8
  variable PRIVATE 16
  variable PROTECTED 32
  variable PUBLIC 64
  variable STATIC 128
  variable TRANSIENT 256
  variable DEPRECATED 512
    
  # array definitions for a nonTea class (from global scope)
  array set NonTeaClass {
    name ""
    package ""
    derived ""
    implements ""
    mods 0
  }
  array set NonTeaMethods {
    null ""
  }
  array set NonTeaFields {
    null ""
  }
  array set NonTeaImports {
    null ""
  }
  
  # array of packages who have been loaded already
  variable pkgloaded
  
  # the list of class paths to look for classes in
  variable classpaths
  
  # table of options
  variable options
  
  # set the defaults
  array set options {
    checksource 0
    deprecated 0
    nowarn 0
    verbose 0
    nowrite 0
    savedir ""
    runpot ""
    standalone 0
    compiler 0
  }
  
  # initialize the stand-alone RTE
  proc init { {teapath "/home/john/work/installed/tea-2.1/lib/tea"} } {
    initRTE "classpath $teapath standalone 1"
  }
  
  # initialize the RTE.
  # opts are the command line options that modify the RT behavior
  proc initRTE {opts} {
    variable classpaths
    variable options
    
    # save off the options
    array set options $opts
    
    # get the class paths
    if { [info exists options(classpath)] } {
      set classpaths [split $options(classpath) :]
    } 
    
    # append the classpaths with the env var TEAPATH
    global env 
    if { [info exists env(TEAPATH)] } {
      foreach path [split $env(TEAPATH) :] {
        lappend classpaths $path
      }
    } else {
      # tack on current dir
      lappend classpaths .
    }

    # if we're running a potfile, stick it on the head of the classpaths list
    if { $options(runpot) != "" } {
      set classpaths "$options(runpot) $classpaths"
    }
    
    # create the runtime namespaces
    #
    # this namespace holds class definitions and their methods code
    namespace eval ::teaclasses {}
    
    # this namespace holds instantiated objects
    namespace eval ::teaobjects {}
    
    # load in the zip file support
    # but first, it may already be loaded if this is tre_lib.tcl
    if { [info commands zip_compress] == "" } {
      if { [catch {uplevel #0 source /home/john/work/installed/tea-2.1/lib/tea/zip_support.tcl} rc] } {
        puts "Warning: can't load ZIP support; no support for TPOT archives"
      }
    }
    
    # special setup if we might run the compiler
    if { $options(checksource) || $options(compiler) } {
      # this namespace holds compiled classes that have not been loaded yet
      namespace eval ::teacompiled {}

      # load the RT compiler
      uplevel #0 source /home/john/work/installed/tea-2.1/lib/tea/tea_compiler.tcl
      
      # and the extra support for dynamic compiling
      uplevel #0 source /home/john/work/installed/tea-2.1/lib/tea/tea_compsupport.tcl
    }
    
    # start loading the core packages that are always in memory
    if { ! [loadClass tea.lang.Object] } {
      error "Fatal: can't find tea.lang.Object"
    }
  }
  
  
  # finds the given class in the file system, using classpaths.
  # this finds only precompiled (.t) classes.
  # returns the full path to the class file, or "" if not found
  proc findClass {classname} {
    variable classpaths
    variable options
    
    # first split out the package from the name
    set pkg ""; set class ""
    splitclass $classname pkg class

    if { $pkg != "" } {
      set pkgpath [eval file join [split $pkg .]]
    } else {
      set pkgpath "."
    }
    
    # now look in the classpaths for this guy
    foreach path $classpaths {
      # the path may actually be a zip file
      if { [file isfile $path] } {
        if { [zip_does_file_exist $path $pkgpath/$class.t] } {
          # we found it in the zip file!
          # returning a list will tell loadClass that it is in an archive
          return [list $path $pkgpath/$class.t $pkg $class]
        } else {
          continue
        }
      } elseif { [file exists $path/$pkgpath/$class.t] } {
        return $path/$pkgpath/$class.t
      }
    }
    
    # if we got here, we didn't find it. Return empty string
    return ""
  }
  
  # proc getClassRef
  # proc getImportsRef
  # create the methods that link up a variable to one of the class
  # definition arrays
  foreach arr {Class Imports} {
    proc get${arr}Ref {class arrref} "
      if { \$class == \"\" } {
        uplevel upvar 0 ::teaRTE::NonTea$arr \$arrref
      } else {
        if { \[namespace children ::teaclasses ::teaclasses::\${class}\] != {} } {
          uplevel upvar 0 ::teaclasses::\${class}::def::$arr \$arrref
        } else {
          uplevel upvar 0 ::teacompiled::\${class}::$arr \$arrref
        }
      }
    "
  }
  
  # Load a class into memory. classname must be fully qualified!
  # returns 1 if the class was or already is loaded successfully, 0 if not
  proc loadClass {classname} {
    variable options
    
    # first look if class is already loaded into memory
    if { [namespace children ::teaclasses ::teaclasses::$classname] != "" } {
      return 1
    }
    
    # we could have its definition compiled but not yet loaded
    if { $options(checksource) || $options(compiler) } {
      if { [namespace children ::teacompiled ::teacompiled::$classname] != "" } {
        return [loadFromNamespace ::teacompiled::$classname]
      }
    }
    
    set file ""
    set sfile ""
    
    # look for the class file
    set file [findClass $classname]

    # did we find it in an archive?
    if { $file != "" && [llength $file] > 1 } {
      return [loadFromTeaPot $file]
    }
    
    # do we want to look at the source file too?
    if { $options(checksource) } {
      set sfile [findClassSource $classname]
    }
    
    # there can be four cases here:
    # 1. t file found and tea file found
    #    compare file times and take latest one
    # 2. t file found and tea file not found
    #    source t file
    # 3. t file not found and tea file found
    #    compile tea file
    # 4. neither file found
    #    return 0
    
    if { $file != "" && $sfile != "" } {
      # compare file times
      if { [file mtime $sfile] > [file mtime $file] } {
        # we need to set the savedir to empty to we recompile in the
        # same dir as the source
        set orig_savedir $options(savedir)
        set options(savedir) ""
        set rc [compile $sfile $classname]
        set options(savedir) $orig_savedir
        return $rc
      } else {
        return [loadFromTFile $file $classname]
      }
    } elseif { $file != "" && $sfile == "" } {
      return [loadFromTFile $file $classname]
    } elseif { $file == "" && $sfile != "" } {
        set orig_savedir $options(savedir)
        set options(savedir) ""
        set rc [compile $sfile $classname]
        set options(savedir) $orig_savedir
        return $rc
    } else {
      return 0
    }
  }
  
  # this loads a class from the Tea Pot file (archive)
  proc loadFromTeaPot {arcinfo} {
    variable options
    variable pkgloaded
    
    set potfile [lindex $arcinfo 0]
    set subfile [lindex $arcinfo 1]
    set pkg [lindex $arcinfo 2]
    set class [lindex $arcinfo 3]

    # has this package ever been initialized?
    if { ! [info exists pkgloaded($pkg)] } {
      # look for the pkg.init file in the pot file
      set pkginit [file dirname $subfile]/$pkg.init
      if { [zip_does_file_exist $potfile $pkginit] } {
        set buf [zip_get_file_in_buf $potfile $pkginit]
        if { [catch {uplevel #0 $buf} rc] } {
          Throw tea.lang.ExceptionInInitializerError \
            "Error running package initializer for $pkg in pot: '$rc'"
        }
      }
      
      # set it to something so we don't do this code again
      set pkgloaded($pkg) {}
    }

    if { $options(verbose) } {
      puts "Loading $pkg.$class from $potfile"
    }

    # extract the file from the archive
    set buf [zip_get_file_in_buf $potfile $subfile]
    
    # now eval this guy
    uplevel #0 $buf
    
    return 1
  }
  
  # this loads a class from a .t file into memory.
  proc loadFromTFile {file class} {
    variable options
    variable pkgloaded
    
    # has this package ever been initialized?
    set pkg ""
    splitclass $class pkg {}
    if { ! [info exists pkgloaded($pkg)] } {
      # look for the pkg.init file
      if { [file exists [file dirname $file]/$pkg.init] } {
        if { [catch {uplevel #0 source [file dirname $file]/$pkg.init} rc] } {
          Throw tea.lang.ExceptionInInitializerError \
            "Error running package initializer for $pkg: '$rc'"
        }
      }
      
      # set it to something so we don't do this code again
      set pkgloaded($pkg) {}
    }
    
    if { $options(verbose) } {
      puts "Loading $class from $file"
    }

    if { [catch {source $file} rc] } {
      return 0
    } else {
      return 1
    }
  }
  
  # RTE service proc. Resolves a short name given just the this pointer.
  # this will handle the case where no match was found by attaching the
  # package of this to it.
  proc resolveNameFromThis {classname this} {
    # get this's classname
    set thisclass [getClassFromThis $this]
    
    # get the import table
    getImportsRef $thisclass imports
    
    # call the real function
    set longname [resolveName $classname imports]
    if { $longname == "" } {
      getClassRef $thisclass def
      set longname $def(package).$classname
    }
    
    return $longname
  }
  
  # this will, using the import table, resolve a shortname with a fully
  # qualified classname
  proc resolveName {classname tableref} {
    # for RT binding, the classname does not have to be fully qualified. The
    # classdef array of the caller has an import table we can try to resolve
    # short class names.
    if { [string first "." $classname] == -1 } {
      # possible short form (could be unnamed package)
      
      upvar $tableref imports
      
      if { [info exists imports($classname)] } {
        # do we have conflicting packages?
        if { [llength $imports($classname)] > 1 } {
          Throw tea.lang.CompileError "Simple classname '$classname' imported from these packages: $imports($classname)"
        } else {
          return $imports($classname)
        }
      } else {
        # couldn't find it, return empty string and let caller deal with it
        return ""
      }
    }
    
    return $classname
  }
  
  # support for the import command. This will find all the classes
  # possible under the given package. This paws through all the paths
  # in the classpaths list and finds all .t and .tea (since both are
  # supported) and updates the importtable.
  # this has been updated in tea-sarte to support zip archives
  proc resolveWildcard {importtableref pkg {lookatsrcs 1}} {
    upvar $importtableref importtable
    variable classpaths
    
    # convert the package name into a path
    if { $pkg != "" } {
      set pkgpath [eval file join [split $pkg .]]
    } else {
      set pkgpath ""
    }
    
    # now look in the classpaths for any classes under this package
    foreach path $classpaths {
      if { [file isdirectory $path/$pkgpath] } {
        if { $lookatsrcs } {
          set files [glob -nocomplain $path/$pkgpath/*.t $path/$pkgpath/*.tea]
        } else {
          set files [glob -nocomplain $path/$pkgpath/*.t]
        }
      } elseif { [file isfile $path] } {
        # it's a tea pot file (we don't want srcs)
        set files [zip_find_files $path $pkgpath/*.t]
      } else {
        continue
      }
        
      foreach class $files {
        # strip off the path and the extension
        set class [file root [file tail $class]]

        # see if it's already in the table
        if { [info exists importtable($class)] } {
          # are they different packages?
          if { $importtable($class) != "$pkg.$class" } {
            # what we'll do is note the fact that we have a conflict. It
            # will only culminate into an error if the user tries to use
            # this simple name
            lappend importtable($class) $pkg.$class
          } else {
            continue
          }
        }

        # put it in the import table
        set importtable($class) $pkg.$class
      }
    }
  }

  proc decodeTypes {parmtypes} {
    set buf ""
    set len [string len $parmtypes]
    for {set i 0} {$i < $len} {incr i} {
      set t [string index $parmtypes $i]
      if { $t == "O" || $t == "o" } {
        set objref ""
        incr i 2
        while { [set o [string index $parmtypes $i]] != "," } {
          append objref $o
          incr i
        }
        if { $t == "o" } {
          append objref &
        }
        lappend buf $objref
      } else {
        lappend buf [decodeType $t]
      }
    }
    
    return $buf
  }

  # create an encoding string using the following mapping:
  # tclstring = S
  # tclint = I
  # tclbool = B
  # tcldouble = D
  # tcllist = L
  # tclarray = A
  # void = V
  proc decodeType {type} {
    switch $type {
      S {return tclstring}
      s {return tclstring&}
      I {return tclint}
      i {return tclint&}
      B {return tclbool}
      b {return tclbool&}
      D {return tcldouble}
      d {return tcldouble&}
      L {return tcllist}
      l {return tcllist&}
      A {return tclarray}
      a {return tclarray&}
      V {return void}
    }
  }
  
  # looks for a field in the given class. Returns the name to field array
  # if found, "" otherwise.
  proc findField {class fieldname} {
    # look for the field in the topmost class
    if { [info vars ::teaclasses::${class}::def::f:$fieldname] != "" } {
      return ::teaclasses::${class}::def::f:$fieldname
    }
    
    # nope, we'll have to look in superclasses
    set super [set ::teaclasses::${class}::def::Class(derived)]
    while { $super != "" } {
      if { [info vars ::teaclasses::${super}::def::f:$fieldname] != "" } {
        return ::teaclasses::${super}::def::f:$fieldname
      } else {
        set super [set ::teaclasses::${super}::def::Class(derived)]
      }
    }
    
    # still haven't found it! Look in the interfaces
    foreach int [set ::teaclasses::${class}::def::Class(implements)] {
      # recursively call this proc on this interface
      set found [findField $int $fieldname]
      if { $found != "" } { return $found }
    }
    
    # not there
    return ""
  }
  
  # this returns the fully qualified array variable name that
  # is the method array for the given methodname. Returns ""
  # if the method is not found
  # the argcount variable must be called "ac" for the expression embedded
  # in the methods array to work
  proc findMethod {name ac class} {
    # get a list of matching methods
    set found ""
    foreach methodref [info vars ::teaclasses::${class}::def::m:${name}:*] {
      upvar 0 $methodref method
      if $method(argexpr) {
        if { $found != "" } {
          Throw tea.lang.AmbiguousMethodError \
            "Call to method $class.$name is ambiguous; more than one possibility"
        } else {
          set found $methodref
        }
      }
    }
    
    if { $found != "" } { return $found }
    
    # if we got here, we didn't find it, look in our parents array.
    # we need to do this because a superclass could be calling a non-
    # accessible method (from the child) through the $this pointer.
    set derived [set ::teaclasses::${class}::def::Class(derived)]
    if { $derived != "" } {
      # make sure it's loaded
      loadClass $derived
      set methodref [findMethod $name $ac $derived]

      return $methodref
    } else {
      # we're at the top, no method by that name. 
      return ""
    }
  }
  
  proc findMethodByType {nameandtypes class {searchstring ""}} {
    if { $searchstring == "" } {
      # pluck off the method name
      set name [lindex $nameandtypes 0]

      # get the types in encoded format
      set enc ""
      foreach type [lrange $nameandtypes 1 end] {
        append enc [encodeType $type]
      }
      
      set searchstring $name:$enc
    }
    
    # now see if our methods array has this
    set methodref [info vars ::teaclasses::${class}::def::m:$searchstring]
    if { $methodref != "" } {
      return $methodref
    }
    
    # if we got here, we didn't find it, look in our parents array
    set derived [set ::teaclasses::${class}::def::Class(derived)]
    if { $derived != "" } {
      # make sure it's loaded
      loadClass $derived
      set methodref [findMethodByType "" $derived $searchstring]

      return $methodref
    } else {
      # we're at the top, no method by that name. 
      return ""
    }
  }
  
  #
  # utility support routines
  #
  # split a fully qualified classname into its package and class name
  # components. 
  proc splitclass {full pkgref classref} {
    if { $pkgref != "" } {upvar $pkgref pkg}
    if { $classref != "" } {upvar $classref class}

    set i [string last . $full]
    if { $i == -1 } {
      # no package name, so it's an unnamed package
      set pkg ""
      set class $full
    } else {
      incr i -1
      set pkg [string range $full 0 $i]
      incr i +2
      set class [string range $full $i end]
    }
  }

  #
  # Runtime support
  #
  
  # a counter for creating unique object references
  variable objcounter 0

  # allocates an object of the given fully-qualified classname. Does
  # not invoke the constructor!
  # returns the reference to this object
  proc allocateObject {classname} {
    variable objcounter

    incr objcounter
    set new ::teaobjects::${classname}::$objcounter

    # create the namespace
    set ns ::teaclasses::$classname
    #namespace eval $new "namespace import -force ${ns}::*;upvar #0 ${ns}::_class_def_ _class_def_"
    namespace eval $new "upvar #0 ${ns}::_class_def_ _class_def_"

    # initialize variables
    ${ns}::__init_vars $new

    # create the access method
#    uplevel #0 "proc $new {method args} { return \[uplevel runmethod $new $classname \[list \$method\] \$args\] }"
    interp alias {} $new {} runmethod $new $classname 1

    return $new
  }
  
  namespace export new
  proc new { class args } {
    variable objcounter 
#puts "new $class $args"

    # get the id of the caller
    set caller [uplevel ::teaRTE::getCallerInfo]

    # caller may be specifying explicit types for the ctor
    # ie: set a [new {X tclint} 23]
    if { [llength $class] > 1 } {
      set explicittypes [lrange $class 1 end]
      set class [lindex $class 0]
    } else {
      set explicittypes ""
    }
    
    # resolve possible short classname
    getImportsRef $caller imports
    set fullclass [resolveName $class imports]
    
    if { $fullclass == "" } {
      # assume it's a package-friendly class of the same package
      getClassRef $caller callerdef
      set fullclass $callerdef(package).$class
    }
#puts "trying to new class '$fullclass'"

    # load the class
    if { ! [loadClass $fullclass] } {
      Throw tea.lang.ClassNotFoundException "Can't locate class '$class'"
    }
    
    # get this class definition
    getClassRef $fullclass def

    # is this class an abstract class?
    if { $def(mods) & $::teaRTE::ABSTRACT } {
      Throw tea.lang.InstantiationException \
        "Cannot instantiate abstract class '$fullclass'"
    }
    
    # see if the caller can even allocate one of these
    
    # call the RT security manager
    if { ! [SMaccessClass $caller $fullclass] } {
      Throw tea.lang.IllegalAccessException \
        "Cannot access '$fullclass' from '$caller'"
    }
    
    # everything's a go security-wise
    
    # allocate the object
    set new [allocateObject $fullclass]

    # call the ctor
    if { $explicittypes != "" } {
      uplevel runmethod $new $fullclass 1 [list [concat $fullclass $explicittypes]] $args
    } else {
      uplevel runmethod $new $fullclass 1 $fullclass $args
    }
    
    return $new
  }

  # proc to retrieve information about a caller. The caller better be one
  # level up, so you better use uplevel when calling this proc. This
  # returns the class name of the caller, or "" if global scope
  proc getCallerInfo {} {
    set ns [uplevel namespace current]
    
    if { [string match ::teaclasses::* $ns] } {
      return [namespace tail $ns]
    } elseif { [string match ::teaobjects::* $ns] } { 
      # need to support this in case we're calling things from inside 
      # the __init_var proc, which is run in the objects namespace 
      return [namespace tail [namespace qualifiers $ns]] 
    } else {
      return ""
    }
  }
  
  # delete an object
  # (this is going away, don't use it)
  proc delete { this } {
    # call the finalizer
    $this finalize

    # remove the accessor proc
    rename $this {}

    # delete the namespace
    namespace delete $this
  }

  
  namespace export final
  proc final {type args} {
    if { $type == "objref" } {
      # need to skip over the objref type
      uplevel ::teaRTE::_final [lrange $args 1 end]
    } elseif { $type != "tclarray" } {
      uplevel ::teaRTE::_final $args
    } else {
      uplevel ::teaRTE::_finalarr $args
    }
  }

  # generate all the primitive type handlers
  foreach {type value} {tclstring "" tclint 0 tcllist "" tclbool 0 tcldouble 0.0} {
    namespace export $type
    proc $type [list var [list value $value]] {
      uplevel set $var [list $value]
      #uplevel ::teaRTE::handle_primitive $var [list $value]
    }
  }

  # this one needs special attention
  namespace export tclarray
  proc tclarray {varname {value {}}} {
    variable finalflag
#     if { $finalflag } {
#       uplevel ::teaRTE::_finalarr $varname $value
#     } else {
      # if there is no value, set a dummy then unset it to force the
      # variable to be of array type
      if { $value == {} } {
        uplevel set $varname\(dummy) dummy
        uplevel unset $varname\(dummy)
      } else {
        # just do a normal set
        uplevel array set $varname [list $value]
      }
#     }
  }

  namespace export objref
  proc objref {class var {val "null"}} {
    # this is cheap for now...
    uplevel set $var $val
    #uplevel ::teaRTE::handle_primitive $var $val
  }

  # plucks off the classname from the this pointer (could be a static
  # this pointer, meaning that it points to the classes ns instead of
  # the objects ns)
  proc getClassFromThis {this} {
    if { [isATeaObject $this] } {
      return [namespace tail [namespace qualifiers $this]]
    } elseif { $this != "" } {
      return [namespace tail $this]
    } else {
      # global scope
      return ""
    }
  }
  
  # this is a simpler runtime binder. The optimizer has done most of the
  # work for us. He found out the mangled name of the method to run, he
  # found out if permission is allowed to access the method. He just didn't
  # know which method to run, so we'll look in the vmt to find it
  proc runmethod2 {this method args} {
    # check for null reference
    if { $this == "null" } {
      Throw tea.lang.NullPointerException \
        "accessing method '$method' from a null reference"
    }
    
    # see if we're calling a static
    if { ! [isATeaObject $this] } {
      # make sure the package and class are loaded
      set splitlist [split $this :]
      ::tea.lang::ClassLoader::loadClass1 ign [list [lindex $splitlist 2] [lindex $splitlist 4]]
    }
    
    array set vmt [set ${this}::_class_def_(vmt)]

    if { ! [info exists vmt($method)] } {
      Throw tea.lang.RuntimeError \
        "method '$method' does not exist in VMT"
    }

    set real $vmt($method)
    if { $real == "null" } {
      Throw tea.lang.AbstractMethodError \
        "Trying to run abstract method $method"
    }
    
    # the vmt only specifies the class
    append real ::$method
    
    return [uplevel $real $this $args]
  }
  
  # algorithm for running a method, which can involve
  # static methods, virtual methods, overloaded methods, etc:
  # 1. find out what class is calling the method. Could be "" if calling
  #    a static from outside an object
  # 2. find the real method that matches the argcount
  # 3. get the class that defined this real method
  # 4. check the privacy of the real method against the calling class
  # 5. if everything's ok, run it.
  # (this would be a lot quicker if implemented in C)
  #
  # this handles getting the real method name and checking protection
  namespace export runmethod
  proc runmethod {this forceclass dosecurity method args} {
    #puts "runmethod '$this' '$invokedas' '$method' '$args'"
    set staticflag 0
    set must_be_static 0

    # if we are not calling from a class method, then we can only
    # call statics
    if { ! [isATeaObject $this] } {
      # the this pointer is a class name
      set must_be_static 1
    }

    # get the caller info
    set caller [uplevel ::teaRTE::getCallerInfo]

    # forceclass is a way to implement $super method, to force execution of
    # the method on the given class, ignoring an overriding method in the
    # child
    if { $forceclass == "" } {
      set forceclass [getClassFromThis $this]
    }
    
    set realname [realmethod $forceclass $dosecurity $method [llength $args] $caller staticflag]
#puts "--> realname found is $realname"

    if { $must_be_static && $staticflag != 1 } {
      if { $caller == "" } {
        Throw tea.lang.NoSuchMethodException \
          "Can only call static methods from a class accessor"
      } else {
        Throw tea.lang.NoSuchMethodException \
          "Can only call static methods from $caller"
      }
    }

    # if we've made here, then we found the method and the caller
    # is allowed to call it.

    # run the actual method
    if { $staticflag } {
      if { ! [isATeaObject $this] } {
        set code [catch {uplevel $realname $this $args} rc]
      } else {
        set code [catch {uplevel $realname ::teaclasses::[getClassFromThis $this] $args} rc]
      }
    } else {
      set code [catch {uplevel $realname $this $args} rc]
    }
    
    return -code $code $rc
  }

  # this gets the real method name and verifies that the caller can call it
  proc realmethod {class dosecurity method argcount caller staticflagvar} {
    upvar $staticflagvar staticflag

    # caller could be explicitly specifying the parm types to get
    # around overloading ambiguities
    if { [llength $method] > 1 } {
      # get the method using the parm types
      set methodref [findMethodByType $method $class]
    } else {
      # get the method array from the owning class using the arg count
      set methodref [findMethod $method $argcount $class]
    }
    
    if { $methodref == "" } {
        Throw tea.lang.NoSuchMethodException \
          "method $class.$method with $argcount arguments does not exist"
    }

    upvar 0 $methodref methoddef
    
    # is this an abstract method?
    if { $methoddef(mods) & $::teaRTE::ABSTRACT } {
      Throw tea.lang.AbstractMethodError \
        "Trying to run abstract method $class.$method from '$caller'"
    }
    
    # the method owner may be different than the this pointer if the method
    # is actually implemented in a superclass
    
    if { $dosecurity } {
      # make sure caller can call this method
      if { ! [SMaccessMethod $caller methoddef] } {
        Throw tea.lang.IllegalAccessException \
          "Cannot access $class.$method with $argcount arguments from $caller"
      }
    }
    
    # need to determine if this method is static
    if { $methoddef(mods) & $::teaRTE::STATIC } {
      set staticflag 1
    } else {
      set staticflag 0
    }

    return ::teaclasses::$methoddef(owner)::$methoddef(fullname)
  }
  
  # in Java, the first line of a ctor MUST invoke it's superclass's
  # ctor (or implicitly by invoking another ctor via "this"), or else
  # the compiler (in this case the RTE) will insert a call to the super-
  # class default ctor automatically.
  #
  # This method ensures that this behavior occurs
  proc ctorfirstcmd {superclass {cmd ""} args} {
    upvar this this
    upvar super super

    # look at the first statement of the ctor
    # it might have explicit type suggestions
    if { [llength $cmd] > 1 } {
      if { [lindex $cmd 0] == $this } {
        # chain to a peer ctor
        #set classname [getClassFromThis $this]
        set classname [uplevel ::teaRTE::getCallerInfo]
        uplevel runmethod $this $classname 0 [list [concat $classname [lrange $cmd 1 end]]] $args
      } elseif { [lindex $cmd 0] == $super } {
        # call our super's ctor
        uplevel runmethod $this $superclass 0 [list [concat $superclass [lrange $cmd 1 end]]] $args
      } else {
        # better insert a call to the default super ctor
        if { $superclass != "" } {
          uplevel runmethod $this $superclass 0 $superclass
        }
        # now we can run whatever they had in mind
        uplevel [list $cmd] $args
      }
    } else {
      if { $cmd == $this } {
        # chain to peer ctor
        #set classname [getClassFromThis $this]
        set classname [uplevel ::teaRTE::getCallerInfo]
        uplevel runmethod $this $classname 0 $classname $args
      } elseif { $cmd == $super } {
        # calling the super's ctor
        uplevel runmethod $this $superclass 0 $superclass $args
      } else {
        # better insert a call to the default suprt ctor
        if { $superclass != "" } {
          uplevel runmethod $this $superclass 0 $superclass
        }
        uplevel $cmd $args
      }
    }
  }
  
  # proc for handling null references
  #
  namespace export null
  proc null {args} {
    Throw tea.lang.NullPointerException \
      "trying to call '$args' through a null reference"
  }

  proc isATeaObject {object} {
    return [string match ::teaobjects::* $object]
  }
    
  # try/throw/catch/finally functionality
  #
  namespace export try
  proc try {code args} {
    global errorCode errorInfo
    
    if { [set fi [lsearch -exact $args finally]] != -1 } {
      incr fi
      set finally_var $fi
      #trace variable finally_var u "::teaRTE::run_finally [info level]"
      trace variable finally_var u "eval [list [lindex $args $fi]] ;#"
    }
    
    # try running the code, while catching any errors
    set e [catch {uplevel $code} msg]
    #puts "e is '$e' and msg is '$msg' and errorCode is '$errorCode'"
    #set excobj $errorCode
    set excobj $msg
    set info $errorInfo
    switch -- $e {
      0 -
      1 {}
      default {
        # this is a return of continue or break, let it go through
        #return -code $e -errorcode $excobj -errorinfo $info $msg
        return -code $e -errorcode $errorCode -errorinfo $info $msg
      }
    }
      
    if { $e } {
      # is it one of our classes?
      if { ! [isATeaObject $excobj] } {
        # this might be a special case of an error thrown from a variable
        # trace. Tcl wraps our IllegalAccess exceptions with his own
        # message, so we need to look a little harder for the tea exception
        if { [set i [string last ::teaobjects $excobj]] != -1 } {
          set excobj [string range $excobj $i end]
        } else {
          # convert the tcl error to an exception
          # but check if this has been tried before
          if { [string first "Unable to throw this exception" $msg] == 0 } {
            # let it go, we can't do anything here
            error $msg
          }
          
          set excobj [new tea.lang.TclError $msg]
        }
      }
      
      # find a catch handler
      foreach {catchstring class_object code} $args {
        if { $catchstring != "catch" } {
          #return -code error -errorcode $excobj $msg
          return -code error -errorcode $errorCode $excobj
        }
        
        # resolve class name to long form
        set caller [uplevel ::teaRTE::getCallerInfo]
        if { $caller != "" } {
          getImportsRef $caller imports 
          set fullclass [resolveName [lindex $class_object 0] imports]
          if { $fullclass == "" } {
            getClassRef $caller def
            set fullclass $def(package).[lindex $class_object 0]
          } 
        } else {
            set fullclass [lindex $class_object 0]
        }
        
        # is this a match
        if { [$excobj instanceof $fullclass] } {
          uplevel set [lindex $class_object 1] $excobj
          uplevel $code
          # unset the exception object
          uplevel unset [lindex $class_object 1]
          return
        }
      }
      
      # if we got here, we never found it, so rethrow
      error $msg {} $errorCode
    }

    return
  }
  
  # this internal proc is to trap throws that fail because the internal core
  # is screwed up so bad that it cannot locate or compile an exception
  # class or his superclasses. This detects this occurrence and just
  # tosses a good ol' Tcl error.
  variable throwlock 0
  proc Throw {exc msg} {
    variable throwlock
    
    # are we calling this recursively?
    if { $throwlock != 0 } {
      error "Unable to throw this exception (check your setup): $exc '$msg'"
    } else {
      set throwlock 1
      set obj [new $exc $msg]
      set throwlock 0
      throw $obj
    }
  }
  
  namespace export throw
  proc throw {object} {
    # make sure it's a valid class
    if { ! [isATeaObject $object] } {
      set object [new tea.lang.ClassCastException "Object to throw is not a Tea class object"]
    }
    
    # make sure object is throwable
    if { ! [$object instanceof tea.lang.Throwable] } {
      Throw tea.lang.ClassCastException \
        "Object to throw is not an instance of Throwable"
    }
    
    #return -code error -errorcode $object "TEA EXCEPTION"
    #return -code error -errorcode "TEA EXCEPTION" $object
    error $object {} "TEA EXCEPTION"
  }
  
  proc _final {variable args} {
    upvar $variable var
    if { [llength $args] == 0 } {
      # support for a blank final
      set var ""
      trace variable var wr [list ::teaRTE::_finalblank]
    } else {
      set value [lindex $args 0]
      set var $value
      trace variable var w [list ::teaRTE::_finalmod $value]
    }
  }
  
  proc _finalmod {value name1 name2 op} {
    upvar $name1 name
    set name $value
    Throw tea.lang.IllegalAccessException "Trying to set final field $name1"
  }

  proc _finalblank {name1 name2 op} {
    upvar $name1 name
    if { $op == "r" } {
      # we're trying to read an unset blank final
      Throw tea.lang.RuntimeException \
        "Trying to read an uninitialized blank final '$name1'"
    } elseif { $op == "w" } {
      # we're setting a blank final for the first time
      # the variable has already been set, so let's use that as it's final value
      trace vdelete name wr [list ::teaRTE::_finalblank]
      trace variable name w [list ::teaRTE::_finalmod $name]
    }
  }
  
  proc _finalarr {array args} {
    upvar $array arr
    if { [llength $args] == 0 } {
      # support for a blank final
      # this is kind of a kludge. Just saying array set arr {} doesn't
      # create the array. So we do the following then remove the 
      # blank index when it is finally set
      set arr() {}
      trace variable arr wr [list ::teaRTE::_finalarrblank]
    } else {
      set values [lindex $args 0]
      array set arr $values
      trace variable arr w [list ::teaRTE::_finalmodarr $values]
    }
  }
  
  proc _finalmodarr {values name1 name2 op} {
    upvar $name1 name
    unset name
    array set name $values
    Throw tea.lang.IllegalAccessException \
      "Trying to set element in final array field $name1"
  }

  proc _finalarrblank {name1 name2 op} {
    upvar $name1 name
    if { $op == "r" } {
      # we're trying to read an unset blank final
      Throw tea.lang.RuntimeException \
        "Trying to read an uninitialized blank final array '$name1'"
    } elseif { $op == "w" } {
      # we're setting a blank final for the first time
      # the variable has already been set, so let's use that as it's final value
      trace vdelete name wr [list ::teaRTE::_finalarrblank]
      trace variable name w [list ::teaRTE::_finalmodarr [array get name]]
      # remove the placeholder index
      unset name()
    }
  }
  
  # returns 1 if source is an instance of target. Pass in class names
  proc isInstanceOf {source target} {
    # first check if the two classes are identical
    if { $source == $target } {
      return 1
    }
    
    # get the def
    getClassRef $source sourcedef
    
    if { $sourcedef(derived) == "" } {
      # tea.lang.Object is not an instance of anybody
      return 0
    }
    
    if { $sourcedef(derived) == $target } {
      return 1
    }
    
    if { [lsearch -exact $sourcedef(implements) $target] != -1 } {
      return 1
    }
    
    # still no match, start going up the inheritance tree
    set rc [isInstanceOf $sourcedef(derived) $target]
    if { ! $rc } {
      # go through the interfaces
      foreach int $sourcedef(implements) {
        if { [isInstanceOf $int $target] } {
          return 1
        }
      }
    }
    
    return $rc
  }

  # create an encoding string using the following mapping:
  # tclstring = S
  # tclint = I
  # tclbool = B
  # tcldouble = D
  # tcllist = L
  # tclarray = A
  # void = V
  # objref Xxx = O,Xxx,
  proc encodeType {type} {
    switch -glob $type {
      tclstring {return S}
      tclstring& {return s}
      tclint {return I}
      tclint& {return i}
      tclbool {return B}
      tclbool& {return b}
      tcldouble {return D}
      tcldouble& {return d}
      tcllist {return L}
      tcllist& {return l}
      tclarray {return A}
      tclarray& {return a}
      void {return V}
      *& {return o,$type,}
      default {return O,$type,}
    }
  }

  namespace export teafield
  proc teafield {this fieldname} {
    variable STATIC
    
    # get the caller
    set caller [uplevel ::teaRTE::getCallerInfo]
    
    # get the classname. If it's not a tea object, we may have to resolve
    # a short name
    if { ! [isATeaObject $this] } {
      set classname [resolveNameFromThis $this $caller]
       if { ! [loadClass $classname] } {
        Throw tea.lang.ClassNotFoundException.tea \
          "can't find class '$classname' while getting field '$fieldname'"
      }
   } else {
      set classname [getClassFromThis $this]
    }
    
    # get the field array for this field
    set fielddef [findField $classname $fieldname]
    if { $fielddef == "" } {
      Throw tea.lang.NoSuchFieldException \
        "no field '$fieldname' in '$classname'"
    } else {
      upvar 0 $fielddef field
    }
    
    # can the caller access this field?
    getClassRef $caller callerdef
    getClassRef $classname ownerdef
    if { ! [SMaccessField field callerdef ownerdef] } {
      Throw tea.lang.IllegalAccessException \
        "No access to field $classname.$fieldname"
    }
    
    # everything's a go, return the namespace version of the name
    if { $field(mods) & $STATIC } {
      return ::teaclasses::${classname}::${fieldname}
    } else {
      return ${this}::${fieldname}
    }
  }
  
  namespace export teaset
  proc teaset {this fieldname value} {
    set [uplevel teafield $this $fieldname] $value
  }
  
  namespace export teaget
  proc teaget {this fieldname} {
    return [set [uplevel teafield $this $fieldname]]
  }
  
  # this is for the global or Tcl scope to have his own importtable.
  namespace export import
  proc import {import} {
    variable NonTeaImports
    
    set pkg ""
    set class ""
    splitclass $import pkg class
    
    if { $class != "*" } {
      # does it already exist in the map?
      if { [info exists NonTeaImports($class)] } {
        # if they're the same, we can ignore it
        if { $NonTeaImports($class) == "$pkg.$class" } {
          return
        } else {
          error "conflicting class '$class' in import"
        }
      } else {
        set NonTeaImports($class) $pkg.$class
      }
    } else {
      # have the RT find all the possible classes in this package
      resolveWildcard NonTeaImports $pkg 0
    }
  }
  
  #
  # SECURITY MANAGEMENT
  #
  
  # checks if the caller class can access the target class for the given
  proc SMaccessClass {caller target} {
    getClassRef $target targetdef
    
    set pkg ""; set cl ""
    splitclass $caller pkg cl
    
    # if the target is a public class, then it's automatically ok
    if { $targetdef(mods) & $::teaRTE::PUBLIC } {
      return 1
    } else {
      # it's a package friendly class, so we'll need to compare packages
      return [expr {$targetdef(package) == $pkg}]
    }
  }
  
  # this checks to see if the caller can access the method in the target class
  proc SMaccessMethod {caller method&} {
    upvar ${method&} method
    
    set mods $method(mods)
    
    if { $mods & $::teaRTE::PUBLIC } {
      # public. nothing to check, permission is granted
      return 1
    } elseif { $mods & $::teaRTE::PRIVATE } {
      # Private. the caller and the callee better be the same
      return [expr {$caller == $method(owner)}]
    } elseif { $mods & $::teaRTE::PROTECTED } {
      # Protected. caller better be an instance of callee
      return [isInstanceOf $caller $method(owner)]
    } else {
      getClassRef $caller callerdef
      getClassRef $method(owner) targetdef
      # package-friendly, better have the same package
      return [expr {$targetdef(package) == $callerdef(package)}]
    }
  }
  
  # this checks if a child class can access a super's variable
  proc SMaccessField { field& child& super& } {
    upvar ${field&} field
    upvar ${child&} child
    upvar ${super&} super
    
    set mods $field(mods)
    
    if { $mods & $::teaRTE::PRIVATE } {
      return 0;
    }
    
    if { $mods & $::teaRTE::PUBLIC || 
         $mods & $::teaRTE::PROTECTED } {
      return 1
    }
    
    if { $super(package) == $child(package) } {
      return 1
    }
    
    return 0
  }
  
  proc SMcanOverrideMethod {childmethod& supermethod&} {
    variable PRIVATE
    variable PROTECTED
    variable PUBLIC
    variable STATIC
    variable FINAL
    variable DEPRECATED
    
    upvar ${childmethod&} childmethod
    set caller $childmethod(owner)

    upvar ${supermethod&} supermethod
    set super $supermethod(owner)
    
    set smods $supermethod(mods)
    set cmods $childmethod(mods)
    
    if { $smods & $FINAL } {
      return [list error \
        "$caller can't override final method '$childmethod(name)' defined in $super"]
    }
    
    if { $smods & $PRIVATE } {
      return [list error \
        "$caller can't override private method '$childmethod(name)' defined in $super"]
    }

    # a static cannot hide a non static
    if { ($cmods & $STATIC) && !($smods & $STATIC)  } {
      return [list error \
        "Static method '$caller' cannot hide non-static method '$childmethod(name)' defined in '$super'"]
    }

    # a nonstatic cannot hide a static
    if { !($cmods & $STATIC) && ($smods & $STATIC) } {
      return [list error \
        "Static method '$childmethod(name)' in '$caller' cannot hide non-static method defined in '$super'"]
    }

    # overriding method must provide at least as much access to overriden.
    # if overridee is public, overrider must also be public
    # if overridee is protected, overrider must be protected or public
    # if overridee is package-friendly, overrider must not be private
    set cm [expr {($PRIVATE | $PROTECTED | $PUBLIC) & $cmods}]
    set sm [expr {($PRIVATE | $PROTECTED | $PUBLIC) & $smods}]
    if { $sm == 0 } {
      if { $cm == $PRIVATE } {
        return [list error \
          "$caller.$childmethod(name) is giving less access to $super.$childmethod(name)"]
      }
    } elseif { $cm < $sm } {
      return [list error \
        "$caller.$childmethod(name) is giving less access to $super.$childmethod(name)"]
    }
    
    # are the return types the same?
    if { $supermethod(type) != $childmethod(type) } {
      return [list error \
        "$caller.$childmethod(name) has a different return type ($childmethod(type)) than $super.$childmethod(name) ($supermethod(type))"]
    }
    
    # are we overriding a deprecated method?
    if { $smods & $DEPRECATED } {
      # print out a warning
      return [list warning \
        "Method '$caller.$childmethod(name)' is overriding a deprecated method in class $super"]
    }
    
    # everything is okay
    return ""
  }
}

# now get all those exported tea commands
namespace import -force ::teaRTE::*

# redefine any existing unknown proc
catch {rename unknown original_unknown}

# and create our own to handle static method calls
proc unknown {cmd args} {
  # get our caller info
  if { [info level] > 1 } {
    set caller [uplevel ::teaRTE::getCallerInfo]
  } else {
    set caller ""
  }
  
  # cmd & args can be of two valid forms:
  # "classname staticmethod args"
  #   or
  # "{method type type...} args"
  
  if { [llength $cmd] > 1 } {
    upvar this this
    uplevel ::teaRTE::runmethod $this [::teaRTE::getClassFromThis $this] 1 [list $cmd] $args
  } else {
    # this could be an objref type for a method or field definition in
    # a class. Look at our stack and see if teaRTE::Compiler is in there
    if { [info level] > 1 && [lindex [info level -1] 0] == "::teaRTE::Compiler::compilefile" } {
      # if the "cmd" is the same as the current compiling class name, then it's
      # a ctor
      if { $cmd == $::teaRTE::Compiler::classname && [llength $args] == 2 } {
        return [uplevel ::teaRTE::Compiler::doconstructor $args]
      } else {
        # it's probably a method or field type
        return [uplevel ::teaRTE::Compiler::_generic_type_handler_ $cmd $args]
      }
    }
    
    # resolve the possible classname
    ::teaRTE::getImportsRef $caller imports
    set class [::teaRTE::resolveName $cmd imports]

    if { $class == "" } {
      # assume it's the same package as the caller
      ::teaRTE::getClassRef $caller def
      set class $def(package).$cmd
    }

    # try to load the class
    if { ! [::teaRTE::loadClass $class] } {
      return [uplevel original_unknown $cmd $args]
    }

    # try to run it
    set method [lindex $args 0]
    uplevel ::teaRTE::runmethod ::teaclasses::$class $class 1 [list $method] [lrange $args 1 end]
  }
}


namespace eval ::teaRTE {
  variable verboseopt ""

  proc zip_set_verbose {} {
    variable verboseopt
    set verboseopt -v
  }

  # adds file to an archive with compression
  proc zip_compress {zip args} {
    variable verboseopt
    eval exec /usr/bin/zip $verboseopt $zip $args
  }

  # stores files in an archive without compressing
  proc zip_store {zip args} {
    variable verboseopt
    eval exec /usr/bin/zip $verboseopt -0 $zip $args
  }

  proc zip_list {zip args} {
    variable verboseopt
    return [eval exec /usr/bin/unzip -l $verboseopt $zip $args]
  }

  proc zip_find_files {zip pattern} {
    set rclist ""
    catch {set files [exec /usr/bin/unzip -l -qq $zip $pattern]}
    foreach {dum dum dum file} $files {
      lappend rclist $file
    }
    
    return $rclist
  }
  
  proc zip_extract {zip args} {
    return [eval exec /usr/bin/unzip -o $zip $args]
  }

  proc zip_set_zipfile_comment {zip comment} {
    set f [open "|/usr/bin/zip -z -q $zip" w]
    puts $f $comment
    close $f
  }

  proc zip_get_zipfile_comment {zip} {
    set output [exec /usr/bin/unzip -zq $zip]
    #return [lrange [split $output "\n"] 1 end]
    return $output
  }

  proc zip_does_file_exist {zip file} {
    if { [catch {exec /usr/bin/unzip -l -qq $zip $file}] } {
      return 0
    } {
      return 1
    }
  }

  proc zip_get_file_in_buf {zip file} {
    set f [open "|/usr/bin/unzip -p $zip $file" r]
    set buf [read $f]
    close $f
    return $buf
  }
}


# the default classpath
set classpath .:/home/john/work/installed/tea-2.1/lib/tea

# if { [info exists env(TEAPATH)] } {
#   append classpath :$env(TEAPATH)
# }

set checksource 0
set nowarn 0
set verbose 0
# we default nowrite to 1 in the interpretter because it is not really
# a compiler, so it won't save anything it happens to have to compile
set nowrite 1
set pot 0

for {set argc 0} {[string match -* [lindex $argv $argc]] ||
                  [string match +* [lindex $argv $argc]] } {incr argc} {
  switch -exact -- [lindex $argv $argc] {
    -version -
    -V {
      puts "Tre v2.1"
      exit 0
    }
    
    -cp -
    -classpath {
      incr argc
      # override the default classpath
      set classpath [lindex $argv $argc]
    }
    
    +cp -
    +classpath {
      incr argc
      # augment the default classpath
      append classpath :[lindex $argv $argc]
    }
    
    -pot {
      set pot 1
    }
    
    -help {
      puts "Tre v2.1\n\nsyntax: tre \[options\] entity \[arguments\]\nOptions:
     -version | -V 
         Prints out the interpreter version and exits. 
          
     -verbose | -v 
         Print out verbose information when classes are loaded or 
         compiled.

     -classpath path 
         Sets up the list of paths to search for referenced classes. 
         path is a colon-separated list. Using this option will override 
         the default, so you must provide the path to the standard 
         package tea.lang. 
          
     +classpath path 
         Sets up the list of paths to search for referenced classes. 
         path is a colon-separated list. This option will append to 
         the default value. 
          
     -nowarn | -q 
         Do not print out warnings from compiler or runtime environment. 
          
     -pot
         Entity argument is a pot file. The actual class to extract and run
         has been set via the \"tpot m\" command. Not necessary if entity has
         the extension \".pot\".

     -help 
         Prints out brief help on options and exits.


  entity can be a fully-qualified class name, or a tea pot archive.
"
      exit 0
    }
    
    -verbose -
    -v {
      set verbose 1
    }

    -nowarn -
    -q { 
      set nowarn 1 
    }
    
    default {
      puts "Unknown option: [lindex $argv $argc]"
      exit 1
    }
  }
}

if { [file extension [lindex $argv $argc]] == ".pot" } {
  set pot 1
}

if { $pot } {
  set runpot [lindex $argv $argc]
  if { $runpot == "" } {
    if { $nowarn == 0 } {
      puts "No potfile specified"
    }
    exit 1
  }
} else {
  set runpot \{\}
}

::teaRTE::initRTE "checksource $checksource nowarn $nowarn classpath $classpath verbose $verbose nowrite $nowrite savedir {} runpot $runpot standalone 1"

if { $argc >= [llength $argv] } {
  while { ! [eof stdin] } {
    puts -nonewline "tre> "; flush stdout
    try {
      set input [gets stdin]
      while { ! [info complete $input] } {
        append input \n[gets stdin]
      }
      puts [eval $input]
    } catch {TclError exc} {
      puts "received uncaught Tcl Error"
      puts [$exc getMessage]
      $exc printTclStackTrace
    } catch {Throwable exc} {
      puts "received uncaught Tea exception of type: [$exc toString]"
      $exc printStackTrace
    }
  }
} else {
  # run the main method on the given class
  try {
    if { $pot } {
      # get the main class from the pot file
      eval [::teaRTE::zip_get_zipfile_comment $runpot]
      if { ! [info exists MainClass] } {
        puts "MainClass not set in potfile '$runpot'"
        exit 1
      } else {
        set class $MainClass
      }
    } else {
      set class [lindex $argv $argc]
    }
    
    incr argc
    # we have to eval because if there are no arguments, then lrange produces
    # {}, which when counted is an arg count of 1
    eval $class main [lrange $argv $argc end]
  } catch {tea.lang.TclError exc} {
    puts "received uncaught Tcl Error"
    puts [$exc getMessage]
    $exc printTclStackTrace
    exit 1
  } catch {tea.lang.Throwable exc} {
    puts "received uncaught Tea exception of type: [$exc toString]"
    $exc printStackTrace
    exit 1
  }
}


