#!/usr/bin/env python
# Copyright 2006 James Tauber and contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import sys
from types import StringType
import compiler
from compiler import ast
import os
import copy

# the standard location for builtins (e.g. pyjslib) can be
# over-ridden by changing this.  it defaults to sys.prefix
# so that on a system-wide install of pyjamas the builtins
# can be found in e.g. {sys.prefix}/share/pyjamas
#
# over-rides can be done by either explicitly modifying
# pyjs.prefix or by setting an environment variable, PYJSPREFIX.

prefix = sys.prefix

if os.environ.has_key('PYJSPREFIX'):
    prefix = os.environ['PYJSPREFIX']

# pyjs.path is the list of paths, just like sys.path, from which
# library modules will be searched for, for compile purposes.
# obviously we don't want to use sys.path because that would result
# in compiling standard python modules into javascript!

path = [os.path.abspath('')]

if os.environ.has_key('PYJSPATH'):
    for p in os.environ['PYJSPATH'].split(os.pathsep):
        p = os.path.abspath(p)
        if os.path.isdir(p):
            path.append(p)

# this is the python function used to wrap native javascript
NATIVE_JS_FUNC_NAME = "JS"

UU = ""

PYJSLIB_BUILTIN_FUNCTIONS=("map",
                           "filter",
                           "dir",
                           "getattr",
                           "setattr",
                           "hasattr",
                           "int",
                           "str",
                           "repr",
                           "range",
                           "len",
                           "hash",
                           "abs",
                           "ord",
                           "chr",
                           "enumerate",
                           "min",
                           "max",
                           "bool",
                           "isinstance")

PYJSLIB_BUILTIN_CLASSES=("BaseException",
                         "Exception",
                         "StandardError",
                         "AttributeError")

# XXX: this is a hack: these should be dealt with another way
# however, console is currently the only global name which is causing
# problems.
PYJS_GLOBAL_VARS=("console")

# This is taken from the django project.
# Escape every ASCII character with a value less than 32.
JS_ESCAPES = (
    ('\'', r'\x27'),
    ('"', r'\x22'),
    ('>', r'\x3E'),
    ('<', r'\x3C'),
    ('&', r'\x26'),
    (';', r'\x3B')
    ) + tuple([('%c' % z, '\\x%02X' % z) for z in range(32)])

def escapejs(value):
    """Hex encodes characters for use in JavaScript strings."""
    for bad, good in JS_ESCAPES:
        value = value.replace(bad, good)
    return value

def uuprefix(name, leave_alone=0):
    name = name.split(".")
    name = name[:leave_alone] + map(lambda x: "__%s" % x, name[leave_alone:])
    return '.'.join(name)

class Klass:

    klasses = {}

    def __init__(self, name, name_):
        self.name = name
        self.name_ = name_
        self.klasses[name] = self
        self.functions = set()

    def set_base(self, base_name):
        self.base = self.klasses.get(base_name)

    def add_function(self, function_name):
        self.functions.add(function_name)


class TranslationError(Exception):
    def __init__(self, message, node):
        self.message = "line %s:\n%s\n%s" % (node.lineno, message, node)

    def __str__(self):
        return self.message

def strip_py(name):
    if name[2:10] == 'pyjamas.':
        return "__"+name[10:]
    if name[2:10] == 'pyjamas_':
        return "__"+name[10:]
    if name[:8] == 'pyjamas.':
        return name[8:]
    return name

def gen_mod_import(parentName, importName, dynamic=1):
    #pyjs_ajax_eval("%(n)s.cache.js", null, true);
    return """
    pyjslib.import_module('%(p)s', '%(n)s', %(d)d, false);
    """ % ({'p': parentName, 'd': dynamic, 'n': importName})

class Translator:

    def __init__(self, mn, module_name, raw_module_name, src, debug, mod, output,
                 dynamic=0):

        if module_name:
            self.module_prefix = module_name + "."
        else:
            self.module_prefix = ""
        self.raw_module_name = raw_module_name
        src = src.replace("\r\n", "\n")
        src = src.replace("\n\r", "\n")
        src = src.replace("\r",   "\n")
        self.src = src.split("\n")
        self.debug = debug
        self.imported_modules = set()
        self.imported_js = set()
        self.top_level_functions = set()
        self.top_level_classes = set()
        self.top_level_vars = set()
        self.local_arg_stack = [[]]
        self.output = output
        self.imported_classes = {}
        self.method_imported_globals = set()
        self.method_self = None
        self.nextTupleAssignID = 1
        self.dynamic = dynamic

        if module_name.find(".") >= 0:
            vdec = ''
        else:
            vdec = 'var '
        print >>self.output, UU+"%s%s = function () {" % (vdec, module_name)

        print >>self.output, UU+"%s.__name__ = '%s';" % (raw_module_name, mn)

        if self.debug:
            haltException = self.module_prefix + "HaltException"
            print >>self.output, haltException + ' = function () {'
            print >>self.output, '  this.message = "Program Halted";'
            print >>self.output, '  this.name = "' + haltException + '";'
            print >>self.output, '}'
            print >>self.output, ''
            print >>self.output, haltException + ".prototype.toString = function()"
            print >>self.output, '{'
            print >>self.output, 'return this.name + ": \\"" + this.message + "\\"";'
            print >>self.output, '}'

            isHaltFunction = self.module_prefix + "IsHaltException"
            print >>self.output, """
    %s = function (s) {
      var suffix="HaltException";
      if (s.length < suffix.length) {
        //alert(s + " " + suffix);
        return false;
      } else {
        var ss = s.substring(s.length, (s.length - suffix.length));
        //alert(s + " " + suffix + " " + ss);
        return ss == suffix;
      }
    }
                """ % isHaltFunction
        for child in mod.node:
            if isinstance(child, ast.Function):
                self.top_level_functions.add(child.name)
            elif isinstance(child, ast.Class):
                self.top_level_classes.add(child.name)

        for child in mod.node:
            if isinstance(child, ast.Function):
                self._function(child, False)
            elif isinstance(child, ast.Class):
                self._class(child)
            elif isinstance(child, ast.Import):
                importName = child.names[0][0]
                if importName == '__pyjamas__': # special module to help make pyjamas modules loadable in the python interpreter
                    pass
                elif importName.endswith('.js'):
                   self.imported_js.add(importName)
                else:
                   self.add_imported_module(strip_py(importName))
            elif isinstance(child, ast.From):
                if child.modname == '__pyjamas__': # special module to help make pyjamas modules loadable in the python interpreter
                    pass
                else:
                    self.add_imported_module(child.modname)
                    self._from(child)
            elif isinstance(child, ast.Discard):
                self._discard(child, None)
            elif isinstance(child, ast.Assign):
                self._assign(child, None, True)
            elif isinstance(child, ast.AugAssign):
                self._augassign(child, None)
            elif isinstance(child, ast.If):
                self._if(child, None)
            elif isinstance(child, ast.For):
                self._for(child, None)
            elif isinstance(child, ast.While):
                self._while(child, None)
            elif isinstance(child, ast.Subscript):
                self._subscript_stmt(child, None)
            elif isinstance(child, ast.Global):
                self._global(child, None)
            elif isinstance(child, ast.Printnl):
               self._print(child, None)
            elif isinstance(child, ast.Print):
               self._print(child, None)
            elif isinstance(child, ast.TryExcept):
                self._tryExcept(child, None)
            elif isinstance(child, ast.Raise):
                self._raise(child, None)
            elif isinstance(child, ast.Stmt):
                self._stmt(child, None)
            else:
                raise TranslationError("unsupported type (in __init__)", child)

        # Initialize all classes for this module
        #print >> self.output, "__"+self.modpfx()+\
        #          "classes_initialize = function() {\n"
        #for className in self.top_level_classes:
        #    print >> self.output, "\t"+UU+self.modpfx()+"__"+className+"_initialize();"
        #print >> self.output, "};\n"

        print >> self.output, "return this;\n"
        print >> self.output, "}; /* end %s */ \n"  % module_name

    def add_local_arg(self, varname):
        local_vars = self.local_arg_stack[-1]
        if varname not in local_vars:
            local_vars.append(varname)

    def add_imported_module(self, importName):

        if importName in self.imported_modules:
            return
        self.imported_modules.add(importName)
        print >> self.output, gen_mod_import(self.raw_module_name,
                                             strip_py(importName),
                                             self.dynamic)

    def _default_args_handler(self, node, arg_names, current_klass,
                              output=None):
        if len(node.defaults):
            output = output or self.output
            default_pos = len(arg_names) - len(node.defaults)
            if arg_names and arg_names[0] == self.method_self:
                default_pos -= 1
            for default_node in node.defaults:
                if isinstance(default_node, ast.Const):
                    default_value = self._const(default_node)
                elif isinstance(default_node, ast.Name):
                    default_value = self._name(default_node, current_klass)
                elif isinstance(default_node, ast.UnarySub):
                    default_value = self._unarysub(default_node, current_klass)
                else:
                    raise TranslationError("unsupported type (in _method)", default_node)

                default_name = arg_names[default_pos]
                default_pos += 1
                print >> output, "    if (typeof %s == 'undefined') %s=%s;" % (default_name, default_name, default_value)

    def _varargs_handler(self, node, varargname, arg_names, current_klass):
        print >>self.output, "    var", varargname, '= new pyjslib.List([]);'
        print >>self.output, "    for(var __va_arg="+str(len(arg_names))+"; __va_arg < arguments.length; __va_arg++) {"
        print >>self.output, "        var __arg = arguments[__va_arg];"
        print >>self.output, "        "+varargname+".append(__arg);"
        print >>self.output, "    }"

    def _kwargs_parser(self, node, function_name, arg_names, current_klass):
        if len(node.defaults) or node.kwargs:
            default_pos = len(arg_names) - len(node.defaults)
            if arg_names and arg_names[0] == self.method_self:
                default_pos -= 1
            print >>self.output, function_name+'.parse_kwargs = function (', ", ".join(["__kwargs"]+arg_names), ") {"
            for default_node in node.defaults:
                default_value = self.expr(default_node, current_klass)
#                if isinstance(default_node, ast.Const):
#                    default_value = self._const(default_node)
#                elif isinstance(default_node, ast.Name):
#                    default_value = self._name(default_node)
#                elif isinstance(default_node, ast.UnarySub):
#                    default_value = self._unarysub(default_node, current_klass)
#                else:
#                    raise TranslationError("unsupported type (in _method)", default_node)

                default_name = arg_names[default_pos]
                print >>self.output, "    if (typeof %s == 'undefined')"%(default_name)
                print >>self.output, "        %s=__kwargs.%s;"% (default_name, default_name)
                default_pos += 1

            #self._default_args_handler(node, arg_names, current_klass)
            if node.kwargs: arg_names += ["pyjslib.Dict(__kwargs)"]
            print >>self.output, "    var __r = "+"".join(["[", ", ".join(arg_names), "]"])+";"
            if node.varargs:
                self._varargs_handler(node, "__args", arg_names, current_klass)
                print >>self.output, "    __r.push.apply(__r, __args.getArray())"
            print >>self.output, "    return __r;"
            print >>self.output, "};"

    def _function(self, node, local=False):
        if local:
            function_name = node.name
            self.add_local_arg(function_name)
        else:
            function_name = UU + self.modpfx() + node.name

        arg_names = list(node.argnames)
        normal_arg_names = list(arg_names)
        if node.kwargs: kwargname = normal_arg_names.pop()
        if node.varargs: varargname = normal_arg_names.pop()
        declared_arg_names = list(normal_arg_names)
        if node.kwargs: declared_arg_names.append(kwargname)

        function_args = "(" + ", ".join(declared_arg_names) + ")"
        print >>self.output, "%s = function%s {" % (function_name, function_args)
        self._default_args_handler(node, normal_arg_names, None)

        if node.varargs:
            self._varargs_handler(node, varargname, declared_arg_names, None)

        for child in node.code:
            self._stmt(child, None)

        # we need to return null always, so it is not undefined
        lastStmt = [p for p in node.code][-1]
        if not isinstance(lastStmt, ast.Return):
            if not self._isNativeFunc(lastStmt):
                print >>self.output, "    return null;"

        print >>self.output, "}"
        print >>self.output, "%s.__name__ = '%s';" % (function_name, node.name)
        print >>self.output, "\n"


        self._kwargs_parser(node, function_name, normal_arg_names, None)


    def _return(self, node, current_klass):
        expr = self.expr(node.value, current_klass)
        # in python a function call always returns None, so we do it
        # here too
        print >>self.output, "    return " + expr + ";"


    def _break(self, node, current_klass):
        print >>self.output, "    break;"


    def _continue(self, node, current_klass):
        print >>self.output, "    continue;"


    def _callfunc(self, v, current_klass):

        if isinstance(v.node, ast.Name):
            if v.node.name in self.top_level_functions:
                call_name = self.modpfx() + v.node.name
            elif v.node.name in self.top_level_classes:
                call_name = self.modpfx() + v.node.name
            elif self.imported_classes.has_key(v.node.name):
                call_name = self.imported_classes[v.node.name] + '.' + v.node.name
            elif v.node.name == "callable":
                call_name = "pyjslib.isFunction"
            elif v.node.name in PYJSLIB_BUILTIN_FUNCTIONS:
                call_name = 'pyjslib.' + v.node.name
            else:
                call_name = v.node.name
            call_args = []
        elif isinstance(v.node, ast.Getattr):
            attr_name = v.node.attrname

            if isinstance(v.node.expr, ast.Name):
                call_name = self._name2(v.node.expr, current_klass, attr_name)
                call_args = []
            elif isinstance(v.node.expr, ast.Getattr):
                call_name = self._getattr2(v.node.expr, current_klass, attr_name)
                call_args = []
            elif isinstance(v.node.expr, ast.CallFunc):
                call_name = self._callfunc(v.node.expr, current_klass) + "." + v.node.attrname
                call_args = []
            elif isinstance(v.node.expr, ast.Subscript):
                call_name = self._subscript(v.node.expr, current_klass) + "." + v.node.attrname
                call_args = []
            elif isinstance(v.node.expr, ast.Const):
                call_name = self.expr(v.node.expr, current_klass) + "." + v.node.attrname
                call_args = []
            else:
                raise TranslationError("unsupported type (in _callfunc)", v.node.expr)
        else:
            raise TranslationError("unsupported type (in _callfunc)", v.node)

        call_name = strip_py(call_name)

        kwargs = []
        for ch4 in v.args:
            if isinstance(ch4, ast.Keyword):
                kwarg = ch4.name + ":" + self.expr(ch4.expr, current_klass)
                kwargs.append(kwarg)
            else:
                arg = self.expr(ch4, current_klass)
                call_args.append(arg)

        if kwargs:
            try: call_this, method_name = call_name.rsplit(".", 1)
            except ValueError:
                # Must be a function call ...
                return ("pyjs_kwargs_function_call("+call_name+", "
                                  + "["+", ".join(['{' + ', '.join(kwargs) + '}']+call_args)+"]"
                                  + ")" )
            else:
                return ("pyjs_kwargs_method_call("+call_this+", '"+method_name+"', "
                                  + "["+", ".join(['{' + ', '.join(kwargs) + '}']+call_args)+"]"
                                  + ")")
        else:
            return call_name + "(" + ", ".join(call_args) + ")"

    def _print(self, node, current_klass):
        call_args = []
        for ch4 in node.nodes:
            arg = self.expr(ch4, current_klass)
            call_args.append(arg)

        print >>self.output, "pyjslib.printFunc([", ', '.join(call_args), "],", int(isinstance(node, ast.Printnl)), ");"

    def _tryExcept(self, node, current_klass):
        if len(node.handlers) != 1:
            raise TranslationError("except statements in this form are" +
                                   " not supported", node)

        expr = node.handlers[0][0]
        as_ = node.handlers[0][1]
        if as_:
            errName = as_.name
        else:
            errName = 'err'
        print >>self.output, "    try {"
        for stmt in node.body.nodes:
            self._stmt(stmt, current_klass)
        print >> self.output, "    } catch(%s) {" % errName
        if expr:
            print >> self.output, "   if((%(err)s === %(expr)s)||(pyjslib.isinstance(%(err)s, %(expr)s))){" % dict (err=errName, expr=self.expr(expr, current_klass))
        for stmt in node.handlers[0][2]:
            self._stmt(stmt, current_klass)
        if expr:
            print >> self.output, "}"
        if node.else_ != None:
            print >>self.output, "    } finally {"
            for stmt in node.else_:
                self._stmt(stmt, current_klass)
        print >>self.output, "    }"

    def _getattr(self, v, current_klass):
        attr_name = v.attrname
        if isinstance(v.expr, ast.Name):
            obj = self._name(v.expr, current_klass, return_none_for_module=True)
            if obj == None and v.expr.name in self.imported_modules:
                return v.expr.name+'.__'+attr_name+'.prototype.__class__'
            return obj + "." + attr_name
        elif isinstance(v.expr, ast.Getattr):
            return self._getattr(v.expr, current_klass) + "." + attr_name
        elif isinstance(v.expr, ast.Subscript):
            return self._subscript(v.expr, self.modpfx()) + "." + attr_name
        elif isinstance(v.expr, ast.CallFunc):
            return self._callfunc(v.expr, self.modpfx()) + "." + attr_name
        else:
            raise TranslationError("unsupported type (in _getattr)", v.expr)


    def modpfx(self):
        return strip_py(self.module_prefix)
        
    def _name(self, v, current_klass, top_level=False,
                                      return_none_for_module=False):

        if v.name == 'ilikesillynamesfornicedebugcode':
            print current_klass, repr(v), dir(v)
            print self.top_level_vars
            print self.top_level_functions
            print self.local_arg_stack

        local_var_names = None
        if len(self.local_arg_stack) > 0:
            local_var_names = self.local_arg_stack[-1]

        if v.name == "True":
            return "true"
        elif v.name == "False":
            return "false"
        elif v.name == "None":
            return "null"
        elif v.name == '__name__' and current_klass is None:
            return self.modpfx() + v.name
        elif v.name == self.method_self:
            return "this"
        elif v.name in self.top_level_functions:
            return UU+self.modpfx() + v.name
        elif v.name in self.method_imported_globals:
            return UU+self.modpfx() + v.name
        elif v.name in local_var_names:
            return v.name
        elif self.imported_classes.has_key(v.name):
            return UU+self.imported_classes[v.name] + '.__' + v.name + ".prototype.__class__"
        elif v.name in self.top_level_classes:
            return UU+self.modpfx() + "__" + v.name + ".prototype.__class__"
        elif v.name in self.imported_modules and return_none_for_module:
            return None
        elif v.name in PYJSLIB_BUILTIN_CLASSES:
            return "pyjslib.__" + v.name +  ".prototype.__class__"
        elif current_klass:
            if v.name not in local_var_names and \
               v.name not in self.top_level_vars and \
               v.name not in PYJS_GLOBAL_VARS and \
               v.name not in self.top_level_functions:

                cls_name = current_klass
                if hasattr(cls_name, "name"):
                    cls_name_ = cls_name.name_
                    cls_name = cls_name.name
                else:
                    cls_name_ = current_klass + "_" # XXX ???
                name = UU+cls_name_ + ".prototype.__class__." \
                                   + v.name
                if v.name == 'listener':
                    name = 'listener+' + name
                return name

        return v.name

    def _name2(self, v, current_klass, attr_name):
        obj = v.name

        if obj in self.method_imported_globals:
            call_name = UU+self.modpfx() + obj + "." + attr_name
        elif self.imported_classes.has_key(obj):
            #attr_str = ""
            #if attr_name != "__init__":
            attr_str = ".prototype.__class__." + attr_name
            call_name = UU+self.imported_classes[obj] + '.__' + obj + attr_str
        elif obj in self.imported_modules:
            call_name = obj + "." + attr_name
        elif obj[0] == obj[0].upper(): # XXX HACK ALERT
            call_name = UU + self.modpfx() + "__" + obj + ".prototype.__class__." + attr_name
        else:
            call_name = UU+self._name(v, current_klass) + "." + attr_name

        return call_name


    def _getattr2(self, v, current_klass, attr_name):
        if isinstance(v.expr, ast.Getattr):
            call_name = self._getattr2(v.expr, current_klass, v.attrname + "." + attr_name)
        elif isinstance(v.expr, ast.Name) and v.expr.name in self.imported_modules:
            if v.expr.name == 'pyjamas':
                call_name = v.attrname + "." + attr_name
            else:
                call_name = UU+v.expr.name + '.__' +v.attrname+".prototype.__class__."+attr_name
        else:
            obj = self.expr(v.expr, current_klass)
            call_name = obj + "." + v.attrname + "." + attr_name

        return call_name


    def _class(self, node):
        """
        Handle a class definition.

        In order to translate python semantics reasonably well, the following
        structure is used:

        A special object is created for the class, which inherits attributes
        from the superclass, or Object if there's no superclass.  This is the
        class object; the object which you refer to when specifying the
        class by name.  Static, class, and unbound methods are copied
        from the superclass object.

        A special constructor function is created with the same name as the
        class, which is used to create instances of that class.

        A javascript class (e.g. a function with a prototype attribute) is
        created which is the javascript class of created instances, and
        which inherits attributes from the class object. Bound methods are
        copied from the superclass into this class rather than inherited,
        because the class object contains unbound, class, and static methods
        that we don't necessarily want to inherit.

        The type of a method can now be determined by inspecting its
        static_method, unbound_method, class_method, or instance_method
        attribute; only one of these should be true.

        Much of this work is done in pyjs_extend, is pyjslib.py
        """
        class_name = self.modpfx() + uuprefix(node.name, 1)
        class_name_ = self.modpfx() + uuprefix(node.name)
        current_klass = Klass(class_name, class_name_)
        init_method = None
        for child in node.code:
            if isinstance(child, ast.Function):
                current_klass.add_function(child.name)
                if child.name == "__init__":
                    init_method = child


        if len(node.bases) == 0:
            base_class = "pyjslib.__Object"
        elif len(node.bases) == 1:
            if isinstance(node.bases[0], ast.Name):
                if self.imported_classes.has_key(node.bases[0].name):
                    base_class_ = self.imported_classes[node.bases[0].name] + '.__' + node.bases[0].name
                    base_class = self.imported_classes[node.bases[0].name] + '.' + node.bases[0].name
                else:
                    base_class_ = self.modpfx() + "__" + node.bases[0].name
                    base_class = self.modpfx() + node.bases[0].name
            elif isinstance(node.bases[0], ast.Getattr):
                # the bases are not in scope of the class so do not
                # pass our class to self._name
                base_class_ = self._name(node.bases[0].expr, None) + \
                             ".__" + node.bases[0].attrname
                base_class = self._name(node.bases[0].expr, None) + \
                             "." + node.bases[0].attrname
            else:
                raise TranslationError("unsupported type (in _class)", node.bases[0])

            current_klass.set_base(base_class)
        else:
            raise TranslationError("more than one base (in _class)", node)

        print >>self.output, UU+class_name_ + " = function () {"
        # call superconstructor
        #if base_class:
        #    print >>self.output, "    __" + base_class + ".call(this);"
        print >>self.output, "}"

        if not init_method:
            init_method = ast.Function([], "__init__", ["self"], [], 0, None, [])
            #self._method(init_method, current_klass, class_name)

        # Generate a function which constructs the object
        clsfunc = ast.Function([],
           node.name,
           init_method.argnames[1:],
           init_method.defaults,
           init_method.flags,
           None,
           [ast.Discard(ast.CallFunc(ast.Name("JS"), [ast.Const(
#            I attempted lazy initialization, but then you can't access static class members
#            "    if(!__"+base_class+".__was_initialized__)"+
#            "        __" + class_name + "_initialize();\n" +
            "    var instance = new " + UU + class_name_ + "();\n" +
            "    if(instance.__init__) instance.__init__.apply(instance, arguments);\n" +
            "    return instance;"
            )]))])

        self._function(clsfunc, False)
        print >>self.output, UU+class_name_ + "_initialize = function () {"
        print >>self.output, "    if("+UU+class_name_+".__was_initialized__) return;"
        print >>self.output, "    "+UU+class_name_+".__was_initialized__ = true;"
        cls_obj = UU+class_name_ + '.prototype.__class__'

        if class_name == "pyjslib.__Object":
            print >>self.output, "    "+cls_obj+" = {};"
        else:
            if base_class and base_class not in ("object", "pyjslib.__Object"):
                print >>self.output, "    if(!"+UU+base_class_+".__was_initialized__)"
                print >>self.output, "        "+UU+base_class_+"_initialize();"
                print >>self.output, "    pyjs_extend(" + UU+class_name_ + ", "+UU+base_class_+");"
            else:
                print >>self.output, "    pyjs_extend(" + UU+class_name_ + ", "+UU+"pyjslib.__Object);"

        print >>self.output, "    "+cls_obj+".__new__ = "+UU+class_name+";"
        print >>self.output, "    "+cls_obj+".__name__ = '"+UU+node.name+"';"

        for child in node.code:
            if isinstance(child, ast.Pass):
                pass
            elif isinstance(child, ast.Function):
                self._method(child, current_klass, class_name, class_name_)
            elif isinstance(child, ast.Assign):
                self.classattr(child, current_klass)
            elif isinstance(child, ast.Discard) and isinstance(child.expr, ast.Const):
                # Probably a docstring, turf it
                pass
            else:
                raise TranslationError("unsupported type (in _class)", child)
        print >>self.output, "}"

        print >> self.output, class_name_+"_initialize();"


    def classattr(self, node, current_klass):
        self._assign(node, current_klass, True)

    def _raise(self, node, current_klass):
        if node.expr2:
            raise TranslationError("More than one expression unsupported",
                                   node)
        print >> self.output, "throw (%s);" % self.expr(
            node.expr1, current_klass)

    def _method(self, node, current_klass, class_name, class_name_):
        # reset global var scope
        self.method_imported_globals = set()

        arg_names = list(node.argnames)

        classmethod = False
        staticmethod = False
        if node.decorators:
            for d in node.decorators:
                if d.name == "classmethod":
                    classmethod = True
                elif d.name == "staticmethod":
                    staticmethod = True

        if staticmethod:
            staticfunc = ast.Function([], class_name_+"."+node.name, node.argnames, node.defaults, node.flags, node.doc, node.code, node.lineno)
            self._function(staticfunc, True)
            print >>self.output, "    " + UU+class_name_ + ".prototype.__class__." + node.name + " = " + class_name_+"."+node.name+";";
            print >>self.output, "    " + UU+class_name_ + ".prototype.__class__." + node.name + ".static_method = true;";
            return
        else:
            if len(arg_names) == 0:
                raise TranslationError("methods must take an argument 'self' (in _method)", node)
            self.method_self = arg_names[0]

            #if not classmethod and arg_names[0] != "self":
            #    raise TranslationError("first arg not 'self' (in _method)", node)

        normal_arg_names = arg_names[1:]
        if node.kwargs: kwargname = normal_arg_names.pop()
        if node.varargs: varargname = normal_arg_names.pop()
        declared_arg_names = list(normal_arg_names)
        if node.kwargs: declared_arg_names.append(kwargname)

        function_args = "(" + ", ".join(declared_arg_names) + ")"

        if classmethod:
            fexpr = UU + class_name_ + ".prototype.__class__." + node.name
        else:
            fexpr = UU + class_name_ + ".prototype." + node.name
        print >>self.output, "    "+fexpr + " = function" + function_args + " {"

        # default arguments
        self._default_args_handler(node, normal_arg_names, current_klass)

        local_arg_names = normal_arg_names + declared_arg_names 

        if node.varargs:
            self._varargs_handler(node, varargname, declared_arg_names, current_klass)
            local_arg_names.append(varargname)


        # stack of local variable names for this function call
        self.local_arg_stack.append(local_arg_names)

        for child in node.code:
            self._stmt(child, current_klass)

        # remove the top local arg names
        self.local_arg_stack.pop()

        print >>self.output, "    };"

        self._kwargs_parser(node, fexpr, normal_arg_names, current_klass)

        if classmethod:
            # Have to create a version on the instances which automatically passes the
            # class as "self"
            altexpr = UU + class_name_ + ".prototype." + node.name
            print >>self.output, "    "+altexpr + " = function() {"
            print >>self.output, "        return " + fexpr + ".apply(this.__class__, arguments);"
            print >>self.output, "    };"
            print >>self.output, "    "+fexpr+".class_method = true;"
            print >>self.output, "    "+altexpr+".instance_method = true;"
        else:
            # For instance methods, we need an unbound version in the class object
            altexpr = UU + class_name_ + ".prototype.__class__." + node.name
            print >>self.output, "    "+altexpr + " = function() {"
            print >>self.output, "        return " + fexpr + ".call.apply("+fexpr+", arguments);"
            print >>self.output, "    };"
            print >>self.output, "    "+altexpr+".unbound_method = true;"
            print >>self.output, "    "+fexpr+".instance_method = true;"
            print >>self.output, "    "+altexpr+".__name__ = '%s';" % node.name

        print >>self.output, UU + class_name_ + ".prototype.%s.__name__ = '%s';" % \
                (node.name, node.name)

        if node.kwargs or len(node.defaults):
            print >>self.output, "    "+altexpr + ".parse_kwargs = " + fexpr + ".parse_kwargs;"

        self.method_self = None
        self.method_imported_globals = set()

    def _isNativeFunc(self, node):
        if isinstance(node, ast.Discard):
            if isinstance(node.expr, ast.CallFunc):
                if isinstance(node.expr.node, ast.Name) and \
                       node.expr.node.name == NATIVE_JS_FUNC_NAME:
                    return True
        return False

    def _stmt(self, node, current_klass):
        debugStmt = self.debug and not self._isNativeFunc(node)
        if debugStmt:
            print >>self.output, '  try {'

        if isinstance(node, ast.Return):
            self._return(node, current_klass)
        elif isinstance(node, ast.Break):
            self._break(node, current_klass)
        elif isinstance(node, ast.Continue):
            self._continue(node, current_klass)
        elif isinstance(node, ast.Assign):
            self._assign(node, current_klass)
        elif isinstance(node, ast.AugAssign):
            self._augassign(node, current_klass)
        elif isinstance(node, ast.Discard):
            self._discard(node, current_klass)
        elif isinstance(node, ast.If):
            self._if(node, current_klass)
        elif isinstance(node, ast.For):
            self._for(node, current_klass)
        elif isinstance(node, ast.While):
            self._while(node, current_klass)
        elif isinstance(node, ast.Subscript):
            self._subscript_stmt(node, current_klass)
        elif isinstance(node, ast.Global):
            self._global(node, current_klass)
        elif isinstance(node, ast.Pass):
            pass
        elif isinstance(node, ast.Function):
            self._function(node, True)
        elif isinstance(node, ast.Printnl):
           self._print(node, current_klass)
        elif isinstance(node, ast.Print):
           self._print(node, current_klass)
        elif isinstance(node, ast.TryExcept):
            self._tryExcept(node, current_klass)
        elif isinstance(node, ast.Raise):
            self._raise(node, current_klass)
        else:
            raise TranslationError("unsupported type (in _stmt)", node)

        if debugStmt:

            errMsg = "Error in " + self.get_line_trace(node)

            haltException = self.module_prefix + "HaltException"
            isHaltFunction = self.module_prefix + "IsHaltException"

            print >>self.output, '  } catch (err) {'
            print >>self.output, '      if (' + isHaltFunction + '(err.name)) {'
            print >>self.output, '          throw err;'
            print >>self.output, '      } else {'
            print >>self.output, '          st = sys.printstack();'
            print >>self.output, '          alert("' + errMsg + '"' \
                                                + '+"stack trace:\\n"' \
                                                + '+st+"\\n"' \
                                                + '+err.name+": "+err.message'\
                                                + ');'
            print >>self.output, '          debugger;'

            print >>self.output, '          throw new ' + self.module_prefix + "HaltException();"
            print >>self.output, '      }'
            print >>self.output, '  }'


    def get_line_trace(self, node):
        lineNum = "Unknown"
        srcLine = ""
        if hasattr(node, "lineno"):
            if node.lineno != None:
                lineNum = node.lineno
                srcLine = self.src[min(lineNum, len(self.src))-1]
                srcLine = srcLine.replace('\\', '\\\\')
                srcLine = srcLine.replace('"', '\\"')
                srcLine = srcLine.replace("'", "\\'")

        return self.raw_module_name + ".py, line " \
               + str(lineNum) + ":"\
               + "\\n" \
               + "    " + srcLine \
               + "\\n"

    def _augassign(self, node, current_klass):
        v = node.node
        if isinstance(v, ast.Getattr):
            lhs = self._getattr(v, current_klass)
        else:
            lhs = self._name(node.node, current_klass)
        op = node.op
        rhs = self.expr(node.expr, current_klass)
        print >>self.output, "    " + lhs + " " + op + " " + rhs + ";"


    def _assign(self, node, current_klass, top_level = False):
        if len(node.nodes) != 1:
            tempvar = '__temp'+str(node.lineno)
            tnode = ast.Assign([ast.AssName(tempvar, "OP_ASSIGN", node.lineno)], node.expr, node.lineno)
            self._assign(tnode, current_klass, top_level)
            for v in node.nodes:
               tnode2 = ast.Assign([v], ast.Name(tempvar, node.lineno), node.lineno)
               self._assign(tnode2, current_klass, top_level)
            return

        def _lhsFromAttr(v, current_klass):
            attr_name = v.attrname
            if isinstance(v.expr, ast.Name):
                obj = v.expr.name
                lhs = self._name(v.expr, current_klass) + "." + attr_name
            elif isinstance(v.expr, ast.Getattr):
                lhs = self._getattr(v, current_klass)
            elif isinstance(v.expr, ast.Subscript):
                lhs = self._subscript(v.expr, current_klass) + "." + attr_name
            else:
                raise TranslationError("unsupported type (in _assign)", v.expr)
            return lhs

        def _lhsFromName(v, top_level, current_klass):
            if top_level:
                if current_klass:
                    lhs = UU+current_klass.name_ + ".prototype.__class__." \
                               + v.name
                else:
                    self.top_level_vars.add(v.name)
                    vname = self.modpfx() + v.name
                    if not self.modpfx() and v.name not in\
                           self.method_imported_globals:
                        lhs = "var " + vname
                    else:
                        lhs = UU + vname
                    self.add_local_arg(v.name)
            else:
                if v.name in self.method_imported_globals:
                    lhs = self.modpfx() + v.name
                else:
                    lhs = "var " + v.name
                    self.add_local_arg(v.name)
            return lhs

        dbg = 0
        v = node.nodes[0]
        if isinstance(v, ast.AssAttr):
            lhs = _lhsFromAttr(v, current_klass)
            if v.flags == "OP_ASSIGN":
                op = "="
            else:
                raise TranslationError("unsupported flag (in _assign)", v)

        elif isinstance(v, ast.AssName):
            lhs = _lhsFromName(v, top_level, current_klass)
            if v.flags == "OP_ASSIGN":
                op = "="
            else:
                raise TranslationError("unsupported flag (in _assign)", v)
        elif isinstance(v, ast.Subscript):
            if v.flags == "OP_ASSIGN":
                obj = self.expr(v.expr, current_klass)
                if len(v.subs) != 1:
                    raise TranslationError("must have one sub (in _assign)", v)
                idx = self.expr(v.subs[0], current_klass)
                value = self.expr(node.expr, current_klass)
                print >>self.output, "    " + obj + ".__setitem__(" + idx + ", " + value + ");"
                return
            else:
                raise TranslationError("unsupported flag (in _assign)", v)
        elif isinstance(v, (ast.AssList, ast.AssTuple)):
            uniqueID = self.nextTupleAssignID
            self.nextTupleAssignID += 1
            tempName = "__tupleassign" + str(uniqueID) + "__"
            print >>self.output, "    var " + tempName + " = " + \
                                 self.expr(node.expr, current_klass) + ";"
            for index,child in enumerate(v.getChildNodes()):
                rhs = tempName + ".__getitem__(" + str(index) + ")"

                if isinstance(child, ast.AssAttr):
                    lhs = _lhsFromAttr(child, current_klass)
                elif isinstance(child, ast.AssName):
                    lhs = _lhsFromName(child, top_level, current_klass)
                elif isinstance(child, ast.Subscript):
                    if child.flags == "OP_ASSIGN":
                        obj = self.expr(child.expr, current_klass)
                        if len(child.subs) != 1:
                            raise TranslationError("must have one sub " +
                                                   "(in _assign)", child)
                        idx = self.expr(child.subs[0], current_klass)
                        value = self.expr(node.expr, current_klass)
                        print >>self.output, "    " + obj + ".__setitem__(" \
                                           + idx + ", " + rhs + ");"
                        continue
                print >>self.output, "    " + lhs + " = " + rhs + ";"
            return
        else:
            raise TranslationError("unsupported type (in _assign)", v)

        rhs = self.expr(node.expr, current_klass)
        if dbg:
            print "b", repr(node.expr), rhs
        print >>self.output, "    " + lhs + " " + op + " " + rhs + ";"


    def _discard(self, node, current_klass):
        
        if isinstance(node.expr, ast.CallFunc):
            debugStmt = self.debug and not self._isNativeFunc(node)
            if debugStmt and isinstance(node.expr.node, ast.Name) and \
               node.expr.node.name == 'import_wait':
               debugStmt = False
            if debugStmt:
                st = self.get_line_trace(node)
                print >>self.output, "sys.addstack('%s');\n" % st
            if isinstance(node.expr.node, ast.Name) and node.expr.node.name == NATIVE_JS_FUNC_NAME:
                if len(node.expr.args) != 1:
                    raise TranslationError("native javascript function %s must have one arg" % NATIVE_JS_FUNC_NAME, node.expr)
                if not isinstance(node.expr.args[0], ast.Const):
                    raise TranslationError("native javascript function %s must have constant arg" % NATIVE_JS_FUNC_NAME, node.expr)
                raw_js = node.expr.args[0].value
                print >>self.output, raw_js
            else:
                expr = self._callfunc(node.expr, current_klass)
                print >>self.output, "    " + expr + ";"

            if debugStmt:
                print >>self.output, "sys.popstack();\n"

        elif isinstance(node.expr, ast.Const):
            if node.expr.value is not None: # Empty statements generate ignore None
                print >>self.output, self._const(node.expr)
        else:
            raise TranslationError("unsupported type (in _discard)", node.expr)


    def _if(self, node, current_klass):
        for i in range(len(node.tests)):
            test, consequence = node.tests[i]
            if i == 0:
                keyword = "if"
            else:
                keyword = "else if"

            self._if_test(keyword, test, consequence, current_klass)

        if node.else_:
            keyword = "else"
            test = None
            consequence = node.else_

            self._if_test(keyword, test, consequence, current_klass)


    def _if_test(self, keyword, test, consequence, current_klass):
        if test:
            expr = self.expr(test, current_klass)

            print >>self.output, "    " + keyword + " (pyjslib.bool(" + expr + ")) {"
        else:
            print >>self.output, "    " + keyword + " {"

        if isinstance(consequence, ast.Stmt):
            for child in consequence.nodes:
                self._stmt(child, current_klass)
        else:
            raise TranslationError("unsupported type (in _if_test)", consequence)

        print >>self.output, "    }"


    def _from(self, node):
        for name in node.names:
            if node.modname == 'pyjamas':
                self.add_imported_module(name[0])
            elif node.modname[:8] == 'pyjamas.':
                self.imported_classes[name[0]] = node.modname[8:]
            else:
                self.imported_classes[name[0]] = node.modname


    def _compare(self, node, current_klass):
        lhs = self.expr(node.expr, current_klass)

        if len(node.ops) != 1:
            raise TranslationError("only one ops supported (in _compare)", node)

        op = node.ops[0][0]
        rhs_node = node.ops[0][1]
        rhs = self.expr(rhs_node, current_klass)

        if op == "in":
            return rhs + ".__contains__(" + lhs + ")"
        elif op == "not in":
            return "!" + rhs + ".__contains__(" + lhs + ")"
        elif op == "is":
            op = "==="
        elif op == "is not":
            op = "!=="

        return "(" + lhs + " " + op + " " + rhs + ")"


    def _not(self, node, current_klass):
        expr = self.expr(node.expr, current_klass)

        return "!(" + expr + ")"

    def _or(self, node, current_klass):
        expr = "("+(") || (".join([self.expr(child, current_klass) for child in node.nodes]))+')'
        return expr

    def _and(self, node, current_klass):
        expr = "("+(") && (".join([self.expr(child, current_klass) for child in node.nodes]))+")"
        return expr

    def _for(self, node, current_klass):
        assign_name = ""
        assign_tuple = ""

        # based on Bob Ippolito's Iteration in Javascript code
        if isinstance(node.assign, ast.AssName):
            assign_name = node.assign.name
            self.add_local_arg(assign_name)
            if node.assign.flags == "OP_ASSIGN":
                op = "="
        elif isinstance(node.assign, ast.AssTuple):
            op = "="
            i = 0
            for child in node.assign:
                child_name = child.name
                if assign_name == "":
                    assign_name = "temp_" + child_name
                self.add_local_arg(child_name)
                assign_tuple += """
                var %(child_name)s %(op)s %(assign_name)s.__getitem__(%(i)i);
                """ % locals()
                i += 1
        else:
            raise TranslationError("unsupported type (in _for)", node.assign)

        if isinstance(node.list, ast.Name):
            list_expr = self._name(node.list, current_klass)
        elif isinstance(node.list, ast.Getattr):
            list_expr = self._getattr(node.list, current_klass)
        elif isinstance(node.list, ast.CallFunc):
            list_expr = self._callfunc(node.list, current_klass)
        else:
            raise TranslationError("unsupported type (in _for)", node.list)

        lhs = "var " + assign_name
        iterator_name = "__" + assign_name

        print >>self.output, """
        var %(iterator_name)s = %(list_expr)s.__iter__();
        try {
            while (true) {
                %(lhs)s %(op)s %(iterator_name)s.next();
                %(assign_tuple)s
        """ % locals()
        for node in node.body.nodes:
            self._stmt(node, current_klass)
        print >>self.output, """
            }
        } catch (e) {
            if (e != StopIteration) {
                throw e;
            }
        }
        """ % locals()


    def _while(self, node, current_klass):
        test = self.expr(node.test, current_klass)
        print >>self.output, "    while (pyjslib.bool(" + test + ")) {"
        if isinstance(node.body, ast.Stmt):
            for child in node.body.nodes:
                self._stmt(child, current_klass)
        else:
            raise TranslationError("unsupported type (in _while)", node.body)
        print >>self.output, "    }"


    def _const(self, node):
        if isinstance(node.value, int):
            return str(node.value)
        elif isinstance(node.value, float):
            return str(node.value)
        elif isinstance(node.value, basestring):
            v = node.value
            if isinstance(node.value, unicode):
                v = v.encode('utf-8')
            return  "'%s'" % escapejs(v)
        elif node.value is None:
            return "null"
        else:
            raise TranslationError("unsupported type (in _const)", node)

    def _unaryadd(self, node, current_klass):
        return self.expr(node.expr, current_klass)

    def _unarysub(self, node, current_klass):
        return "-" + self.expr(node.expr, current_klass)

    def _add(self, node, current_klass):
        return self.expr(node.left, current_klass) + " + " + self.expr(node.right, current_klass)

    def _sub(self, node, current_klass):
        return self.expr(node.left, current_klass) + " - " + self.expr(node.right, current_klass)

    def _div(self, node, current_klass):
        return self.expr(node.left, current_klass) + " / " + self.expr(node.right, current_klass)

    def _mul(self, node, current_klass):
        return self.expr(node.left, current_klass) + " * " + self.expr(node.right, current_klass)

    def _mod(self, node, current_klass):
        if isinstance(node.left, ast.Const) and isinstance(node.left.value, StringType):
           self.imported_js.add("sprintf.js") # Include the sprintf functionality if it is used
           return "sprintf("+self.expr(node.left, current_klass) + ", " + self.expr(node.right, current_klass)+")"
        return self.expr(node.left, current_klass) + " % " + self.expr(node.right, current_klass)

    def _invert(self, node, current_klass):
        return "~" + self.expr(node.expr, current_klass)

    def _bitand(self, node, current_klass):
        return " & ".join([self.expr(child, current_klass) for child in node.nodes])

    def _bitshiftleft(self, node, current_klass):
        return self.expr(node.left, current_klass) + " << " + self.expr(node.right, current_klass)

    def _bitshiftright(self, node, current_klass):
        return self.expr(node.left, current_klass) + " >>> " + self.expr(node.right, current_klass)

    def _bitxor(self,node, current_klass):
        return " ^ ".join([self.expr(child, current_klass) for child in node.nodes])

    def _bitor(self, node, current_klass):
        return " | ".join([self.expr(child, current_klass) for child in node.nodes])

    def _subscript(self, node, current_klass):
        if node.flags == "OP_APPLY":
            if len(node.subs) == 1:
                return self.expr(node.expr, current_klass) + ".__getitem__(" + self.expr(node.subs[0], current_klass) + ")"
            else:
                raise TranslationError("must have one sub (in _subscript)", node)
        else:
            raise TranslationError("unsupported flag (in _subscript)", node)

    def _subscript_stmt(self, node, current_klass):
        if node.flags == "OP_DELETE":
            print >>self.output, "    " + self.expr(node.expr, current_klass) + ".__delitem__(" + self.expr(node.subs[0], current_klass) + ");"
        else:
            raise TranslationError("unsupported flag (in _subscript)", node)

    def _list(self, node, current_klass):
        return "new pyjslib.List([" + ", ".join([self.expr(x, current_klass) for x in node.nodes]) + "])"

    def _dict(self, node, current_klass):
        items = []
        for x in node.items:
            key = self.expr(x[0], current_klass)
            value = self.expr(x[1], current_klass)
            items.append("[" + key + ", " + value + "]")
        return "new pyjslib.Dict([" + ", ".join(items) + "])"

    def _tuple(self, node, current_klass):
        return "new pyjslib.Tuple([" + ", ".join([self.expr(x, current_klass) for x in node.nodes]) + "])"

    def _lambda(self, node, current_klass):
        if node.varargs:
            raise TranslationError("varargs are not supported in Lambdas", node)
        if node.kwargs:
            raise TranslationError("kwargs are not supported in Lambdas", node)
        res = cStringIO.StringIO()
        arg_names = list(node.argnames)
        function_args = ", ".join(arg_names)
        for child in node.getChildNodes():
            expr = self.expr(child, None)
        print >> res, "function (%s){" % function_args
        self._default_args_handler(node, arg_names, None,
                                   output=res)
        print >> res, 'return %s;}' % expr
        return res.getvalue()

    def _slice(self, node, current_klass):
        if node.flags == "OP_APPLY":
            lower = "null"
            upper = "null"
            if node.lower != None:
                lower = self.expr(node.lower, current_klass)
            if node.upper != None:
                upper = self.expr(node.upper, current_klass)
            return  "pyjslib.slice(" + self.expr(node.expr, current_klass) + ", " + lower + ", " + upper + ")"
        else:
            raise TranslationError("unsupported flag (in _slice)", node)

    def _global(self, node, current_klass):
        for name in node.names:
            self.method_imported_globals.add(name)

    def expr(self, node, current_klass):
        if isinstance(node, ast.Const):
            return self._const(node)
        # @@@ not sure if the parentheses should be here or in individual operator functions - JKT
        elif isinstance(node, ast.Mul):
            return " ( " + self._mul(node, current_klass) + " ) "
        elif isinstance(node, ast.Add):
            return " ( " + self._add(node, current_klass) + " ) "
        elif isinstance(node, ast.Sub):
            return " ( " + self._sub(node, current_klass) + " ) "
        elif isinstance(node, ast.Div):
            return " ( " + self._div(node, current_klass) + " ) "
        elif isinstance(node, ast.Mod):
            return self._mod(node, current_klass)
        elif isinstance(node, ast.UnaryAdd):
            return self._unaryadd(node, current_klass)
        elif isinstance(node, ast.UnarySub):
            return self._unarysub(node, current_klass)
        elif isinstance(node, ast.Not):
            return self._not(node, current_klass)
        elif isinstance(node, ast.Or):
            return self._or(node, current_klass)
        elif isinstance(node, ast.And):
            return self._and(node, current_klass)
        elif isinstance(node, ast.Invert):
            return self._invert(node, current_klass)
        elif isinstance(node, ast.Bitand):
            return "("+self._bitand(node, current_klass)+")"
        elif isinstance(node,ast.LeftShift):
            return self._bitshiftleft(node, current_klass)
        elif isinstance(node, ast.RightShift):
            return self._bitshiftright(node, current_klass)
        elif isinstance(node, ast.Bitxor):
            return "("+self._bitxor(node, current_klass)+")"
        elif isinstance(node, ast.Bitor):
            return "("+self._bitor(node, current_klass)+")"
        elif isinstance(node, ast.Compare):
            return self._compare(node, current_klass)
        elif isinstance(node, ast.CallFunc):
            return self._callfunc(node, current_klass)
        elif isinstance(node, ast.Name):
            return self._name(node, current_klass)
        elif isinstance(node, ast.Subscript):
            return self._subscript(node, current_klass)
        elif isinstance(node, ast.Getattr):
            return self._getattr(node, current_klass)
        elif isinstance(node, ast.List):
            return self._list(node, current_klass)
        elif isinstance(node, ast.Dict):
            return self._dict(node, current_klass)
        elif isinstance(node, ast.Tuple):
            return self._tuple(node, current_klass)
        elif isinstance(node, ast.Slice):
            return self._slice(node, current_klass)
        elif isinstance(node, ast.Lambda):
            return self._lambda(node, current_klass)
        else:
            raise TranslationError("unsupported type (in expr)", node)



import cStringIO

def translate(file_name, module_name, debug=False):
    f = file(file_name, "r")
    src = f.read()
    f.close()
    output = cStringIO.StringIO()
    mod = compiler.parseFile(file_name)
    t = Translator(module_name, module_name, module_name, src, debug, mod, output)
    return output.getvalue()


class PlatformParser:
    def __init__(self, platform_dir = ""):
        self.platform_dir = platform_dir
        self.parse_cache = {}
        self.platform = ""

    def setPlatform(self, platform):
        self.platform = platform

    def parseModule(self, module_name, file_name):

        importing = False
        if not self.parse_cache.has_key(file_name):
            importing = True
            mod = compiler.parseFile(file_name)
            self.parse_cache[file_name] = mod
        else:
            mod = self.parse_cache[file_name]

        override = False
        platform_file_name = self.generatePlatformFilename(file_name)
        if self.platform and os.path.isfile(platform_file_name):
            mod = copy.deepcopy(mod)
            mod_override = compiler.parseFile(platform_file_name)
            self.merge(mod, mod_override)
            override = True

        if override:
            print "Importing %s (Platform %s)" % (module_name, self.platform)
        elif importing:
            print "Importing %s" % (module_name)

        return mod, override

    def generatePlatformFilename(self, file_name):
        (module_name, extension) = os.path.splitext(os.path.basename(file_name))
        platform_file_name = module_name + self.platform + extension

        return os.path.join(os.path.dirname(file_name), self.platform_dir, platform_file_name)

    def merge(self, tree1, tree2):
        for child in tree2.node:
            if isinstance(child, ast.Function):
                self.replaceFunction(tree1, child.name, child)
            elif isinstance(child, ast.Class):
                self.replaceClassMethods(tree1, child.name, child)

        return tree1

    def replaceFunction(self, tree, function_name, function_node):
        # find function to replace
        for child in tree.node:
            if isinstance(child, ast.Function) and child.name == function_name:
                self.copyFunction(child, function_node)
                return
        raise TranslationError("function not found: " + function_name, function_node)

    def replaceClassMethods(self, tree, class_name, class_node):
        # find class to replace
        old_class_node = None
        for child in tree.node:
            if isinstance(child, ast.Class) and child.name == class_name:
                old_class_node = child
                break

        if not old_class_node:
            raise TranslationError("class not found: " + class_name, class_node)

        # replace methods
        for function_node in class_node.code:
            if isinstance(function_node, ast.Function):
                found = False
                for child in old_class_node.code:
                    if isinstance(child, ast.Function) and child.name == function_node.name:
                        found = True
                        self.copyFunction(child, function_node)
                        break

                if not found:
                    raise TranslationError("class method not found: " + class_name + "." + function_node.name, function_node)

    def copyFunction(self, target, source):
        target.code = source.code
        target.argnames = source.argnames
        target.defaults = source.defaults
        target.doc = source.doc # @@@ not sure we need to do this any more

def dotreplace(fname):
    path, ext = os.path.splitext(fname)
    return path.replace(".", "/") + ext

class AppTranslator:

    def __init__(self, library_dirs=[], parser=None, dynamic=False):
        self.extension = ".py"

        self.library_modules = []
        self.overrides = {}
        self.library_dirs = path + library_dirs
        self.dynamic = dynamic

        if not parser:
            self.parser = PlatformParser()
        else:
            self.parser = parser

        self.parser.dynamic = dynamic

    def findFile(self, file_name):
        if os.path.isfile(file_name):
            return file_name

        if file_name[:8] == 'pyjamas.': # strip off library name
            if file_name != "pyjamas.py":
                file_name = file_name[8:]
        for library_dir in self.library_dirs:
            file_name = dotreplace(file_name)
            full_file_name = os.path.join(
                os.path.abspath(os.path.dirname(__file__)), library_dir, file_name)
            if os.path.isfile(full_file_name):
                return full_file_name

            fnameinit, ext = os.path.splitext(file_name)
            fnameinit = fnameinit + "/__init__.py"

            full_file_name = os.path.join(
                os.path.abspath(os.path.dirname(__file__)), library_dir, fnameinit)
            if os.path.isfile(full_file_name):
                return full_file_name

        raise Exception("file not found: " + file_name)

    def _translate(self, module_name, is_app=True, debug=False,
                   imported_js=set()):
        if module_name not in self.library_modules:
            self.library_modules.append(module_name)

        file_name = self.findFile(module_name + self.extension)

        output = cStringIO.StringIO()

        f = file(file_name, "r")
        src = f.read()
        f.close()

        mod, override = self.parser.parseModule(module_name, file_name)
        if override:
            self.overrides[module_name] = "%s.%s" % \
                                          (self.parser.platform.lower(),
                                           module_name)
        if is_app:
            mn = '__main__'
        else:
            mn = module_name

        t = Translator(mn, module_name, module_name,
                       src, debug, mod, output, self.dynamic)
        module_str = output.getvalue()
        imported_js.update(set(t.imported_js))
        imported_modules_str = ""
        for module in t.imported_modules:
            if module not in self.library_modules:
                self.library_modules.append(module)
                #imported_js.update(set(t.imported_js))
                #imported_modules_str += self._translate(
                #    module, False, debug=debug, imported_js=imported_js)

        if module_name == 'pyjamas':
            return imported_modules_str
        return imported_modules_str + module_str


    def translate(self, module_name, is_app=True, debug=False,
                  library_modules=[]):
        app_code = cStringIO.StringIO()
        lib_code = cStringIO.StringIO()
        imported_js = set()
        self.library_modules = []
        self.overrides = {}
        for library in library_modules:
            if library.endswith(".js"):
                imported_js.add(library)
                continue
            self.library_modules.append(library)
            print 'Including LIB', library
            print >> lib_code, '\n//\n// BEGIN LIB '+library+'\n//\n'
            print >> lib_code, self._translate(
                library, False, debug=debug, imported_js=imported_js)

            print >> lib_code, "/* initialize static library */"
            print >> lib_code, "%s%s();\n" % (UU, library)

            print >> lib_code, '\n//\n// END LIB '+library+'\n//\n'
        print >> app_code, self._translate(
            module_name, is_app, debug=debug, imported_js=imported_js)
        for js in imported_js:
           path = self.findFile(js)
           if os.path.isfile(path):
              print 'Including JS', js
              print >> lib_code,  '\n//\n// BEGIN JS '+js+'\n//\n'
              print >> lib_code, file(path).read()
              print >> lib_code,  '\n//\n// END JS '+js+'\n//\n'
           else:
              print >>sys.stderr, 'Warning: Unable to find imported javascript:', js
        return lib_code.getvalue(), app_code.getvalue()

usage = """
  usage: %s file_name [module_name]
"""

def main():
    import sys
    if len(sys.argv)<2:
        print >> sys.stderr, usage % sys.argv[0]
        sys.exit(1)
    file_name = os.path.abspath(sys.argv[1])
    if not os.path.isfile(file_name):
        print >> sys.stderr, "File not found %s" % file_name
        sys.exit(1)
    if len(sys.argv) > 2:
        module_name = sys.argv[2]
    else:
        module_name = None
    print translate(file_name, module_name),

if __name__ == "__main__":
    main()

