/* * 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.es.ESId; import com.caucho.util.CharBuffer; import com.caucho.util.IntMap; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; /** * Function is an intermediate form representing an expression. */ class Function { static ESId PROTOTYPE = ESId.intern("prototype"); Parser parser; ParseClass cl; private Function parent; int funDepth; private int lambdaCount = 0; ArrayList formals; Expr returnType; ArrayList variables = new ArrayList(); // child closures ArrayList functions; private IntMap funMap; boolean isClass; ESId classProto; // null for non-classes Function constructor; String name; ESId id; int num; boolean isSilent; boolean hasCall; boolean hasThis; boolean allowLocals; boolean allowJavaLocals; boolean needsScope; ArrayList data = new ArrayList(); private HashMap vars = new HashMap(); CharBuffer tail; private int iterCount; private int tempCount; private int stmtCount; private int stmtTop; private HashMap usedVars; private boolean isGlobal; // True if an arguments variable needs to be created. eval, closures, // the arguments variable and "with" will set this. private boolean needsArguments; private boolean needsStatementResults; // True if any variable can be used even if not explicitly referenced // e.g. if the "arguments" variable is used. private boolean useAllVariables; // True if the function contains an eval private boolean isEval; // True if the function contains a switch private boolean hasSwitch; Function(ParseClass cl, Function parent, String name, ESId id, boolean isClass) { this.id = id; this.name = name; this.parent = parent; this.cl = cl; if (parent == null || isClass) funDepth = 0; else funDepth = parent.funDepth + 1; num = -1; isGlobal = parent == null; allowLocals = funDepth >= 1 || isClass; allowJavaLocals = allowLocals; needsStatementResults = parent == null; } void setFast(boolean isFast) { if (isFast) { allowLocals = true; allowJavaLocals = true; } } Function getParent() { return parent; } boolean isGlobalScope() { return ! needsScope && (getFunctionDepth() == 0 || getFunctionDepth() == 1 && ! needsArguments()); } boolean needsStatementResults() { return needsStatementResults; } void setNeedsResults() { needsStatementResults = true; } void setEval() { setArguments(); needsStatementResults = true; isEval = true; } boolean useAllVariables() { return isEval || useAllVariables; } void setUseAllVariables() { useAllVariables = true; } String getStatementVar() { if (! needsStatementResults()) return null; else return "_val" + stmtCount; } void pushStatementLoop() { if (! needsStatementResults()) return; stmtCount++; if (stmtCount > stmtTop) stmtTop = stmtCount; } void popStatementLoop() { if (! needsStatementResults()) return; stmtCount--; } void setCall() { this.hasCall = true; } void setThis() { this.hasThis = true; } /** * Force all unassigned variables to become object vars. */ void setVars() { Iterator iter = vars.values().iterator(); while (iter.hasNext()) { Variable var = (Variable) iter.next(); var.getType(); } } boolean isGlobal() { return isGlobal; } int getFunctionDepth() { return funDepth; } void disableGlobal() { isGlobal = false; } void disallowLocal() { if (parent != null) { allowJavaLocals = false; allowLocals = false; needsScope = true; } } void disallowJavaLocal() { allowJavaLocals = false; } void setNeedsScope() { if (parent != null) needsScope = true; } void setArguments() { needsArguments = true; setNeedsScope(); disallowLocal(); } boolean needsArguments() { return needsArguments; } boolean allowLocals() { return allowLocals; } boolean allowJavaLocals() { return allowLocals && allowJavaLocals; } void useClosureVar(ESId name) { for (Function fun = parent; fun != null; fun = fun.parent) { Variable var = (Variable) fun.vars.get(name); fun.needsArguments = true; if (var != null) var.setUsedByClosure(); else { if (fun.usedVars == null) fun.usedVars = new HashMap(); fun.usedVars.put(name, name); } } } /** * Returns true if the variable is declared. */ boolean hasVar(ESId name) { return vars.get(name) != null; } /** * Returns a new variable. */ IdExpr newVar(Block block, ESId name) { return newVar(block, name, null); } /** * Returns a new variable. */ IdExpr newVar(Block block, ESId name, Expr type) { Variable var = (Variable) vars.get(name); if (var == null && type == null) { var = cl.getVariable(name); if (var != null) { return new IdExpr(block, var); } } if (var == null) { var = new Variable(block, name, type, false); vars.put(name, var); if (usedVars != null && usedVars.get(name) != null) var.setUsedByClosure(); } useClosureVar(name); return new IdExpr(block, var); } /** * Add a variable to the function. If the function is the global * function, add it to the class. */ void addVariable(Block block, ESId id, Expr type) { if (variables == null) variables = new ArrayList(); Variable var = (Variable) vars.get(id); if (var == null) { var = new Variable(block, id, type, allowLocals); vars.put(id, var); if (usedVars != null && usedVars.get(id) != null) var.setUsedByClosure(); // Only add global variables if they're declared, not if defined // by use. if (parent == null && type != null) cl.addVariable(id, var); } else if (parent != null) var.setLocal(); if (! variables.contains(var) && (formals == null || ! formals.contains(var))) variables.add(var); useClosureVar(id); } int getIter() { return iterCount++; } String getTemp() { return "temp" + tempCount++; } void setConstructor(Function constructor) { isClass = true; this.constructor = constructor; } void setClassProto(ESId classProto) { isClass = true; this.classProto = classProto; } void setCodeNumber(int num) { this.num = num; } void addFormal(Block block, ESId id, Expr type) { if (formals == null) formals = new ArrayList(); Variable var = new Variable(block, id, type, true); var.setUsed(); formals.add(var); vars.put(id, var); } int getFormalSize() { return formals == null ? 0 : formals.size(); } Variable getFormal(int j) { return (Variable) formals.get(j); } Expr getReturnType() { return returnType; } void setReturnType(Expr type) { returnType = type; } int getVariableSize() { return variables == null ? 0 : variables.size(); } void addFunction(Function function) { if (functions == null) { functions = new ArrayList(); funMap = new IntMap(); } int pos = funMap.get(function.id); if (pos < 0) { funMap.put(function.id, functions.size()); functions.add(function); } else functions.set(pos, function); } int getFunctionSize() { return functions == null ? 0 : functions.size(); } Function getFunction(int i) { return (Function) functions.get(i); } Function getFunction(ESId id) { if (funMap == null) return null; int index = funMap.get(id); if (index >= 0) { return (Function) functions.get(index); } else return null; } void print(Object value) { if (tail == null) tail = CharBuffer.allocate(); tail.append(String.valueOf(value)); } void println(String value) { if (tail == null) tail = CharBuffer.allocate(); tail.append(String.valueOf(value)); tail.append('\n'); } void addExpr(Expr expr) { if (tail != null) data.add(tail); tail = null; data.add(expr); expr.setUsed(); } void addBoolean(Expr expr) { if (tail != null) data.add(tail); tail = null; data.add(expr.setBoolean()); expr.setUsed(); } Object getTop() { if (tail != null) return tail; else if (data.size() > 0) return data.get(data.size() - 1); else { tail = new CharBuffer(); return tail; } } Object getSwitch() { if (tail != null) data.add(tail); tail = null; hasSwitch = true; return data.get(data.size() - 1); } int mark() { if (tail != null) data.add(tail); tail = null; return data.size(); } void moveChunk(Object topObject, int mark) { if (tail != null) data.add(tail); tail = null; int top; for (top = 0; top < mark; top++) { if (data.get(top) == topObject) break; } top++; int here = data.size(); for (int i = 0; i < here - mark; i++) { Object chunk = data.remove(data.size() - 1); data.add(top, chunk); } } void writeCode(ParseClass cl) throws IOException { cl.print("public "); if (returnType == null) cl.print("ESBase "); else if (returnType.getType() == Expr.TYPE_INTEGER) cl.print("int "); else cl.print("ESBase "); cl.print(name + "(Call _env, int _length"); for (int i = 0; i < getFormalSize(); i++) { Variable formal = (Variable) formals.get(i); if (formal.getTypeExpr() instanceof TypeExpr) { TypeExpr type = (TypeExpr) formal.getTypeExpr(); if (type.getTypeName() != null) cl.print(", " + type.getTypeName() + " " + formal.getId()); } } cl.println(")"); cl.println("throws Throwable"); cl.println("{"); cl.pushDepth(); if (hasCall) cl.println("Call _call = _env.getCall();"); if (hasThis) cl.println("ESObject _this = _env.getThis();"); if (parent != null && functions != null && functions.size() > 0) { needsArguments = true; setNeedsScope(); } // Eval just uses the calling scope if (isEval) { cl.println("ESObject _arg = _env.getEval();"); } else { if (needsScope && parent != null) cl.println("_env.fillScope();"); if (needsArguments && parent != null) { cl.print("ESObject _arg = _env.createArg("); if (getFormalSize() > 0) cl.print("_js._a_" + num); else cl.print("_js._a_null"); cl.println(", _length);"); } } printFormals(); printLocalVariables(); // do closures for (int i = 0; needsArguments && functions != null && i < functions.size(); i++) { if (i == 0) cl.println("ESClosure _closure;"); Function fun = (Function) functions.get(i); Variable var = (Variable) vars.get(fun.id); if (! isGlobal && ! isEval && var != null && ! var.isUsed() && ! useAllVariables) { continue; } cl.print("_closure = new ESClosure("); cl.printLiteral(fun.id); cl.println(", _js, null, " + fun.num + ", _js._a_null, null);"); cl.println("_closure.closure(_env);"); if (! isEval && allowLocals && var != null && var.isUsed() && var.isJavaLocal()) { cl.println("ESBase " + fun.id + " = _closure;"); continue; } else if (! isEval && parent != null) cl.print("_arg.put("); else cl.print("_env.global.put("); cl.printLiteral(fun.id); cl.print(", _closure, "); if (! isEval) cl.print("ESBase.DONT_ENUM|ESBase.DONT_DELETE"); else cl.print("0"); cl.println(");"); } for (int i = 0; i < iterCount; i++) cl.println("java.util.Iterator iter" + i + ";"); for (int i = 0; i < tempCount; i++) cl.println("ESBase temp" + i + ";"); if (needsStatementResults()) cl.println("ESBase _val0 = ESBase.esUndefined;"); for (int i = 1; i <= stmtTop; i++) cl.println("ESBase _val" + i + " = ESBase.esUndefined;"); if (hasSwitch) { cl.println("int _switchcode;"); cl.println("ESBase _switchtemp;"); } for (int i = 0; i < data.size(); i++) { Object d = data.get(i); if (d instanceof CharBuffer) cl.print((CharBuffer) d); else if (d instanceof Expr) { Expr expr = (Expr) d; //cl.setLine(expr.getFilename(), expr.getLine()); expr.printExpr(); } } if (tail != null) cl.print(tail); cl.popDepth(); cl.println("}"); } void printFormals() throws IOException { formal: for (int i = 0; formals != null && i < formals.size(); i++) { Variable formal = (Variable) formals.get(i); // Functions have priority if (funMap != null && funMap.get(formal.getId()) >= 0) continue formal; // Duplicate formals use the last one for (int j = i + 1; j < formals.size(); j++) { if (formal.getId() == ((Variable) formals.get(j)).getId()) continue formal; } if (! allowLocals) { } else if (formal.getTypeExpr() instanceof JavaTypeExpr) { } else if (formal.getType() != Expr.TYPE_INTEGER) { cl.print("ESBase " + formal.getId()); cl.println(" = _env.getArg(" + i + ", _length);"); } } } /** * Initializes the local variables for a function. */ void printLocalVariables() throws IOException { for (int i = 0; i < variables.size(); i++) { Variable var = (Variable) variables.get(i); if (funMap != null && funMap.get(var.getId()) >= 0) { var.killLocal(); continue; } if (var.isJavaGlobal()) { // already initialized } else if ((! allowLocals || ! var.isJavaLocal()) && isGlobal()) { cl.print("_env.global.setProperty("); cl.printLiteral(var.getId()); cl.println(", ESBase.esUndefined);"); } else if (! var.isUsed() && ! useAllVariables()) { } else if (! allowLocals || ! var.isJavaLocal()) { if (isEval) { cl.print("if (_arg.getProperty("); cl.printLiteral(var.getId()); cl.println(") == ESBase.esEmpty)"); cl.print(" "); } if (! var.hasInit()) { cl.print("_arg.put("); cl.printLiteral(var.getId()); cl.print(", ESBase.esUndefined, "); if (! isEval) cl.print("ESBase.DONT_ENUM|ESBase.DONT_DELETE"); else cl.print("0"); cl.println(");"); } } else if (var.getType() == Expr.TYPE_BOOLEAN) { cl.println("boolean " + var.getId() + ";"); if (! var.hasInit()) cl.println(var.getId() + " = false;"); } else if (var.getType() == Expr.TYPE_INTEGER) { cl.println("int " + var.getId() + ";"); if (! var.hasInit()) cl.println(var.getId() + " = 0;"); } else if (var.getType() == Expr.TYPE_NUMBER) { cl.println("double " + var.getId() + ";"); if (! var.hasInit()) cl.println(var.getId() + " = Double.NaN;"); } else if (var.getType() == Expr.TYPE_STRING) { cl.println("ESString " + var.getId() + ";"); if (! var.hasInit()) cl.println(var.getId() + " = null;"); } else if (var.getType() == Expr.TYPE_JAVA && var.getTypeExpr() != null) { TypeExpr type = (TypeExpr) var.getTypeExpr(); cl.println(type.getTypeName() + " " + var.getId() + " = null;"); } else if (var.isLocal()) { if (! var.hasInit()) cl.println("ESBase " + var.getId() + " = ESBase.esUndefined;"); else cl.println("ESBase " + var.getId() + ";"); } else { cl.print(" _env.global.setProperty("); cl.printLiteral(var.getId()); cl.println(", ESBase.esUndefined);"); } } } }