/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.es.parser; import com.caucho.VersionFactory; import com.caucho.es.*; import com.caucho.java.LineMap; import com.caucho.util.CharBuffer; import com.caucho.util.IntMap; import com.caucho.vfs.Path; import com.caucho.vfs.WriteStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; class ParseClass { private String srcFilename; private LineMap lineMap; private Parser parser; // The full classname of the generated script. private String className; // The package name of the generated script. private String pkg; // The class name. private String name; private ESId proto; // the global parse class private ParseClass root; // class instance variables private HashMap variables = new HashMap(); // child classes private ArrayList classes = new ArrayList(); // class methods private ArrayList functions = new ArrayList(); private IntMap funMap = new IntMap(); private HashMap names = new HashMap(); private HashMap literals = new HashMap(); private HashMap mangledSet = new HashMap(); private Function global; private int unique; private int callDepth; private ArrayList imports = new ArrayList(); private ArrayList javaImports = new ArrayList(); private WriteStream os; private int printDepth; private boolean isFirst; private int destLine; private Path sourcePath; /** * Creates a new parse class. * * @param srcFilename the name of the source (*.js) * @param name the classname. */ ParseClass(String srcFilename, String name) { this.srcFilename = srcFilename; this.root = this; className = name; int p = name.lastIndexOf('.'); if (p >= 0) { pkg = name.substring(0, p); this.name = name.substring(p + 1); } else { this.name = name; } lineMap = new LineMap(name, srcFilename); lineMap.add(1, 1); destLine = 1; } /** * Sets the source path for searching import classes. */ void setSourcePath(Path sourcePath) { this.sourcePath = sourcePath; } /** * Gets the script search path */ Path getScriptPath() { return root.parser.getScriptPath(); } void setParser(Parser parser) { root.parser = parser; } void setWriteStream(WriteStream os) { this.os = os; } /** * Creates a child subclasses of this class. * * @param id the new class name. */ ParseClass newClass(ESId id) { ParseClass cl = new ParseClass(srcFilename, id.toString()); classes.add(cl); cl.root = root; return cl; } Function newFunction(Function oldFun, ESId id, boolean isClass) { if (id == null) id = ESId.intern("$lambda" + unique++); String realId = "_f_" + id.toString(); while (names.get(realId) != null) { realId = realId + unique++; } names.put(realId, realId); Function fun = new Function(this, oldFun, realId, id, isClass); setFunction(fun); return fun; } void setFunction(Function function) { int index; if ((index = funMap.get(function.name)) >= 0) { function.num = index; functions.set(index, function); } else { function.num = functions.size(); funMap.put(function.name, functions.size()); functions.add(function); } } void addImport(String importName) { if (! imports.contains(importName)) imports.add(importName); } /** * Adds a Java class/package import to the list. */ void addJavaImport(String importName) { if (! javaImports.contains(importName)) javaImports.add(importName); } /** * Gets a class variable. */ Variable getVariable(ESId id) { return (Variable) variables.get(id); } /** * Sets a new class variable. */ void addVariable(ESId id, Variable var) { variables.put(id, var); var.setJavaGlobal(true); } void setGlobal(Function global) { this.global = global; } void setProto(ESId proto) { this.proto = proto; } void addFunction(Function function) { setFunction(function); } void writeCode(WriteStream os) throws IOException { this.os = os; println("/**"); println(" * Generated by " + VersionFactory.getFullVersion()); println(" */"); println(); println("package " + pkg + ";"); println("import java.io.*;"); println("import com.caucho.es.*;"); println("import com.caucho.util.*;"); for (int i = 0; i < javaImports.size(); i++) println("import " + javaImports.get(i) + ";"); println("public class " + name + " extends com.caucho.es.Script {"); pushDepth(); writeExecute(); writeClassContents(); writeLastModified(); printLineMap(); popDepth(); println("}"); } /** * Writes the contents of the class. Actually generates two classes. * One is a straight Java class, the other is the wrapper. */ void writeClassContents() throws IOException { if (os == null) os = root.os; String name = this == root ? "js_global" : this.name; println(); println("public static class " + name + " {"); pushDepth(); println(name + "_es _js;"); Iterator iter = variables.values().iterator(); while (iter.hasNext()) { Variable var = (Variable) iter.next(); writeVarDecl(var, true); } for (int i = 0; i < functions.size(); i++) { Function fun = (Function) functions.get(i); println(); fun.writeCode(this); } popDepth(); println("}"); println(); if (name.equals("js_global")) println("public static class js_global_es extends ESGlobal {"); else println("public static class " + name + "_es extends ESClass {"); pushDepth(); println(name + " _js_object;"); println(); println(); if (name.equals("js_global")) { println("js_global_es(Global resin)"); println("{"); println(" super(resin);"); println(" resin.setGlobal(this);"); } else { println(name + "_es()"); println("{"); } println(" _js_object = new " + name + "();"); println(" _js_object._js = this;"); println("}"); writeMap(); printGetProperty(); printPropNames(); writeInit(); writeWrapperInit(name); writeStaticInit(); writeExport(); popDepth(); println("}"); for (int i = 0; i < classes.size(); i++) { ParseClass subClass = (ParseClass) classes.get(i); subClass.setWriteStream(os); subClass.writeClassContents(); } } void writeExport() throws IOException { println(); println("public void export(ESObject dst) throws Throwable"); println("{"); println(" ESBase tmp;"); for (int i = 0; global.functions != null && i < global.functions.size(); i++) { Function fun = (Function) global.functions.get(i); println(" tmp = getProperty(\"" + fun.id + "\");"); println(" dst.put(\"" + fun.id + "\", tmp, ESBase.DONT_ENUM);"); } for (int i = 0; i < classes.size(); i++) { ParseClass cl = (ParseClass) classes.get(i); Function fun = cl.getFunction(ESId.intern(cl.name)); println(" tmp = getProperty(\"" + cl.name + "\");"); println(" dst.put(\"" + cl.name + "\", tmp, ESBase.DONT_ENUM);"); } println("}"); } void writeExecute() throws IOException { println("public ESGlobal initClass(Global resin) throws Throwable"); println("{"); pushDepth(); println("resin.addScript(\"" + className + "\", this);"); println("js_global_es test = new js_global_es(resin);"); println("test._init(resin, test);"); println("return test;"); popDepth(); println("}"); } void writeWrapperInit(String name) throws IOException { } void writeVarDecl(Variable var, boolean init) throws IOException { if (var.getType() == Expr.TYPE_BOOLEAN) { print("boolean " + var.getId()); if (init) println(" = false;"); else println(";"); } else if (var.getType() == Expr.TYPE_INTEGER) { print("int " + var.getId()); if (init) println(" = 0;"); else println(";"); } else if (var.getType() == Expr.TYPE_NUMBER) { print("double " + var.getId()); if (init) println(" = Double.NaN;"); else println(";"); } else if (var.getType() == Expr.TYPE_STRING) { print("ESString " + var.getId()); if (init) println(" = ESString.create(\"\");"); else println(";"); } else if (var.getType() == Expr.TYPE_JAVA && var.getTypeExpr() != null) { TypeExpr type = (TypeExpr) var.getTypeExpr(); println(type.getTypeName() + " " + var.getId() + ";"); } else { if (init) println("ESBase " + var.getId() + " = ESBase.esUndefined;"); else println("ESBase " + var.getId() + ";"); } } void printId(ESId id) throws IOException { print("js_global_es."); print(getMangledLiteral(id)); } String getMangledLiteral(ESBase value) { String literal = (String) literals.get(value); if (literal == null) { literal = mangleLiteral(value); literals.put(value, literal); } return literal; } // XXX: hacks void pushCall() { callDepth++; } void popCall(int n) { callDepth -= n; } int getCallDepth() { return callDepth; } void printLiteral(ESBase obj) throws IOException { if (obj == null) print("ESBase.esNull"); else if (obj instanceof ESNull) print("ESBase.esNull"); else if (obj instanceof ESUndefined) print("ESBase.esUndefined"); else if (obj == ESBoolean.TRUE) print("ESBoolean.TRUE"); else if (obj == ESBoolean.FALSE) print("ESBoolean.FALSE"); else { String literal = (String) literals.get(obj); if (literal == null) { try { if (obj instanceof ESNumber && Double.isNaN(obj.toNum())) { print("ESNumber.NaN"); return; } } catch (Throwable e) { } literal = mangleLiteral(obj); literals.put(obj, literal); } print("js_global_es."); print(literal); } } private String mangleLiteral(Object obj) { String s = obj.toString(); CharBuffer cb = new CharBuffer("_l_"); for (int i = 0; i < s.length() && i < 32; i++) { char ch = s.charAt(i); // Java can't really deal with non-ascii? if (Character.isJavaIdentifierPart(ch) && ch >= ' ' && ch < 127) cb.append(ch); else if (cb.getLastChar() != '_') cb.append("_"); } if (mangledSet.get(cb) != null) cb.append("_" + unique++); mangledSet.put(cb, cb); return cb.toString(); } /** * Writes the method map. */ void writeMap() throws IOException { println("public ESBase call(int n, Call call, int length)"); println(" throws Throwable"); println("{"); println(" switch(n) {"); for (int i = 0; i < functions.size(); i++) { Function fun = (Function) functions.get(i); println(" case " + i + ":"); print(" return "); Expr expr = fun.getReturnType(); boolean hasCoerce = false; if (expr == null) { } else if (expr.getType() == Expr.TYPE_INTEGER || expr.getType() == Expr.TYPE_NUMBER) { hasCoerce = true; print("ESNumber.create("); } else if (expr instanceof JavaTypeExpr) { print("call.global.wrap("); } print("_js_object."); print(fun.name + "(call, length"); for (int j = 0; j < fun.getFormalSize(); j++) { Variable formal = fun.getFormal(j); if (formal.getTypeExpr() instanceof JavaTypeExpr) { TypeExpr type = (TypeExpr) formal.getTypeExpr(); print(", (" + type.getTypeName() + ") call.getArgObject(" + j + ", length)"); } else if (formal.getType() == Expr.TYPE_INTEGER) print(", call.getArgInt32(" + j + ", length)"); } print(")"); if (hasCoerce) print(")"); println(";"); } println(" default:"); println(" throw new RuntimeException();"); println(" }"); println("}"); } void printGetProperty() throws IOException { if (variables.size() == 0) return; println(); println("public ESBase getProperty(ESString key) throws Throwable"); println("{"); pushDepth(); println("switch (propNames.get(key)) {"); Iterator iter = variables.values().iterator(); int i = 0; while (iter.hasNext()) { Variable var = (Variable) iter.next(); println("case " + i + ":"); Class javaClass = var.getTypeExpr().getJavaClass(); if (ESBase.class.isAssignableFrom(javaClass)) println(" return _js_object." + var.getId() + ";"); else println(" return wrap(_js_object." + var.getId() + ");"); i++; } println("default:"); println(" return super.getProperty(key);"); println("}"); popDepth(); println("}"); } void printSetProperty() throws IOException { if (variables.size() == 0) return; println(); println("public void setProperty(ESString key, ESBase value) throws Throwable"); println("{"); pushDepth(); println("switch (propNames.get(key)) {"); Iterator iter = variables.values().iterator(); int i = 0; while (iter.hasNext()) { Variable var = (Variable) iter.next(); println("case " + i + ":"); Class javaClass = var.getTypeExpr().getJavaClass(); if (ESBase.class.isAssignableFrom(javaClass)) println(" _js_object." + var.getId() + " = (" + javaClass.getName() + ") value.toJavaObject();"); else println(" _js_object." + var.getId() + " = value;"); println(" break;"); i++; } println("default:"); println(" return super.setProperty(key, value);"); println("}"); popDepth(); println("}"); } void printPropNames() throws IOException { if (variables.size() == 0) return; println(); println("private static com.caucho.util.IntMap propNames;"); println(); println("static {"); pushDepth(); println("propNames = new com.caucho.util.IntMap();"); Iterator iter = variables.values().iterator(); int i = 0; while (iter.hasNext()) { Variable var = (Variable) iter.next(); println("propNames.put(ESId.intern(\"" + var.getId() + "\"), " + i + ");"); i++; } popDepth(); println("}"); } void writeInit() throws IOException { println(); println("public void _init(Global resin, ESObject global) throws Throwable"); println("{"); pushDepth(); for (int i = 0; i < imports.size(); i++) { String importName = (String) imports.get(i); print("resin.importScript(this, \""); printString(importName); println("\");"); } println("ESClosure fun;"); for (int i = 0; global.functions != null && i < global.functions.size(); i++) { Function fun = (Function) global.functions.get(i); print("fun = new ESClosure("); print(getMangledLiteral(fun.id)); print(", this, null, " + fun.num + ", "); if (fun.getFormalSize() == 0) print("_a_null"); else print("_a_" + fun.num); println(", global);"); println("global.put(\"" + fun.id + "\", fun, ESBase.DONT_ENUM);"); } println("ESObject protoProto;"); println("ESObject proto;"); for (int i = 0; i < classes.size(); i++) { ParseClass cl = (ParseClass) classes.get(i); Function fun = cl.getFunction(ESId.intern(cl.name)); println("{"); pushDepth(); println(cl.name + "_es jsClass;"); println("jsClass = new " + cl.name + "_es();"); if (cl.proto != null) { print("proto = new ESObject(\"Object\", getProperty("); print(getMangledLiteral(cl.proto)); println(").getProperty("); printLiteral(ESId.intern("prototype")); println("));"); } else println("proto = resin.createObject();"); println("jsClass._init(resin, proto);"); print("fun = new ESClosure("); print(getMangledLiteral(ESId.intern(cl.name))); print(", jsClass, proto, " + fun.num + ", "); if (fun.getFormalSize() == 0) print("_a_null"); else print("_a_" + (i + functions.size())); println(", null);"); println("setProperty(\"" + cl.name + "\", fun);"); popDepth(); println("}"); } popDepth(); println("}"); } void writeLastModified() throws IOException { println(); println("public boolean isModified()"); println("{"); if (sourcePath == null || sourcePath.getLastModified() <= 0) println(" return false;"); else if (getScriptPath().lookup(sourcePath.getUserPath()).exists()) { print(" return scriptPath.lookup(\""); printString(sourcePath.getUserPath()); println("\").getLastModified() != " + sourcePath.getLastModified() + "L;"); } else { print(" return com.caucho.vfs.Vfs.lookup(\""); printString(sourcePath.getFullPath()); println("\").getLastModified() != " + sourcePath.getLastModified() + "L;"); } println("}"); } Function getFunction(ESId name) { for (int i = 0; i < functions.size(); i++) { Function fun = (Function) functions.get(i); if (fun.id == name) return fun; } return null; } boolean hasFunction(ArrayList functions, ESId var) { for (int i = 2; i < functions.size(); i++) { Function fun = (Function) functions.get(i); if (fun.id == var) return true; } return false; } void writeStaticInit() throws IOException { Iterator iter = literals.keySet().iterator(); if (iter.hasNext()) println(); while (iter.hasNext()) { Object o = iter.next(); String name = (String) literals.get(o); if (o instanceof ESId) { print("static ESId " + name); print(" = ESId.intern(\""); printString(String.valueOf(o)); println("\");"); } else if (o instanceof ESString) { print("static ESString " + name); print(" = ESString.create(\""); printString(String.valueOf(o)); println("\");"); } else if (o instanceof ESNumber) { print("static ESNumber " + name); double v = ((ESNumber) o).toNum(); if (Double.isInfinite(v)) println(" = ESNumber.create(Double.POSITIVE_INFINITY);"); else if (Double.isInfinite(-v)) println(" = ESNumber.create(Double.NEGATIVE_INFINITY);"); else if (Double.isNaN(v)) throw new RuntimeException(); else println(" = ESNumber.create(" + o + "D);"); } else throw new RuntimeException(); } println("static ESId[] _a_null = new ESId[0];"); for (int i = 0; i < functions.size(); i++) { Function fun = (Function) functions.get(i); printFormals(fun, i); } for (int i = 0; i < classes.size(); i++) { ParseClass cl = (ParseClass) classes.get(i); Function fun = cl.getFunction(ESId.intern(cl.name)); printFormals(fun, i + functions.size()); } } void setLine(String filename, int line) { lineMap.add(filename, line, destLine); } /** * Prints the mapping between java lines and the original *.js lines. */ void printLineMap() throws IOException { String dst = name + ".java"; String src = srcFilename; String srcTail = srcFilename; int p = srcTail.lastIndexOf('/'); if (p >= 0) srcTail = srcTail.substring(p + 1); p = srcTail.lastIndexOf('\\'); if (p >= 0) srcTail = srcTail.substring(p + 1); println(); println("public com.caucho.java.LineMap getLineMap()"); println("{"); pushDepth(); print("com.caucho.java.LineMap lineMap = new com.caucho.java.LineMap(\""); printString(dst); print("\", \""); printString(srcTail); println("\");"); println("lineMap.add(1, 1);"); Iterator iter = lineMap.iterator(); while (iter.hasNext()) { LineMap.Line line = (LineMap.Line) iter.next(); if (line.getSourceFilename() == src) { println("lineMap.add(" + line.getSourceLine() + ", " + line.getDestinationLine() + ");"); } else { src = line.getSourceFilename(); srcTail = src; p = srcTail.lastIndexOf('/'); if (p >= 0) srcTail = srcTail.substring(p + 1); p = srcTail.lastIndexOf('\\'); if (p >= 0) srcTail = srcTail.substring(p + 1); print("lineMap.add(\""); printString(srcTail); println("\", " + line.getSourceLine() + ", " + line.getDestinationLine() + ");"); } } println("return lineMap;"); popDepth(); println("}"); } private void printFormals(Function fun, int i) throws IOException { if (fun.getFormalSize() > 0) { print(" private static ESId[] _a_" + i + " = new ESId[] {"); for (int j = 0; fun.formals != null && j < fun.formals.size(); j++) { if (j != 0) print(","); Variable var = (Variable) fun.formals.get(j); print(" ESId.intern(\"" + var.getId() + "\")"); } println("};"); } } /** * Escapes the string so the Java compiler can properly understand it. */ void printString(String s) throws IOException { for (int i = 0; i < s.length(); i++) { if (i > 0 && i % (16 * 1024) == 0) os.print("\" + \""); char ch = s.charAt(i); switch (ch) { case '\\': os.print("\\\\"); break; case '\n': os.print("\\n"); break; case '\r': os.print("\\r"); break; case '\t': os.print("\\t"); break; case '"': os.print("\\\""); break; default: if (ch >= 32 && ch < 127) os.print(ch); else { os.print("\\u"); printHex(ch >> 12); printHex(ch >> 8); printHex(ch >> 4); printHex(ch >> 0); } break; } } } void printHex(int i) throws IOException { i &= 0xf; if (i < 10) { os.print(i); } else { os.print((char) ('a' + i - 10)); } } void pushDepth() { printDepth += 2; } void popDepth() { printDepth -= 2; } void print(boolean b) throws IOException { if (isFirst) printSpaces(); root.os.print(b); } void print(int i) throws IOException { if (isFirst) printSpaces(); root.os.print(i); } void print(char c) throws IOException { if (isFirst) printSpaces(); root.os.print(c); if (c == '\n') destLine++; } void print(String s) throws IOException { if (isFirst) printSpaces(); if (s == null) s = "null"; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '\n') { isFirst = true; destLine++; } else isFirst = false; } root.os.print(s); } void print(Object o) throws IOException { if (isFirst) printSpaces(); print(String.valueOf(o)); } void println() throws IOException { if (isFirst) printSpaces(); root.os.println(); destLine++; isFirst = true; } void println(String s) throws IOException { print(s); println(); } void println(Object o) throws IOException { print(String.valueOf(o)); println(); } void printSpaces() throws IOException { for (int i = 0; i < printDepth; i++) root.os.print(' '); isFirst = false; } static class Location { String filename; int line; Location(String filename, int line) { this.filename = filename; this.line = line; } } }