#!/usr/bin/env python

# .
# A Python implementation that uses AST to turn Python into Javascript code. Hurray!

# TODO:
# Fix constructor names DONE
# Add stuff like xrange range map etc
# try..except
# Properly indent...maybe use a code beautifyer?
# Port small bit of python core lib? os, sys, string?
# use expando modifyer to do special stuff with __dict__
# vars must be declared!?
# escape strings

from compiler import ast
import compiler
import os
import string
import time
import sys

import unittest

pylib = """
function len(s) {
return s.length;
}

function dict() {
    var arr = new Array();
    for (var i=0;i<dict.arguments.length;i+=2) {
        arr[dict.arguments[i]] = dict.arguments[i+1]
    }
    arr.toString = function() {
      var result = "";
      for (var key in arr)
      {
        if (result.length) result += ", "
        result += String(key) + ": " + String(arr[key])
      }
      return "{" + result + "}";
    }
    return arr;
}
"""

global current_class,classes
current_class = ""
classes = []
uniqueid = 0

def getuniquename(base):
    global uniqueid
    uniqueid += 1
    tmpname = "__%s%d__" % (base, uniqueid)
    return tmpname

def process_assign(node):
    global uniqueid
    lval = node.nodes[0]
    if lval.__class__ == ast.AssTuple:
        tmpname = getuniquename("tupleassign")
        tuplefoldout = ["%s = %s[%d]" % (python2js(lvalitem), tmpname, item) for item, lvalitem in enumerate(lval.getChildNodes())]
        return tmpname + " = " + python2js(node.expr) + ";" + os.linesep + (";" + os.linesep).join(tuplefoldout) + ";" + os.linesep
    else:
        return python2js(lval) + " = " + python2js(node.expr) + ";" + os.linesep
            

def python2js(node):
    global current_class,classes
    jsbuf = ""
    # handle the node
    if node is None:
        return ""
    nodetype = node.__class__
    closechar = "" # TODO
    error = False
    parsechildren = True

    if hasattr(node,"doc") and node.doc != None:
        jsbuf = jsbuf + "/* %s */" % (node.doc) + os.linesep
        
    if nodetype == ast.Module:
        # define a module, who cares, should prolly split into a file but its tough isnt it
        #jsbuf = jsbuf + "package Blah {" + os.linesep
        #closechar = "}" + os.linesep
        pass
    elif nodetype == ast.Stmt:
        # a statement i dont think it matters
        #closechar = ";" + os.linesep
        pass
    elif nodetype == ast.Import:
        jsbuf = jsbuf + "import "
        for n in node.names: # maybe getChildren()?
            jsbuf = jsbuf + n[0] + ","
        jsbuf = jsbuf[:-1]
        closechar = ";" + os.linesep
    elif nodetype == ast.From:
        jsbuf = jsbuf + "import "
        for n in node.names:
            jsbuf = jsbuf + node.modname + "." + n[0] + ","
        jsbuf = jsbuf[:-1]
        closechar = ";" + os.linesep
    elif nodetype == ast.Printnl or nodetype == ast.Print:
        parsechildren = False
        jsbuf = jsbuf + "document.write( "
        printparts = ["String(" + python2js(childnode) + ")" for childnode in node.getChildNodes()]
        if nodetype == ast.Printnl:
          printparts.append('"\\n"')
        jsbuf += ", ' ', ".join(printparts)
        jsbuf += ");" + os.linesep
    elif nodetype == ast.Add:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + " + " + python2js(node.right)
    elif nodetype == ast.Const:
        if node.value.__class__ == str:
            jsbuf = jsbuf + "\"" + node.value.replace("\"","\\\"") + "\""
        else:
            jsbuf = jsbuf + str(node.value)
    elif nodetype == ast.Name or nodetype == ast.AssName:
        jsbuf = jsbuf + node.name
    elif nodetype == ast.AugAssign:
        parsechildren = False
        jsbuf += python2js(node.node) + " " + node.op + " " + python2js(node.expr) + ";" + os.linesep
    elif nodetype == ast.Assign:
        parsechildren = False
        #jsbuf = jsbuf + python2js(node.nodes[0]) + " = " + python2js(node.expr)
        #closechar = ";" + os.linesep
        jsbuf = jsbuf + process_assign(node)
    elif nodetype == ast.If:
        parsechildren = False
        jsbuf = jsbuf + "if (" + python2js(node.tests[0][0]) + ") {" + os.linesep + python2js(node.tests[0][1]) + "}"
        for test in node.tests[1:]:
            jsbuf = jsbuf + " else if (" + python2js(test[0]) + ") {" + os.linesep + python2js(test[1]) + "}"
        try:
            jsbuf = jsbuf + " else {" + os.linesep + python2js(node.else_) + "}"
        except:
            pass
        jsbuf = jsbuf + os.linesep
    elif nodetype == ast.Compare:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.expr)
        for op in node.ops:
            jsbuf = jsbuf + " " + op[0] + " " + python2js(op[1])
    elif nodetype == ast.Or or nodetype == ast.And:
        if nodetype == ast.Or:
            op = "||"
        else:
            op = "&&"
        parsechildren = False
        lst = []
        for n in node.nodes:
            lst.append(python2js(n))
        jsbuf = jsbuf + string.join(lst," " + op + " ")
    elif nodetype == ast.Not:
        parsechildren = False
        jsbuf = jsbuf + "! " + python2js(node.expr)
    elif nodetype == ast.Bitand or nodetype == ast.Bitor or nodetype == ast.Bitxor:
        parsechildren = False
        if nodetype == ast.Bitand:
            op = "&"
        elif nodetype == ast.Bitor:
            op = "|"
        elif nodetype == ast.Bitxor:
            op = "^"
        lst = []
        for n in node.nodes:
            lst.append(python2js(n))
        jsbuf = jsbuf + string.join(lst," " + op + " ")
    elif nodetype == ast.AssAttr:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.expr) + "." + node.attrname
    elif nodetype == ast.Break:
        jsbuf = jsbuf + "break"
        closechar = ";" + os.linesep
    elif nodetype == ast.CallFunc:
        parsechildren = False
        n = python2js(node.node)
        if n in classes:
            n = "new " + n
        jsbuf = jsbuf + n + "("
        lst = []
        for arg in node.args:
            lst.append(python2js(arg))
        jsbuf = jsbuf + string.join(lst,",")
        jsbuf = jsbuf + ")"
    elif nodetype == ast.Class:
        parsechildren = False
        if len(node.bases) > 1:
            print "Warning: only supports single inheritance"
        closechar = "}" + os.linesep
        current_class = node.name
        classes.append(node.name)
        jsbuf = jsbuf + "class " + node.name + " "
        if len(node.bases) > 0:
            lst = []
            for base in node.bases:
                lst.append(python2js(base))
            jsbuf = jsbuf + "extends " + string.join(lst,",") + " "
        jsbuf = jsbuf + "{" + os.linesep + python2js(node.code)
    elif nodetype == ast.Continue:
        closechar = ";" + os.linesep
        jsbuf = jsbuf + "continue"
    elif nodetype == ast.Dict:
        #print "I can't do dicts, how much does that suck :("
        parsechildren = False
        jsbuf = jsbuf + "dict( "
        for item in node.items:
            jsbuf = jsbuf + python2js(item[0]) + "," + python2js(item[1]) + ","
        jsbuf = jsbuf[:-1] + ")"
    elif nodetype == ast.Discard:
        parsechildren = False
        closechar = ";" + os.linesep
        jsbuf = jsbuf + python2js(node.expr)
    elif nodetype == ast.Exec:
        parsechildren = False
        closechar = ";" + os.linesep
        jsbuf = jsbuf + "eval('" + python2js(node.expr).replace("'","\'") + "')"
    elif nodetype == ast.For:
        parsechildren = False
        closechar = "}" + os.linesep
        tmpvar = getuniquename("foriter")
        listvar = getuniquename("forlist")
        jsbuf = jsbuf + listvar + " = " + python2js(node.list) + ";" + os.linesep
        jsbuf = jsbuf + "for (var " + tmpvar + " = 0; " + tmpvar + " < " + listvar + ".length; " + tmpvar + " += 1) {" + os.linesep
        jsbuf = jsbuf + python2js(node.assign) + " = " + listvar + "[" + tmpvar + "];" + os.linesep
        jsbuf = jsbuf + python2js(node.body)
    elif nodetype == ast.ListCompFor:
        parsechildren = False
        tmpvar = getuniquename("foriter")
        listvar = getuniquename("forlist")
        jsbuf = jsbuf + listvar + " = " + python2js(node.list) + ";"
        jsbuf = jsbuf + "for (var " + tmpvar + " = 0; " + tmpvar + " < " + listvar + ".length; " + tmpvar + " += 1) {"
        jsbuf = jsbuf + python2js(node.assign) + " = " + listvar + "[" + tmpvar + "];"
        # jsbuf = jsbuf + "for (var " + python2js(node.assign) + " in " + python2js(node.list) + ")\n{ "
    elif nodetype == ast.ListComp:
        parsechildren = False
        closechar = ""
        jsbuf = jsbuf + "function() { "
        tmpvar = getuniquename("listcomp")
        jsbuf += " var " + tmpvar + " = new Array(); "
        quals = []
        for qual in node.quals:
          quals.insert(0, qual)
        for qual in quals:
          jsbuf += python2js(qual)
          closechar += "} "
        closechar = tmpvar + ".push(" + python2js(node.expr) + "); " + closechar + " ; return " + tmpvar + "; }()"
    elif nodetype == ast.Function:
        parsechildren = False
        closechar = "}" + os.linesep
        if node.name == "__init__":
            if current_class != "":
                node.name = current_class
        if current_class != "":
            # we're in a class, assign the first param to this and remove it from arg list
            selfarg = node.argnames[0]
            del node.argnames[0]
        else:
            selfarg = None
            #print "Warning: method on line " + str(node.lineno) + " is a constructor. You must make a change in the JS source..."
        jsbuf = jsbuf + "function " + node.name + "(" + string.join(node.argnames,",") + ") {" + os.linesep
        if selfarg != None:
            jsbuf = jsbuf + selfarg + " = this;" + os.linesep
        jsbuf = jsbuf + python2js(node.code)
        #{" + pyt
    elif nodetype == ast.Getattr:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.expr) + "." + node.attrname
        #print "Getattr is not implemented yet. Perhaps we'll have a JScript object model that implements stuff like __dict__?"
    elif nodetype == ast.Global:
        parsechildren = False
        print "Global is not supported yet. Maybe stick at beginning of file?"
    elif nodetype == ast.List or nodetype == ast.Tuple:
        parsechildren = False
        jsbuf = jsbuf + "new Array("
        lst = []
        for n in node.nodes:
            lst.append(python2js(n))
        jsbuf = jsbuf + string.join(lst,",") + ")"
    elif nodetype == ast.Mod:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + " % " + python2js(node.right)
    elif nodetype == ast.LeftShift:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + "<<" + python2js(node.right)
    elif nodetype == ast.RightShift:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + ">>" + python2js(node.right)
    elif nodetype == ast.Mul:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + " * " + python2js(node.right)
    elif nodetype == ast.Pass:
        jsbuf = jsbuf + "//" + os.linesep
    elif nodetype == ast.Power:
        parsechildren = False
        jsbuf = jsbuf + "pow(" + python2js(node.left) + ", " + python2js(node.right) + ")"
    elif nodetype == ast.Raise:
        parsechildren = False
        print "Warning: raise only supports 1st arg right now"
        jsbuf = jsbuf + "throw " + python2js(node.expr1) + ";" + os.linesep
    elif nodetype == ast.Slice:
        parsechildren = False
        if node.lower == None:
            nodelower = 0
        else:
            nodelower = python2js(node.lower)
        jsbuf = jsbuf + python2js(node.expr) + ".substr(" + str(nodelower)
        if node.upper != None:
            jsbuf = jsbuf + "," + python2js(node.upper)
        jsbuf = jsbuf + ")"
    elif nodetype == ast.Sub:
        parsechildren = False
        jsbuf = jsbuf + python2js(node.left) + " - " + python2js(node.right)
    elif nodetype == ast.Subscript:
        parsechildren = False
        lst = []
        for sub in node.subs:
            lst.append(python2js(sub))
        jsbuf = jsbuf + python2js(node.expr) + "[" + string.join(lst,",") + "]"
    elif nodetype == ast.UnaryAdd:
        parsechildren = False
        jsbuf = jsbuf + "+" + python2js(node.expr)
    elif nodetype == ast.UnarySub:
        parsechildren = False
        jsbuf = jsbuf + "-" + python2js(node.expr)
    elif nodetype == ast.While:
        parsechildren = False
        closechar = "}" + os.linesep
        jsbuf = jsbuf + "while (" + python2js(node.test) + ") {" + os.linesep + python2js(node.body)
    elif nodetype == ast.Yield:
        print "Warning: using yield, it wont work right"
        parsechildren  = False
        closechar = ";" + os.linesep
        jsbuf = jsbuf + "return " + python2js(node.value)
    elif nodetype == ast.Return:
        parsechildren = False
        closechar = ";" + os.linesep
        jsbuf = jsbuf + "return " + python2js(node.value)
    elif nodetype == ast.Lambda:
        parsechildren = False
        closechar = ";};" + os.linesep
        jsbuf = jsbuf + "function(" + string.join(node.argnames,",") + ") {" + python2js(node.code)
    elif nodetype == ast.AssTuple or nodetype == ast.AssList:
        parsechildren = False
        # this is handled by process_assign
    else:
        error = True
    #elif nodetype == 

    if parsechildren:        
        for child in node.getChildNodes():
            jsbuf = jsbuf + python2js(child)

    if jsbuf != "":    
        jsbuf = jsbuf + closechar

    if nodetype == ast.Class:
        current_class = ""
    if error:
        error = "// Syntax error for " + nodetype.__name__ + ": " + repr(node) + " having " + ",".join(dir(node)) + os.linesep
        jsbuf += os.linesep + error + os.linesep
    return jsbuf

def compilefile(infile,outfile=""):
    jssrc = python2js(compiler.parseFile(infile))
    jssrc = pylib + jssrc
    if outfile:
        f = open(outfile,"w")
        f.write(jssrc)
        f.close()
    else:
        sys.stdout.write(jssrc)

if __name__=="__main__":
    compilefile(sys.argv[1])
    #unittest.main()


