/*
* 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.ESBase;
import com.caucho.es.ESException;
import com.caucho.es.ESId;
import com.caucho.es.ESParseException;
import com.caucho.es.Script;
import com.caucho.java.JavaCompiler;
import com.caucho.java.LineMap;
import com.caucho.loader.SimpleLoader;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntArray;
import com.caucho.util.L10N;
import com.caucho.vfs.MergePath;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import com.caucho.vfs.WriteStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.logging.Logger;
/**
* Parser is a factory for generating compiled Script objects.
*
* <p>Most applications will use the <code>parse(String)</code> interface
* to parse JavaScript. That method will try to load a precompiled
* script from the work directory before trying to parse it.
*
* <p>Applications will often set the script path a directory for
* script and include the classpath in the path. Applications will
* often override the work dir for a more appropriate work directory.
*
* <code><pre>
* package com.caucho.vfs.*;
* package com.caucho.es.*;
*
* ...
*
* com.caucho.es.parser.Parser parser;
* parser = new com.caucho.es.parser.Parser();
*
* // configure the path to search for *.js files
* MergePath scriptPath = new MergePath();
* scriptPath.addMergePath(Vfs.lookup("/home/ferg/js"));
* ClassLoader loader = Thread.currentThread().contextClassLoader();
* scriptPath.addClassPath(loader);
* parser.setScriptPath(scriptPath);
*
* // configure the directory storing compiled scripts
* Path workPath = Vfs.lookup("/tmp/caucho/work");
* parser.setWorkDir(workPath);
*
* Script script = parser.parse("test.js");
* </pre></code>
*/
public class Parser {
private static final Logger log
= Logger.getLogger(Parser.class.getName());
private static final L10N L = new L10N(Parser.class);
private static final Object LOCK = new Object();
static ESId CLINIT = ESId.intern("__clinit__");
static ESId PROTOTYPE = ESId.intern("prototype");
static ESId FINALLY = ESId.intern("finally");
static ESId ANONYMOUS = ESId.intern("anonymous");
static ESId OBJECT = ESId.intern("Object");
static ESId REGEXP = ESId.intern("RegExp");
static ESId ARRAY = ESId.intern("Array");
static ESId LENGTH = ESId.intern("length");
static ESId PACKAGES = ESId.intern("Packages");
static ESId JAVA = ESId.intern("java");
static ESId COM = ESId.intern("com");
static ESId CAUCHO = ESId.intern("caucho");
static final int PREC_DOT = 1;
static final int PREC_POST = PREC_DOT;
static final int PREC_FUN = PREC_POST + 1;
static final int PREC_UMINUS = PREC_FUN + 1;
static final int PREC_TIMES = PREC_UMINUS + 1;
static final int PREC_PLUS = PREC_TIMES + 1;
static final int PREC_SHIFT = PREC_PLUS + 1;
static final int PREC_CMP = PREC_SHIFT + 1;
static final int PREC_EQ = PREC_CMP + 1;
static final int PREC_BITAND = PREC_EQ + 1;
static final int PREC_BITXOR = PREC_BITAND + 1;
static final int PREC_BITOR = PREC_BITXOR + 1;
static final int PREC_AND = PREC_BITOR + 1;
static final int PREC_OR = PREC_AND + 1;
static final int PREC_COND = PREC_OR + 1;
static final int PREC_ASSIGN = PREC_COND + 1;
static final int PREC_COMMA = PREC_ASSIGN + 1;
static final int PREC_MAX = PREC_COMMA + 1;
ClassLoader parentLoader;
ClassLoader loader;
Path scriptPath;
boolean isEval;
// Name of the generated class
String className;
Lexer lexer;
IntArray hashes = new IntArray();
ArrayList importList = new ArrayList();
ParseClass parseClass;
Function globalFunction;
Function staticFunction;
Function function; // the current prototype, i.e. class
Block block;
LineMap lineMap;
Path workPath;
//JavaCompiler compiler;
boolean isFast;
public Parser()
{
workPath = CauchoSystem.getWorkPath();
//compiler.setEncoding("utf8");
addImport("java.lang.*");
addImport("com.caucho.jslib.*");
}
/**
* Sets the parent class loader. If unspecified, defaults to the context
* classloader. Most applications will just use the default.
*
* @param parentLoader the classloader to be used for the script's parent.
*/
public void setParentLoader(ClassLoader parentLoader)
{
this.parentLoader = parentLoader;
}
/**
* Internal method to set the actual class loader.
* Normally, this should only be called from com.caucho.es functions.
*/
public void setClassLoader(ClassLoader loader)
{
this.loader = loader;
}
/**
* Returns the current class loader. If null, creates from the parent
* loader and the work-dir.
*/
ClassLoader getClassLoader()
{
if (loader != null)
return loader;
if (parentLoader != null)
loader = SimpleLoader.create(parentLoader, workPath, null);
else
loader = SimpleLoader.create(null, workPath, null);
return loader;
}
/**
* Sets the path to search for imported javascript source files.
* Normally, ScriptPath will be a MergePath. If the MergePath
* adds the classpath, then JavaScript files can be put in the
* normal Java classpath.
*
* <p>If the ScriptPath is not specified, it will use the
* current directory and the classpath from the context class loader.
*
* <code><pre>
* MergePath scriptPath = new MergePath();
* scriptPath.addMergePath(Vfs.lookup("/home/ferg/js"));
*
* ClassLoader loader = Thread.currentThread().contextClassLoader();
* scriptPath.addClassPath(loader);
*
* parser.setScriptPath(scriptPath);
* </pre></code>
*
* @param scriptPath path to search for imported scripts.
*/
public void setScriptPath(Path scriptPath)
{
this.scriptPath = scriptPath;
}
/**
* Returns the path to search for imported javascript. Normally, scriptPath
* will be a MergePath.
*
* @param scriptPath path to search for imported scripts.
*/
public Path getScriptPath()
{
return scriptPath;
}
/**
* Adds a package/script to be automatically imported by the script.
* Each import is the equivalent of adding the following javascript:
*
* <code><pre>
* package <em>name</em>;
* </pre></code>
*
* @param name package or script name to be automatically imported.
*/
public void addImport(String name)
{
if (! importList.contains(name))
importList.add(name);
}
/**
* Sets "fast" mode, i.e. JavaScript 2.0. Fast mode lets the compiler
* make assumptions about types and classes, e.g. that class methods
* won't change dynamically. This lets the compiler generate more code
* that directly translates to Java calls.
*/
public void setFast(boolean isFast)
{
this.isFast = isFast;
}
/**
* Sets a line number map. For generated files like JSP or XTP,
* the error messages need an extra translation to get to the original
* line numbers.
*/
public void setLineMap(LineMap lineMap)
{
this.lineMap = lineMap;
}
/**
* Sets the name of the generated java class. If unset, the parser will
* mangle the input name.
*/
public void setClassName(String name)
{
this.className = name;
}
/**
* Sets the directory for generated *.java and *.class files.
* The parser will check this directory for any precompiled javascript
* classes. The default work-dir is /tmp/caucho on unix and
* c:\temp\caucho on windows.
*
* @param path the work directory.
*/
public void setWorkDir(Path path)
{
workPath = path;
}
/**
* Returns the directory for generated *.java and *.class files.
*/
public Path getWorkDir()
{
return workPath;
}
/**
* Main application parsing method. The parser will try to load
* the compiled script. If the compiled script exists and the
* source file has not changed, parse will return the old script.
* Otherwise, it will parse and compile the javascript.
*
* @param name the name of the javascript source.
*
* @return the parsed script
*/
public Script parse(String name) throws ESException, IOException
{
Path path;
try {
String className;
if (this.className != null)
className = this.className;
else
className = "_js." + JavaCompiler.mangleName(name);
if (scriptPath == null) {
MergePath mergePath = new MergePath();
mergePath.addMergePath(Vfs.lookup());
ClassLoader parentLoader = this.parentLoader;
if (parentLoader == null)
parentLoader = Thread.currentThread().getContextClassLoader();
mergePath.addClassPath(parentLoader);
scriptPath = mergePath;
}
Script script = loadScript(className);
if (! script.isModified()) {
script.setScriptPath(getScriptPath());
script.setClassDir(workPath);
return script;
}
} catch (Throwable e) {
}
path = getScriptPath().lookup(name);
ReadStream is = path.openRead();
try {
return parse(is, name, 1);
} finally {
is.close();
}
}
/**
* Alternative parsing method when the application only has an open
* stream to the file. Since this method will always compile a new script,
* it can be significantly slower than the <code>parse(String)</code>
* method.
*
* @param is a read stream to the javascript source.
*
* @return the parsed script.
*/
public Script parse(ReadStream is) throws ESException, IOException
{
return parse(is, null, 1);
}
/**
* An alternative parsing method given an open stream, a filename and
* a line number.
*
* @param is a stream to the javascript source.
* @param name filename to use for error messages.
* @param line initial line number.
*
* @return the compiled script
*/
public Script parse(ReadStream is, String name, int line)
throws ESException, IOException
{
if (name == null)
name = is.getUserPath();
if (line <= 0)
line = 1;
return parse(is, name, line, false);
}
/**
* Parses a script for the JavaScript "eval" expression. The
* semantics for "eval" are slightly different from standard
* scripts.
*
* @param is stream to the eval source.
* @param name filename to use for error messages
* @param line initial line number to use for error messages.
*
* @return the compiled script.
*/
public Script parseEval(ReadStream is, String name, int line)
throws ESException, IOException
{
if (name == null)
name = "eval";
if (line <= 0)
line = 1;
return parse(is, name, line, true);
}
/**
* The main parser method.
*
* @param is stream to read the script.
* @param name the filename to use for error messages.
* @param line the line number to use for error messages.
* @param isEval if true, parse for an eval expression.
*
* @return the compiled javascript
*/
private Script parse(ReadStream is, String name,
int line, boolean isEval)
throws ESException, IOException
{
if (name == null)
name = "anonymous";
if (line <= 0)
line = 1;
lexer = new Lexer(is, name, line);
if (lineMap != null)
lexer.setLineMap(lineMap);
if (className == null)
className = "_js." + JavaCompiler.mangleName(name);
if (scriptPath == null) {
MergePath mergePath = new MergePath();
if (is.getPath() != null)
mergePath.addMergePath(is.getPath().getParent());
else
mergePath.addMergePath(Vfs.lookup());
ClassLoader parentLoader = this.parentLoader;
if (parentLoader == null)
parentLoader = Thread.currentThread().getContextClassLoader();
mergePath.addClassPath(parentLoader);
scriptPath = mergePath;
}
block = null;
JavaCompiler compiler = JavaCompiler.create(getClassLoader());
compiler.setClassDir(workPath);
parseClass = new ParseClass(name, className);
parseClass.setParser(this);
if (is.getPath() != null && is.getPath().getLastModified() > 0)
parseClass.setSourcePath(is.getPath());
globalFunction = parseClass.newFunction(null, ESId.intern("global"), false);
globalFunction.setFast(isFast);
staticFunction = parseClass.newFunction(null, ESId.intern("__es_static"), false);
parseClass.setGlobal(globalFunction);
if (isEval) {
block = Block.create(this, globalFunction);
block.finish();
function = parseClass.newFunction(globalFunction, ESId.intern("eval"), false);
function.setEval();
}
else
function = globalFunction;
block = Block.create(this, function);
parseBlock(true);
block.finish();
if (lexer.peek() != Lexer.EOF)
throw expect(L.l("end of file"));
block = Block.create(this, staticFunction);
block.finish();
synchronized (LOCK) {
Path path = workPath.lookup(className.replace('.', '/') + ".java");
path.getParent().mkdirs();
WriteStream os = path.openWrite();
os.setEncoding("JAVA");
parseClass.writeCode(os);
os.close();
Script script;
try {
compiler.compile(className.replace('.', '/') + ".java", null);
script = loadScript(className);
} catch (Exception e) {
throw new ESParseException(e);
}
script.setScriptPath(getScriptPath());
script.setClassDir(workPath);
return script;
}
}
/**
* Tries to load an already compiled script.
*
* @param className mangled classname of the script.
*/
private Script loadScript(String className)
throws Exception
{
ClassLoader loader = getClassLoader();
Class cl = CauchoSystem.loadClass(className, false, loader);
Script script = (Script) cl.newInstance();
script.setScriptPath(getScriptPath());
script.setClassDir(workPath);
return script;
}
/**
* Parse a function
*/
private Function parseFunction() throws ESException
{
function.setNeedsScope();
ESId id = null;
if (lexer.peek() == Lexer.IDENTIFIER) {
lexer.next();
id = lexer.getId();
}
if (lexer.next() != '(')
throw expect("`('");
if (id != null) {
function.addVariable(block, id, null);
block.newVar(id).getVar().setType(Expr.TYPE_ES);
}
Block oldBlock = block;
Function oldFun = function;
function = parseClass.newFunction(oldFun, id, false);
oldFun.addFunction(function);
block = Block.create(this, function);
boolean isFirst = true;
while (lexer.peek() != ')') {
if (! isFirst && lexer.next() != ',')
throw expect("`,'");
isFirst = false;
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("formal argument"));
ESId argId = lexer.getId();
Expr type = null;
if (lexer.peek() == ':') {
lexer.next();
type = parseType();
}
function.addFormal(block, argId, type);
}
lexer.next();
if (lexer.peek() == ':') {
lexer.next();
Expr type = parseType();
function.setReturnType(type);
}
if (lexer.next() != '{')
throw expect("`{'");
parseBlock(false);
if (lexer.next() != '}') {
throw expect("`}'");
}
block.finish();
Function newFun = function;
function = oldFun;
block = oldBlock;
return newFun;
}
/**
* Parses a list of statements, returning a block representing the
* statements.
*
* @param parent the containing block
* @param isTop true if this is a top level block
*/
private void parseBlock(boolean isTop)
throws ESException
{
loop:
while (true) {
switch (lexer.peek()) {
case Lexer.UNARY_OP:
case Lexer.BANDU_OP:
case Lexer.NEW:
case Lexer.DELETE:
case Lexer.VOID:
case Lexer.TYPEOF:
case Lexer.POSTFIX:
case Lexer.IDENTIFIER:
case Lexer.THIS:
case Lexer.EVAL:
case Lexer.LITERAL:
case Lexer.REGEXP:
case '(':
case '[':
case Lexer.HASH_REF:
case Lexer.HASH_DEF:
case Lexer.FUNCTION:
parseStatement();
break;
case Lexer.IF:
case Lexer.FOR:
case Lexer.WHILE:
case Lexer.DO:
case Lexer.VAR:
case Lexer.BREAK:
case Lexer.WITH:
case Lexer.SYNCHRONIZED:
case Lexer.CONTINUE:
case Lexer.RETURN:
case Lexer.SWITCH:
case Lexer.TRY:
case Lexer.THROW:
case ';':
parseStatement();
break;
case '{':
block = block.startBlock();
parseStatement();
block = block.finishBlock();
break;
case Lexer.CATCH:
block.doTry();
parseCatch();
break;
case Lexer.FINALLY:
block.doTry();
parseFinally();
break;
case Lexer.CLASS:
parseClass();
break;
case Lexer.IMPORT:
parseImport();
break;
case Lexer.STATIC:
if (true) throw new ESException("nostatus");
//parseStatic();
break;
default:
break loop;
}
}
}
/**
* Parses a statement.
*/
private void parseStatement() throws ESException
{
int lexeme = lexer.peek();
hashes.clear();
int line = lexer.getLine();
Expr expr = null;
block.setLine(line);
if (block.isDead) {
switch (lexeme) {
case ';':
case Lexer.VAR:
case Lexer.FUNCTION:
case Lexer.CATCH:
case Lexer.FINALLY:
break;
default:
throw error(L.l("can't reach statement"));
}
}
switch (lexeme) {
case Lexer.IDENTIFIER:
parseIdentifierStatement();
break;
case Lexer.UNARY_OP:
case Lexer.BANDU_OP:
case Lexer.NEW:
case Lexer.THIS:
case Lexer.EVAL:
case Lexer.LITERAL:
case Lexer.REGEXP:
case Lexer.POSTFIX:
case Lexer.DELETE:
case Lexer.VOID:
case Lexer.TYPEOF:
case '(':
case '[':
case Lexer.HASH_REF:
case Lexer.HASH_DEF:
block.addExpr(parseExpression(PREC_MAX, true));
parseStatementEnd();
break;
case Lexer.FUNCTION:
lexer.next();
Function newFun = parseFunction();
break;
case Lexer.VAR:
parseVar(false);
parseStatementEnd();
break;
case Lexer.BREAK:
lexer.next();
if (lexer.peek() == Lexer.IDENTIFIER && ! lexer.seenLineFeed()) {
block.doBreak(lexer.getId());
lexer.next();
} else
block.doBreak();
parseStatementEnd();
break;
case Lexer.CONTINUE:
lexer.next();
if (lexer.peek() == Lexer.IDENTIFIER && ! lexer.seenLineFeed()) {
block.doContinue(lexer.getId());
lexer.next();
} else
block.doContinue();
parseStatementEnd();
break;
case Lexer.RETURN:
lexer.next();
if (lexer.peek() == ';' || lexer.peek() == '}' ||
lexer.seenLineFeed()) {
block.doReturn();
} else {
block.doReturn(parseExpression(PREC_MAX, true));
}
parseStatementEnd();
break;
case Lexer.IF:
parseIf();
break;
case Lexer.SWITCH:
parseSwitch();
break;
case Lexer.WHILE:
parseWhile(null);
break;
case Lexer.DO:
parseDo(null);
break;
case Lexer.FOR:
parseFor(null);
break;
case Lexer.WITH:
parseWith();
break;
case Lexer.SYNCHRONIZED:
parseSynchronized();
break;
case Lexer.TRY:
parseTry();
break;
case Lexer.THROW:
lexer.next();
block.doThrow(parseExpression(PREC_MAX));
break;
case ';':
lexer.next();
break;
case '{':
lexer.next();
parseBlock(false);
if (lexer.next() != '}')
throw expect("`}'");
break;
default:
throw expect(L.l("statement"));
}
}
private void parseStatementEnd() throws ESException
{
if (lexer.peek() == ';')
lexer.next();
else if (lexer.peek() == '}' ||
lexer.peek() == Lexer.EOF || lexer.seenLineFeed()) {
} else {
throw expect("`;'");
}
}
private void parseIdentifierStatement()
throws ESException
{
ESId id = lexer.getId();
int line = lexer.getLine();
lexer.next();
if (lexer.peek() != ':') {
Expr var = getVar(id);
Expr expr = parseExprRec(parseTermTail(var, false, true),
PREC_MAX, false, true);
block.addExpr(expr);
parseStatementEnd();
return;
}
lexer.next();
/*
if (findLoop(null, id) != null)
throw error("duplicate label `" + id + "'");
*/
switch (lexer.peek()) {
case Lexer.WHILE:
parseWhile(id);
break;
case Lexer.DO:
parseDo(id);
break;
case Lexer.FOR:
parseFor(id);
break;
default:
block = block.startBlock(id);
parseStatement();
block = block.finishBlock();
break;
}
}
private Expr parseType() throws ESException
{
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("identifier"));
Expr type = block.newType(lexer.getId());
while (lexer.peek() == '.') {
lexer.next();
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("identifier"));
type = type.fieldReference(lexer.getId());
}
return type;
}
/**
* if ::= IF '(' expr ')' stmt
*/
private void parseIf() throws ESException
{
boolean isFirst = true;
boolean isDead = true;
block = block.create();
while (lexer.peek() == Lexer.IF) {
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
block.startIf(parseBooleanExpression(PREC_MAX), ! isFirst);
isFirst = false;
if (lexer.next() != ')')
throw expect("`)'");
parseStatement();
block.endBlock();
if (! block.isDead)
isDead = false;
block.isDead = false;
if (lexer.peek() != Lexer.ELSE) {
block = block.pop();
return;
}
lexer.next();
}
block.startElse();
parseStatement();
block.endBlock();
if (! block.isDead)
isDead = false;
block = block.pop();
block.isDead = isDead;
}
/**
* switch ::= SWITCH '(' expr ')' '{' ((CASE expr:|DEFAULT)+ stmt*)* '}'
*/
private void parseSwitch() throws ESException
{
lexer.next();
if (lexer.next() != '(')
throw expect("`)'");
Expr test = parseExpression(PREC_MAX);
if (lexer.next() != ')')
throw expect("`)'");
if (lexer.next() != '{')
throw expect("`{'");
ArrayList exprs = new ArrayList();
block = block.startSwitch(test);
int ch;
while ((ch = lexer.peek()) != -1 && ch != '}') {
switch (ch) {
case Lexer.CASE:
lexer.next();
block.doCase(exprs.size());
exprs.add(parseExpression(PREC_MAX));
if (lexer.next() != ':')
throw expect("`:'");
break;
case Lexer.DEFAULT:
lexer.next();
if (lexer.next() != ':')
throw expect("`:'");
block.doDefault();
break;
default:
parseStatement();
}
}
if (lexer.next() != '}')
throw expect("`}'");
block = block.fillSwitch(exprs);
}
/**
* while ::= WHILE '(' expr ')' stmt
*/
private void parseWhile(ESId id) throws ESException
{
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
Expr expr = parseBooleanExpression(PREC_MAX);
if (expr instanceof LiteralExpr &&
! ((LiteralExpr) expr).getLiteral().toBoolean())
throw error(L.l("while (false) is never executed."));
block = block.startWhile(id, expr);
if (lexer.next() != ')')
throw expect("`)'");
parseStatement();
block = block.endLoop();
}
/**
* for ::= FOR '(' expr ')' stmt
*/
private void parseFor(ESId id) throws ESException
{
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
boolean hasValue = false;
Expr lhs = null;
if (lexer.peek() == Lexer.VAR) {
lhs = parseVar(true);
}
else if (lexer.peek() != ';') {
lhs = parseExpression(PREC_MAX);
} else if (lexer.peek() == Lexer.IN)
throw expect(L.l("expression"));
if (lexer.peek() == Lexer.IN) {
parseForIn(id, lhs);
return;
}
if (lhs != null)
lhs.exprStatement(block.function);
if (lexer.next() != ';')
throw expect("`;'");
Expr test = null;
if (lexer.peek() != ';')
test = parseExpression(PREC_MAX);
if (lexer.next() != ';')
throw expect("`;'");
Expr incr = null;
if (lexer.peek() != ')') {
incr = parseExpression(PREC_MAX);
incr.killValue();
}
if (lexer.next() != ')')
throw expect("`)'");
if (test instanceof LiteralExpr &&
! ((LiteralExpr) test).getLiteral().toBoolean())
throw error(L.l("for (;false;) is never executed."));
block = block.startFor(id, test, incr);
parseStatement();
block = block.endLoop();
}
private void parseForIn(ESId id, Expr lhs)
throws ESException
{
lexer.next();
String var = block.newIterator(id, parseExpression(PREC_MAX));
if (lexer.next() != ')')
throw expect("`)'");
block = block.startWhile(id, block.hasNext(var));
block.addExpr(lhs.next(var, lhs));
parseStatement();
block = block.endLoop();
}
/**
* do ::= DO stmt WHILE '(' expr ')'
*/
private void parseDo(ESId id) throws ESException
{
lexer.next();
block = block.startDo(id);
parseStatement();
if (lexer.next() != Lexer.WHILE)
throw expect("`while'");
if (lexer.next() != '(')
throw expect("`('");
block = block.endDo(parseBooleanExpression(PREC_MAX));
if (lexer.next() != ')')
throw expect("`)'");
parseStatementEnd();
}
/**
* with ::= WITH '(' expr ')' stmt
*/
private void parseWith() throws ESException
{
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
block = block.startWith(parseExpression(PREC_MAX));
if (lexer.next() != ')')
throw expect("`)'");
parseStatement();
block = block.endWith();
}
/**
* var ::= VAR id (= expr)? (, id (= expr)?)*
*/
private Expr parseVar(boolean keepValue) throws ESException
{
boolean isFirst = true;
Expr retVar = null;
do {
lexer.next();
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("identifier"));
ESId name = lexer.getId();
Expr type = null;
if (lexer.peek() == ':') {
lexer.next();
type = parseType();
}
block.defVar(name, type);
if (lexer.peek() == '=') {
lexer.next();
Expr var = block.newVar(name, type);
Expr value = parseExpression(Parser.PREC_ASSIGN + 1, true);
block.evalExpr();
var.assign(value).exprStatement(block.function);
} else if (keepValue)
retVar = block.newVar(name, type);
isFirst = false;
} while (lexer.peek() == ',');
return retVar;
}
/**
* synchronized ::= SYNCHRONIZED '(' expr ')' stmt
*/
private void parseSynchronized() throws ESException
{
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
block = block.startSynchronized(parseExpression(PREC_MAX));
if (lexer.next() != ')')
throw expect("`)'");
parseStatement();
block = block.endSynchronized();
}
/**
* try ::= TRY stmt (CATCH | FINALLY)
*/
private void parseTry() throws ESException
{
lexer.next();
block = block.startTry();
parseStatement();
block = block.endTry();
if (lexer.peek() == Lexer.CATCH) {
parseCatch();
}
else if (lexer.peek() == Lexer.FINALLY)
parseFinally();
else
throw error(L.l("expected `catch' or `finally' at {0}", getToken()));
}
/**
* catch ::= CATCH '(' (expr lhs?)? ')' stmt
*/
private void parseCatch() throws ESException
{
block.function.disallowJavaLocal();
// XXX: don't forget catch w/o try
boolean oldDead = block.isDead;
boolean hasCatchall = false;
while (lexer.peek() == Lexer.CATCH) {
block.isDead = false;
if (hasCatchall)
throw error(L.l("catch () must be last catch clause"));
lexer.next();
if (lexer.next() != '(')
throw expect("`('");
String exceptionClass = "";
while (lexer.peek() == Lexer.IDENTIFIER) {
lexer.next();
exceptionClass = exceptionClass + lexer.getText();
if (lexer.peek() != '.')
break;
lexer.next();
exceptionClass = exceptionClass + ".";
if (lexer.peek() != Lexer.IDENTIFIER)
throw expect(L.l("identifier"));
}
ESId name = null;
if (lexer.peek() == Lexer.IDENTIFIER) {
name = lexer.getId();
lexer.next();
}
if (lexer.next() != ')') {
if (exceptionClass.equals(""))
throw expect(L.l("identifier"));
else
throw expect("`)'");
}
if (name == null) {
name = ESId.intern(exceptionClass);
exceptionClass = "java.lang.Exception";
}
Expr var = null;
if (name != null)
var = block.newVar(name);
block = block.startCatch(exceptionClass, var);
parseStatement();
if (! block.isDead)
oldDead = false;
block = block.endCatch();
}
block.isDead = oldDead;
// Don't forget to throw
if (lexer.peek() == Lexer.FINALLY)
parseFinally();
}
/**
* finally ::= FINALLY stmt
*/
private void parseFinally() throws ESException
{
boolean oldDead = block.isDead;
block.isDead = false;
lexer.next();
block = block.startFinally();
parseStatement();
block = block.endFinally();
block.isDead = oldDead;
}
static ESId BOGUS = ESId.intern("return ");
/**
* Parse a class
*/
private void parseClass() throws ESException
{
if (function.getParent() != null)
throw error(L.l("`class' only allowed at top level"));
lexer.next();
if (lexer.next() != Lexer.IDENTIFIER)
throw expect("class name");
ESId id = lexer.getId();
ESId proto = parseExtends();
if (lexer.next() != '{')
throw expect("`{'");
ParseClass oldClass = parseClass;
Function oldGlobal = globalFunction;
Function oldStatic = staticFunction;
Function oldFunction = function;
Block oldBlock = block;
parseClass = oldClass.newClass(id);
parseClass.setProto(proto);
globalFunction = parseClass.newFunction(null, ESId.intern("global"), true);
staticFunction = parseClass.newFunction(null, ESId.intern("__es_static"), true);
parseClass.setGlobal(globalFunction);
function = globalFunction;
block = Block.create(this, function);
parseBlock(true);
block.finish();
block = Block.create(this, staticFunction);
block.finish();
if (parseClass.getFunction(id) == null) {
function = parseClass.newFunction(null, id, false);
block = Block.create(this, function);
block.finish();
}
block = oldBlock;
function = oldFunction;
globalFunction = oldGlobal;
staticFunction = oldStatic;
parseClass = oldClass;
if (lexer.next() != '}')
throw expect("`}'");
}
private ESId parseExtends()
throws ESException
{
if (lexer.peek() != Lexer.EXTENDS)
return null;
lexer.next();
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("parent class name"));
return lexer.getId();
}
private void parseImport()
throws ESException
{
CharBuffer path = new CharBuffer();
lexer.next();
while (true) {
if (lexer.peek() == Lexer.BIN_OP && lexer.getOp() == '*') {
lexer.next();
path.append('*');
importList.add(path.close());
return;
}
if (lexer.peek() != Lexer.IDENTIFIER)
throw expect(L.l("identifier"));
path.append(lexer.getText());
lexer.next();
if (lexer.peek() != '.')
break;
lexer.next();
path.append('.');
}
String className = path.close();
String pathName = className.replace('.', '/') + ".js";
Path importPath = getScriptPath().lookup(pathName);
if (importPath.exists()) {
function.cl.addImport(pathName);
return;
}
try {
CauchoSystem.loadClass(className, false, getClassLoader());
importList.add(className);
} catch (ClassNotFoundException e) {
throw error(L.l("can't open import `{0}'", pathName));
}
}
/*
private void parseStatic() throws ESException
{
if (function.rest != null || staticCode == null)
throw error("illegal static statement");
lexer.next();
int oldVar = stmtVar;
ParseFun oldFun = function;
try {
stmtVar = -1;
function = staticFunction;
parseStatement(staticCode);
} finally {
function = oldFun;
stmtVar = oldVar;
}
} */
private Expr parseExpression(int prevPrec, boolean isTop)
throws ESException
{
Expr result = parseExprRec(parseTerm(isTop), prevPrec, false, isTop);
result.getType();
return result;
}
private Expr parseBooleanExpression(int prevPrec)
throws ESException
{
Expr result = parseExprRec(parseTerm(false), prevPrec, true, false);
result.getType();
return result;
}
private Expr parseExpression(int prevPrec)
throws ESException
{
Expr result = parseExprRec(parseTerm(false), prevPrec, false, false);
result.getType();
return result;
}
private Expr parseExpression()
throws ESException
{
Expr result = parseExprRec(parseTerm(false), PREC_MAX, false, false);
result.getType();
return result;
}
/*
* parseExpression ()
*
* Grammar:
* expr ::= obj (op obj)*
*/
private Expr parseExprRec(Expr lexpr, int prevPrec,
boolean isBool, boolean isTop)
throws ESException
{
Expr rexpr = null;
int op = 0;
int lex = 0;
int prec = 0;
while (true) {
boolean doLookahead = false;
boolean doPostfix = false;
boolean isRightAssoc = false;
int nextPrec = 0;
int nextOp = 0;
int nextLex = 0;
switch (lexer.peek()) {
case '=':
if (op != 0 && lex != ',')
throw error(L.l("illegal left hand side of assignment"));
if (isBool)
throw error(L.l("assignment used as boolean needs parentheses"));
case Lexer.BIN_OP:
// careful with and/or for unassigned local variables
if (lexer.getOp() == Lexer.AND || lexer.getOp() == Lexer.OR)
function.setVars();
// fall through
case ',':
case Lexer.BANDU_OP:
case '?':
nextLex = lexer.peek();
nextOp = lexer.getOp();
nextPrec = lexer.getPrecedence();
isRightAssoc = lexer.isRightAssoc();
doLookahead = true;
break;
default:
return op != 0 ? lexpr.binaryOp(lex, op, rexpr) : lexpr;
}
if (nextPrec >= prevPrec) {
return op != 0 ? lexpr.binaryOp(lex, op, rexpr) : lexpr;
}
else if (prec == 0) {
}
else if (nextPrec < prec) {
rexpr = parseExprRec(rexpr, prec, isBool, isTop);
continue;
}
else {
lexpr = op != 0 ? lexpr.binaryOp(lex, op, rexpr) : lexpr;
}
prec = nextPrec;
lex = nextLex;
op = nextOp;
if (doLookahead)
lexer.next();
if (isRightAssoc) {
/* XXX: is this the right thing to do? + 1 */
rexpr = parseExpression(prec + 1, isTop);
}
else if (op == '?') {
function.setVars();
Expr mexpr = parseExpression(Parser.PREC_ASSIGN + 1);
if (lexer.peek() != ':')
throw expect("`:'");
lexer.next();
rexpr = parseExpression(Parser.PREC_ASSIGN + 1, isTop);
lexpr = lexpr.conditional(mexpr, rexpr);
op = 0;
}
else
rexpr = parseTerm(isTop);
}
}
/*
* parseTerm
*
* term ::= BANDU_OP term
* ::= UNARY_OP term
* ::= IDENTIFIER termTail
* ::= LITERAL termTail
* ::= objLiteral termTail
* ::= '(' expr ')' termTail
*/
private Expr parseTerm(boolean isTop) throws ESException
{
int op;
switch (lexer.peek()) {
case Lexer.BANDU_OP:
case Lexer.UNARY_OP:
lexer.next();
op = lexer.getOp();
return parseTerm(isTop).unaryOp(op);
case Lexer.VOID:
lexer.next();
return parseTerm(isTop).doVoid();
case Lexer.TYPEOF:
lexer.next();
return parseTerm(isTop).typeof();
case Lexer.DELETE:
lexer.next();
return parseTerm(isTop).delete();
case Lexer.POSTFIX:
lexer.next();
op = lexer.getOp();
return parseTerm(isTop).prefix(op);
case Lexer.LITERAL:
case Lexer.REGEXP:
case Lexer.IDENTIFIER:
case Lexer.THIS:
case Lexer.EVAL:
case Lexer.NEW:
case '(':
case '{':
case '[':
case Lexer.HASH_REF:
case Lexer.HASH_DEF:
case Lexer.FUNCTION:
return parseLhs(false, isTop);
default:
throw expect(L.l("expression"));
}
}
/**
* Parses the left-hand side of an expression
*
* @param hasNew true if this follows a new
* @return the new expression
*/
private Expr parseLhs(boolean hasNew, boolean isTop)
throws ESException
{
String name;
int op;
Expr expr = null;
switch (lexer.next()) {
case Lexer.NEW:
return parseTermTail(parseLhs(true, isTop), hasNew, isTop);
case Lexer.LITERAL:
return parseTermTail(block.newLiteral(lexer.getLiteral()),
hasNew, isTop);
case Lexer.REGEXP:
/*
return parseTermTail(block.newRegexp(lexer.getLiteral(),
lexer.getFlags()),
hasNew, isTop);
*/
throw new UnsupportedOperationException();
case Lexer.IDENTIFIER:
return parseTermTail(getVar(lexer.getId()),
hasNew, isTop);
case Lexer.THIS:
return parseTermTail(block.newThis(), hasNew, isTop);
case Lexer.EVAL:
if (lexer.peek() != '(')
throw expect("`('");
function.setArguments();
return parseTermTail(block.newVar(ESId.intern("eval")), hasNew, false);
case '(':
expr = parseExpression(PREC_MAX);
if (lexer.next() != ')')
throw expect("`)'");
return parseTermTail(expr, hasNew, isTop);
case '{':
expr = parseObjectLiteral(-1);
if (lexer.next() != '}')
throw expect("`}'");
return parseTermTail(expr, hasNew, isTop);
case '[':
expr = parseArrayLiteral(-1);
if (lexer.next() != ']')
throw expect("`]'");
return parseTermTail(expr, hasNew, isTop);
case Lexer.FUNCTION:
Function newFun = parseFunction();
//function.addFunction(newFun);
function.addVariable(block, newFun.id, null);
block.newVar(newFun.id).getVar().setType(Expr.TYPE_ES);
expr = block.newVar(newFun.id);
return parseTermTail(expr, hasNew, isTop);
case Lexer.HASH_DEF:
switch (lexer.peek()) {
case '{':
lexer.next();
expr = parseObjectLiteral(lexer.intValue);
if (lexer.next() != '}')
throw expect("`}'");
return parseTermTail(expr, hasNew, isTop);
case '[':
lexer.next();
expr = parseArrayLiteral(lexer.intValue);
if (lexer.next() != ']')
throw expect("`]'");
return parseTermTail(expr, hasNew, isTop);
default:
/* XXX:
expr = parseLhs(hasNew, isTop);
int var = code.newVar();
code.store(var);
hashes.add(lexer.intValue, var);
code.load(var);
*/
return expr;
}
/*
case Lexer.HASH_REF:
if (hashes.size() <= lexer.intValue ||
hashes.get(lexer.intValue) <= 0)
throw error("bad sharp reference at " + getToken());
return parseTermTail(code.load(hashes.get(lexer.intValue)),
hasNew, isTop);
*/
default:
throw expect(L.l("term"));
}
}
/*
* parseTermTail
*
* termTail ::=
* ::= '.' Id termTail
* ::= '(' exprList ')' termTail
* ::= '[' expr ']' termTail
* ::= '++' termTail
*/
private Expr parseTermTail(Expr term, boolean hasNew, boolean isTop)
throws ESException
{
int op;
while (true) {
switch (lexer.peek()) {
case '.':
lexer.next();
if (lexer.next() != Lexer.IDENTIFIER)
throw expect(L.l("property name"));
term = term.fieldReference(lexer.getId());
break;
case '(':
if (isTop && lexer.seenLineFeed())
return term;
lexer.next();
int n = 0;
CallExpr call;
if (hasNew)
call = term.startNew();
else
call = term.startCall();
while (lexer.peek() != ')') {
if (n != 0 && lexer.peek() != ',')
throw expect("`,'");
else if (n != 0)
lexer.next();
call.addCallParam(parseExpression(PREC_COMMA));
n++;
}
lexer.next();
if (hasNew)
return call;
else
term = call;
break;
case '[':
if (isTop && lexer.seenLineFeed())
return term;
lexer.next();
term = term.fieldReference(parseExpression(PREC_MAX));
if (lexer.next() != ']')
throw expect("`]'");
break;
case Lexer.POSTFIX:
if (hasNew)
return term.startNew();
if (lexer.seenLineFeed())
return term;
term = term.postfix(lexer.getOp());
lexer.next();
break;
case '@':
lexer.next();
term = term.cast(parseType());
break;
default:
if (hasNew)
return term.startNew();
else
return term;
}
}
}
private Expr parseObjectLiteral(int hash) throws ESException
{
Expr expr = block.newVar(ESId.intern("Object"));
CallExpr call = expr.startCall();
/*
if (hash >= 0) {
if (hashes.size() <= hash)
hashes.setLength(hash + 1);
hashes.add(hash, var);
}
*/
if (lexer.peek() == ',') {
lexer.next();
return call;
}
while (lexer.peek() == Lexer.LITERAL ||
lexer.peek() == Lexer.IDENTIFIER) {
ESId id;
if (lexer.next() == Lexer.LITERAL)
id = ESId.intern(lexer.literal.toString());
else
id = lexer.getId();
if (lexer.next() != ':')
throw expect("`:'");
call.addCallParam(block.newLiteral(id));
call.addCallParam(parseExpression(PREC_COMMA));
if (lexer.peek() != ',')
break;
lexer.next();
}
return call;
}
private Expr parseArrayLiteral(int hash)
throws ESException
{
Expr expr = block.newVar(ESId.intern("Array"));
CallExpr call = expr.startCall();
boolean isFirst = true;
while (lexer.peek() != ']') {
if (lexer.peek() == ',') {
lexer.next();
call.addCallParam(block.newLiteral(ESBase.esUndefined));
isFirst = false;
continue;
}
Expr value = parseExpression(PREC_COMMA);
if (isFirst && lexer.peek() == ']')
return block.newArray(value);
if (lexer.peek() != ',') {
call.addCallParam(value);
break;
}
lexer.next();
if (isFirst && lexer.peek() == ']')
return block.newArray(value);
isFirst = false;
call.addCallParam(value);
}
return call;
}
/**
* Gets a variable instance.
*/
private Expr getVar(ESId name)
throws ESException
{
if (name == PACKAGES)
return new PackageExpr(block);
else if (name == JAVA)
return new PackageExpr(block).fieldReference(JAVA);
else if (name == CAUCHO)
return new PackageExpr(block).fieldReference(COM).fieldReference(CAUCHO);
else if (block.hasVar(name))
return block.newVar(name);
else {
for (int i = 0; i < importList.size(); i++) {
String className = (String) importList.get(i);
if (className.endsWith(".*"))
className = className.substring(0, className.length() - 1) + name;
try {
Class cl = CauchoSystem.loadClass(className, false,
getClassLoader());
return new JavaClassExpr(block, cl);
} catch (Throwable e) {
}
}
return block.newVar(name);
}
}
/**
* Returns the current filename being parsed.
*/
public String getFilename()
{
return lexer.getFilename();
}
/**
* Creates an error message with the given text.
*/
ESException error(String text)
{
return lexer.error(text);
}
/**
* Returns the current token for an error message.
*/
private String getToken()
{
if (lexer.isEof())
return "end of file";
else
return "`" + lexer.getToken() + "'";
}
/**
* Returns a parse exception when expecting a result.
*/
private ESException expect(String expect)
{
try {
return lexer.error(L.l("expected {0} at {1}", expect, getToken()));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}