diff --git a/.idea/modules.xml b/.idea/modules.xml index 71d1bb10964..4c80b02dfd9 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -35,6 +35,7 @@ + diff --git a/build.xml b/build.xml index 7181062551e..957f399bd5e 100644 --- a/build.xml +++ b/build.xml @@ -78,6 +78,7 @@ + @@ -100,6 +101,7 @@ + @@ -165,6 +167,7 @@ + diff --git a/js/js.parser/js.parser.iml b/js/js.parser/js.parser.iml new file mode 100644 index 00000000000..38ca9ad2a9d --- /dev/null +++ b/js/js.parser/js.parser.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsParser.java b/js/js.parser/src/com/google/gwt/dev/js/JsParser.java new file mode 100644 index 00000000000..46586e0f5a8 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/JsParser.java @@ -0,0 +1,1290 @@ +/* + * Copyright 2008 Google Inc. + * + * 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. + */ +package com.google.gwt.dev.js; + +import com.google.gwt.dev.jjs.Correlation.Literal; +import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.SourceOrigin; +import com.google.gwt.dev.js.ast.JsArrayAccess; +import com.google.gwt.dev.js.ast.JsArrayLiteral; +import com.google.gwt.dev.js.ast.JsBinaryOperation; +import com.google.gwt.dev.js.ast.JsBinaryOperator; +import com.google.gwt.dev.js.ast.JsBlock; +import com.google.gwt.dev.js.ast.JsBooleanLiteral; +import com.google.gwt.dev.js.ast.JsBreak; +import com.google.gwt.dev.js.ast.JsCase; +import com.google.gwt.dev.js.ast.JsCatch; +import com.google.gwt.dev.js.ast.JsConditional; +import com.google.gwt.dev.js.ast.JsContinue; +import com.google.gwt.dev.js.ast.JsDebugger; +import com.google.gwt.dev.js.ast.JsDefault; +import com.google.gwt.dev.js.ast.JsDoWhile; +import com.google.gwt.dev.js.ast.JsEmpty; +import com.google.gwt.dev.js.ast.JsExprStmt; +import com.google.gwt.dev.js.ast.JsExpression; +import com.google.gwt.dev.js.ast.JsFor; +import com.google.gwt.dev.js.ast.JsForIn; +import com.google.gwt.dev.js.ast.JsFunction; +import com.google.gwt.dev.js.ast.JsIf; +import com.google.gwt.dev.js.ast.JsInvocation; +import com.google.gwt.dev.js.ast.JsLabel; +import com.google.gwt.dev.js.ast.JsName; +import com.google.gwt.dev.js.ast.JsNameRef; +import com.google.gwt.dev.js.ast.JsNew; +import com.google.gwt.dev.js.ast.JsNode; +import com.google.gwt.dev.js.ast.JsNullLiteral; +import com.google.gwt.dev.js.ast.JsNumberLiteral; +import com.google.gwt.dev.js.ast.JsObjectLiteral; +import com.google.gwt.dev.js.ast.JsParameter; +import com.google.gwt.dev.js.ast.JsPostfixOperation; +import com.google.gwt.dev.js.ast.JsPrefixOperation; +import com.google.gwt.dev.js.ast.JsPropertyInitializer; +import com.google.gwt.dev.js.ast.JsRegExp; +import com.google.gwt.dev.js.ast.JsReturn; +import com.google.gwt.dev.js.ast.JsRootScope; +import com.google.gwt.dev.js.ast.JsScope; +import com.google.gwt.dev.js.ast.JsStatement; +import com.google.gwt.dev.js.ast.JsStringLiteral; +import com.google.gwt.dev.js.ast.JsSwitch; +import com.google.gwt.dev.js.ast.JsThisRef; +import com.google.gwt.dev.js.ast.JsThrow; +import com.google.gwt.dev.js.ast.JsTry; +import com.google.gwt.dev.js.ast.JsUnaryOperator; +import com.google.gwt.dev.js.ast.JsVars; +import com.google.gwt.dev.js.ast.JsWhile; +import com.google.gwt.dev.js.rhino.Context; +import com.google.gwt.dev.js.rhino.ErrorReporter; +import com.google.gwt.dev.js.rhino.EvaluatorException; +import com.google.gwt.dev.js.rhino.IRFactory; +import com.google.gwt.dev.js.rhino.Node; +import com.google.gwt.dev.js.rhino.Parser; +import com.google.gwt.dev.js.rhino.TokenStream; + +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +/** + * Parses JavaScript source. + */ +public class JsParser { + + public static List parse(SourceInfo rootSourceInfo, + JsScope scope, Reader r) throws IOException, JsParserException { + return new JsParser().parseImpl(rootSourceInfo, scope, r); + } + + public static void parseInto(SourceInfo rootSourceInfo, JsScope scope, + JsBlock block, Reader r) throws IOException, JsParserException { + List childStmts = parse(rootSourceInfo, scope, r); + List parentStmts = block.getStatements(); + parentStmts.addAll(childStmts); + } + + private final Stack scopeStack = new Stack(); + private final Stack sourceInfoStack = new Stack(); + + private JsParser() { + } + + List parseImpl(final SourceInfo rootSourceInfo, JsScope scope, + Reader r) throws JsParserException, IOException { + // Create a custom error handler so that we can throw our own exceptions. + Context.enter().setErrorReporter(new ErrorReporter() { + @Override + public void error(String msg, String loc, int ln, String src, int col) { + throw new UncheckedJsParserException(new JsParserException(msg, ln, + src, col, rootSourceInfo.getFileName())); + } + + @Override + public EvaluatorException runtimeError(String msg, String loc, int ln, + String src, int col) { + // Never called, but just in case. + throw new UncheckedJsParserException(new JsParserException(msg, ln, + src, col, rootSourceInfo.getFileName())); + } + + @Override + public void warning(String msg, String loc, int ln, String src, int col) { + // Ignore warnings. + } + }); + try { + // Parse using the Rhino parser. + // + TokenStream ts = new TokenStream(r, rootSourceInfo.getFileName(), + rootSourceInfo.getStartLine()); + Parser parser = new Parser(new IRFactory(ts)); + Node topNode = (Node) parser.parse(ts); + + // Map the Rhino AST to ours. + pushScope(scope, rootSourceInfo); + List stmts = mapStatements(topNode); + popScope(); + return stmts; + } catch (UncheckedJsParserException e) { + throw e.getParserException(); + } finally { + Context.exit(); + } + } + + private JsParserException createParserException(String msg, Node offender) { + return new JsParserException(msg, offender.getLineno(), null, 0, + sourceInfoStack.peek().getFileName()); + } + + private JsScope getScope() { + return scopeStack.peek(); + } + + private SourceInfo makeSourceInfo(Node node) { + SourceInfo parent = sourceInfoStack.peek(); + int lineno = node.getLineno(); + if (lineno == -1) { + // Rhino only reports line numbers for statement nodes, not expressions + return parent; + } + return parent.makeChild(SourceOrigin.create(lineno, parent.getFileName())); + } + + /** + * Force a distinct child to be created, so correlations can be added. + */ + private SourceInfo makeSourceInfoDistinct(Node node) { + SourceInfo parent = sourceInfoStack.peek(); + int lineno = node.getLineno(); + if (lineno == -1) { + // Rhino only reports line numbers for statement nodes, not expressions + lineno = parent.getStartLine(); + } + return parent.makeChild(SourceOrigin.create(lineno, parent.getFileName())); + } + + private JsNode map(Node node) throws JsParserException { + + switch (node.getType()) { + case TokenStream.SCRIPT: { + JsBlock block = new JsBlock(makeSourceInfo(node)); + mapStatements(block.getStatements(), node); + return block; + } + + case TokenStream.DEBUGGER: + return mapDebuggerStatement(node); + + case TokenStream.VOID: + // VOID = nothing was parsed for this node + return null; + + case TokenStream.EXPRSTMT: + return mapExprStmt(node); + + case TokenStream.REGEXP: + return mapRegExp(node); + + case TokenStream.ADD: + return mapBinaryOperation(JsBinaryOperator.ADD, node); + + case TokenStream.SUB: + return mapBinaryOperation(JsBinaryOperator.SUB, node); + + case TokenStream.MUL: + return mapBinaryOperation(JsBinaryOperator.MUL, node); + + case TokenStream.DIV: + return mapBinaryOperation(JsBinaryOperator.DIV, node); + + case TokenStream.MOD: + return mapBinaryOperation(JsBinaryOperator.MOD, node); + + case TokenStream.AND: + return mapBinaryOperation(JsBinaryOperator.AND, node); + + case TokenStream.OR: + return mapBinaryOperation(JsBinaryOperator.OR, node); + + case TokenStream.BITAND: + return mapBinaryOperation(JsBinaryOperator.BIT_AND, node); + + case TokenStream.BITOR: + return mapBinaryOperation(JsBinaryOperator.BIT_OR, node); + + case TokenStream.BITXOR: + return mapBinaryOperation(JsBinaryOperator.BIT_XOR, node); + + case TokenStream.ASSIGN: + return mapAssignmentVariant(node); + + case TokenStream.RELOP: + return mapRelationalVariant(node); + + case TokenStream.EQOP: + return mapEqualityVariant(node); + + case TokenStream.SHOP: + return mapShiftVariant(node); + + case TokenStream.UNARYOP: + return mapUnaryVariant(node); + + case TokenStream.INC: + return mapIncDecFixity(JsUnaryOperator.INC, node); + + case TokenStream.DEC: + return mapIncDecFixity(JsUnaryOperator.DEC, node); + + case TokenStream.HOOK: + return mapConditional(node); + + case TokenStream.STRING: { + SourceInfo info = makeSourceInfoDistinct(node); + info.addCorrelation(info.getCorrelator().by(Literal.STRING)); + return new JsStringLiteral(info, node.getString()); + } + + case TokenStream.NUMBER: + return mapNumber(node); + + case TokenStream.CALL: + return mapCall(node); + + case TokenStream.GETPROP: + return mapGetProp(node); + + case TokenStream.SETPROP: + return mapSetProp(node); + + case TokenStream.DELPROP: + return mapDeleteProp(node); + + case TokenStream.IF: + return mapIfStatement(node); + + case TokenStream.WHILE: + return mapDoOrWhileStatement(true, node); + + case TokenStream.DO: + return mapDoOrWhileStatement(false, node); + + case TokenStream.FOR: + return mapForStatement(node); + + case TokenStream.WITH: + return mapWithStatement(node); + + case TokenStream.GETELEM: + return mapGetElem(node); + + case TokenStream.SETELEM: + return mapSetElem(node); + + case TokenStream.FUNCTION: + return mapFunction(node); + + case TokenStream.BLOCK: + return mapBlock(node); + + case TokenStream.SETNAME: + return mapBinaryOperation(JsBinaryOperator.ASG, node); + + case TokenStream.NAME: + case TokenStream.BINDNAME: + return mapName(node); + + case TokenStream.RETURN: + return mapReturn(node); + + case TokenStream.BREAK: + return mapBreak(node); + + case TokenStream.CONTINUE: + return mapContinue(node); + + case TokenStream.OBJLIT: + return mapObjectLit(node); + + case TokenStream.ARRAYLIT: + return mapArrayLit(node); + + case TokenStream.VAR: + return mapVar(node); + + case TokenStream.PRIMARY: + return mapPrimary(node); + + case TokenStream.COMMA: + return mapBinaryOperation(JsBinaryOperator.COMMA, node); + + case TokenStream.NEW: + return mapNew(node); + + case TokenStream.THROW: + return mapThrowStatement(node); + + case TokenStream.TRY: + return mapTryStatement(node); + + case TokenStream.SWITCH: + return mapSwitchStatement(node); + + case TokenStream.LABEL: + return mapLabel(node); + + default: + int tokenType = node.getType(); + throw createParserException("Unexpected top-level token type: " + + tokenType, node); + } + } + + private JsArrayLiteral mapArrayLit(Node node) throws JsParserException { + JsArrayLiteral toLit = new JsArrayLiteral(makeSourceInfo(node)); + Node from = node.getFirstChild(); + while (from != null) { + toLit.getExpressions().add(mapExpression(from)); + from = from.getNext(); + } + return toLit; + } + + /** + * Produces a {@link JsNameRef}. + */ + private JsNameRef mapAsPropertyNameRef(Node nameRefNode) + throws JsParserException { + JsNode unknown = map(nameRefNode); + // This is weird, but for "a.b", the rhino AST calls "b" a string literal. + // However, since we know it's for a PROPGET, we can unstringliteralize it. + // + if (unknown instanceof JsStringLiteral) { + JsStringLiteral lit = (JsStringLiteral) unknown; + String litName = lit.getValue(); + return new JsNameRef(makeSourceInfo(nameRefNode), litName); + } else { + throw createParserException("Expecting a name reference", nameRefNode); + } + } + + private JsExpression mapAssignmentVariant(Node asgNode) + throws JsParserException { + switch (asgNode.getIntDatum()) { + case TokenStream.NOP: + return mapBinaryOperation(JsBinaryOperator.ASG, asgNode); + + case TokenStream.ADD: + return mapBinaryOperation(JsBinaryOperator.ASG_ADD, asgNode); + + case TokenStream.SUB: + return mapBinaryOperation(JsBinaryOperator.ASG_SUB, asgNode); + + case TokenStream.MUL: + return mapBinaryOperation(JsBinaryOperator.ASG_MUL, asgNode); + + case TokenStream.DIV: + return mapBinaryOperation(JsBinaryOperator.ASG_DIV, asgNode); + + case TokenStream.MOD: + return mapBinaryOperation(JsBinaryOperator.ASG_MOD, asgNode); + + case TokenStream.BITAND: + return mapBinaryOperation(JsBinaryOperator.ASG_BIT_AND, asgNode); + + case TokenStream.BITOR: + return mapBinaryOperation(JsBinaryOperator.ASG_BIT_OR, asgNode); + + case TokenStream.BITXOR: + return mapBinaryOperation(JsBinaryOperator.ASG_BIT_XOR, asgNode); + + case TokenStream.LSH: + return mapBinaryOperation(JsBinaryOperator.ASG_SHL, asgNode); + + case TokenStream.RSH: + return mapBinaryOperation(JsBinaryOperator.ASG_SHR, asgNode); + + case TokenStream.URSH: + return mapBinaryOperation(JsBinaryOperator.ASG_SHRU, asgNode); + + default: + throw createParserException("Unknown assignment operator variant: " + + asgNode.getIntDatum(), asgNode); + } + } + + private JsExpression mapBinaryOperation(JsBinaryOperator op, Node node) + throws JsParserException { + Node from1 = node.getFirstChild(); + Node from2 = from1.getNext(); + + JsExpression to1 = mapExpression(from1); + JsExpression to2 = mapExpression(from2); + + return new JsBinaryOperation(makeSourceInfo(node), op, to1, to2); + } + + private JsBlock mapBlock(Node nodeStmts) throws JsParserException { + SourceInfo info = makeSourceInfo(nodeStmts); + JsBlock block = new JsBlock(info); + pushSourceInfo(info); + mapStatements(block.getStatements(), nodeStmts); + popSourceInfo(); + return block; + } + + private JsBreak mapBreak(Node breakNode) { + Node fromLabel = breakNode.getFirstChild(); + if (fromLabel != null) { + return new JsBreak(makeSourceInfo(breakNode), mapName(fromLabel)); + } else { + return new JsBreak(makeSourceInfo(breakNode)); + } + } + + private JsInvocation mapCall(Node callNode) throws JsParserException { + JsInvocation invocation = new JsInvocation(makeSourceInfo(callNode)); + + // Map the target expression. + // + Node from = callNode.getFirstChild(); + JsExpression to = mapExpression(from); + invocation.setQualifier(to); + + // Iterate over and map the arguments. + // + List args = invocation.getArguments(); + from = from.getNext(); + while (from != null) { + to = mapExpression(from); + args.add(to); + from = from.getNext(); + } + + return invocation; + } + + private JsExpression mapConditional(Node condNode) throws JsParserException { + JsConditional toCond = new JsConditional(makeSourceInfo(condNode)); + + Node fromTest = condNode.getFirstChild(); + toCond.setTestExpression(mapExpression(fromTest)); + + Node fromThen = fromTest.getNext(); + toCond.setThenExpression(mapExpression(fromThen)); + + Node fromElse = fromThen.getNext(); + toCond.setElseExpression(mapExpression(fromElse)); + + return toCond; + } + + private JsContinue mapContinue(Node contNode) { + Node fromLabel = contNode.getFirstChild(); + if (fromLabel != null) { + return new JsContinue(makeSourceInfo(contNode), mapName(fromLabel)); + } else { + return new JsContinue(makeSourceInfo(contNode)); + } + } + + private JsStatement mapDebuggerStatement(Node node) { + // Calls an optional method to invoke the debugger. + // + return new JsDebugger(makeSourceInfo(node)); + } + + private JsExpression mapDeleteProp(Node node) throws JsParserException { + Node from = node.getFirstChild(); + JsExpression to = mapExpression(from); + if (to instanceof JsNameRef) { + return new JsPrefixOperation(makeSourceInfo(node), + JsUnaryOperator.DELETE, to); + } else if (to instanceof JsArrayAccess) { + return new JsPrefixOperation(makeSourceInfo(node), + JsUnaryOperator.DELETE, to); + } else { + throw createParserException( + "'delete' can only operate on property names and array elements", + from); + } + } + + private JsStatement mapDoOrWhileStatement(boolean isWhile, Node ifNode) + throws JsParserException { + + // Pull out the pieces we want to map. + // + Node fromTestExpr; + Node fromBody; + if (isWhile) { + fromTestExpr = ifNode.getFirstChild(); + fromBody = ifNode.getFirstChild().getNext(); + } else { + fromBody = ifNode.getFirstChild(); + fromTestExpr = ifNode.getFirstChild().getNext(); + } + + SourceInfo info = makeSourceInfo(ifNode); + pushSourceInfo(info); + + // Map the test expression. + // + JsExpression toTestExpr = mapExpression(fromTestExpr); + + // Map the body block. + // + JsStatement toBody = mapStatement(fromBody); + + popSourceInfo(); + + // Create and attach the "while" or "do" statement we're mapping to. + // + if (isWhile) { + return new JsWhile(info, toTestExpr, toBody); + } else { + return new JsDoWhile(info, toTestExpr, toBody); + } + } + + private JsExpression mapEqualityVariant(Node eqNode) throws JsParserException { + switch (eqNode.getIntDatum()) { + case TokenStream.EQ: + return mapBinaryOperation(JsBinaryOperator.EQ, eqNode); + + case TokenStream.NE: + return mapBinaryOperation(JsBinaryOperator.NEQ, eqNode); + + case TokenStream.SHEQ: + return mapBinaryOperation(JsBinaryOperator.REF_EQ, eqNode); + + case TokenStream.SHNE: + return mapBinaryOperation(JsBinaryOperator.REF_NEQ, eqNode); + + case TokenStream.LT: + return mapBinaryOperation(JsBinaryOperator.LT, eqNode); + + case TokenStream.LE: + return mapBinaryOperation(JsBinaryOperator.LTE, eqNode); + + case TokenStream.GT: + return mapBinaryOperation(JsBinaryOperator.GT, eqNode); + + case TokenStream.GE: + return mapBinaryOperation(JsBinaryOperator.GTE, eqNode); + + default: + throw createParserException("Unknown equality operator variant: " + + eqNode.getIntDatum(), eqNode); + } + } + + private JsExpression mapExpression(Node exprNode) throws JsParserException { + JsNode unknown = map(exprNode); + if (unknown instanceof JsExpression) { + return (JsExpression) unknown; + } else { + throw createParserException("Expecting an expression", exprNode); + } + } + + private JsExprStmt mapExprStmt(Node node) throws JsParserException { + pushSourceInfo(makeSourceInfo(node)); + JsExpression expr = mapExpression(node.getFirstChild()); + popSourceInfo(); + return expr.makeStmt(); + } + + private JsStatement mapForStatement(Node forNode) throws JsParserException { + Node fromInit = forNode.getFirstChild(); + Node fromTest = fromInit.getNext(); + Node fromIncr = fromTest.getNext(); + Node fromBody = fromIncr.getNext(); + + SourceInfo info = makeSourceInfo(forNode); + if (fromBody == null) { + // This could be a "for...in" structure. + // We could based on the different child layout. + // + Node fromIter = forNode.getFirstChild(); + Node fromObjExpr = fromIter.getNext(); + fromBody = fromObjExpr.getNext(); + + JsForIn toForIn; + if (fromIter.getType() == TokenStream.VAR) { + // A named iterator var. + // + Node fromIterVarName = fromIter.getFirstChild(); + String fromName = fromIterVarName.getString(); + JsName toName = getScope().declareName(fromName); + toForIn = new JsForIn(info, toName); + Node fromIterInit = fromIterVarName.getFirstChild(); + if (fromIterInit != null) { + // That has an initializer expression (useful only for side effects). + // + toForIn.setIterExpr(mapOptionalExpression(fromIterInit)); + } + } else { + // An unnamed iterator var. + // + toForIn = new JsForIn(info); + toForIn.setIterExpr(mapExpression(fromIter)); + } + toForIn.setObjExpr(mapExpression(fromObjExpr)); + + // The body stmt. + // + JsStatement bodyStmt = mapStatement(fromBody); + if (bodyStmt != null) { + toForIn.setBody(bodyStmt); + } else { + toForIn.setBody(new JsEmpty(info)); + } + + return toForIn; + } else { + // Regular ol' for loop. + // + JsFor toFor = new JsFor(info); + + // The first item is either an expression or a JsVars. + JsNode initThingy = map(fromInit); + if (initThingy != null) { + if (initThingy instanceof JsVars) { + toFor.setInitVars((JsVars) initThingy); + } else { + assert (initThingy instanceof JsExpression); + toFor.setInitExpr((JsExpression) initThingy); + } + } + toFor.setCondition(mapOptionalExpression(fromTest)); + toFor.setIncrExpr(mapOptionalExpression(fromIncr)); + + JsStatement bodyStmt = mapStatement(fromBody); + if (bodyStmt != null) { + toFor.setBody(bodyStmt); + } else { + toFor.setBody(new JsEmpty(info)); + } + return toFor; + } + } + + private JsExpression mapFunction(Node fnNode) throws JsParserException { + + Node fromFnNameNode = fnNode.getFirstChild(); + Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild(); + Node fromBodyNode = fnNode.getFirstChild().getNext().getNext(); + + // Decide the function's name, if any. + // + String fromFnName = fromFnNameNode.getString(); + JsName toFnName = null; + if (fromFnName != null && fromFnName.length() > 0) { + toFnName = getScope().declareName(fromFnName); + } + + // Create it, and set the params. + // + SourceInfo fnSourceInfo = makeSourceInfo(fnNode); + JsFunction toFn = new JsFunction(fnSourceInfo, getScope(), toFnName); + + // Creating a function also creates a new scope, which we push onto + // the scope stack. + // + pushScope(toFn.getScope(), fnSourceInfo); + + while (fromParamNode != null) { + String fromParamName = fromParamNode.getString(); + // should this be unique? I think not since you can have dup args. + JsName paramName = toFn.getScope().declareName(fromParamName); + toFn.getParameters().add(new JsParameter(fnSourceInfo, paramName)); + fromParamNode = fromParamNode.getNext(); + } + + // Map the function's body. + // + JsBlock toBody = mapBlock(fromBodyNode); + toFn.setBody(toBody); + + // Pop the new function's scope off of the scope stack. + // + popScope(); + + return toFn; + } + + private JsArrayAccess mapGetElem(Node getElemNode) throws JsParserException { + Node from1 = getElemNode.getFirstChild(); + Node from2 = from1.getNext(); + + JsExpression to1 = mapExpression(from1); + JsExpression to2 = mapExpression(from2); + + return new JsArrayAccess(makeSourceInfo(getElemNode), to1, to2); + } + + private JsNameRef mapGetProp(Node getPropNode) throws JsParserException { + Node from1 = getPropNode.getFirstChild(); + Node from2 = from1.getNext(); + + JsExpression toQualifier = mapExpression(from1); + JsNameRef toNameRef; + if (from2 != null) { + toNameRef = mapAsPropertyNameRef(from2); + } else { + // Special properties don't have a second expression. + // + Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP); + assert (obj instanceof String); + toNameRef = new JsNameRef(makeSourceInfo(getPropNode), (String) obj); + } + toNameRef.setQualifier(toQualifier); + + return toNameRef; + } + + private JsIf mapIfStatement(Node ifNode) throws JsParserException { + + // Pull out the pieces we want to map. + // + Node fromTestExpr = ifNode.getFirstChild(); + Node fromThenBlock = ifNode.getFirstChild().getNext(); + Node fromElseBlock = ifNode.getFirstChild().getNext().getNext(); + + // Create the "if" statement we're mapping to. + // + JsIf toIf = new JsIf(makeSourceInfo(ifNode)); + + // Map the test expression. + // + JsExpression toTestExpr = mapExpression(fromTestExpr); + toIf.setIfExpr(toTestExpr); + + // Map the "then" block. + // + toIf.setThenStmt(mapStatement(fromThenBlock)); + + // Map the "else" block. + // + if (fromElseBlock != null) { + toIf.setElseStmt(mapStatement(fromElseBlock)); + } + + return toIf; + } + + private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node) + throws JsParserException { + switch (node.getIntDatum()) { + case TokenStream.PRE: + return mapPrefixOperation(op, node); + case TokenStream.POST: + return mapPostfixOperation(op, node); + default: + throw createParserException( + "Unknown prefix/postfix variant: " + node.getIntDatum(), node); + } + } + + private JsLabel mapLabel(Node labelNode) throws JsParserException { + String fromName = labelNode.getFirstChild().getString(); + JsName toName = getScope().declareName(fromName); + Node fromStmt = labelNode.getFirstChild().getNext(); + JsLabel toLabel = new JsLabel(makeSourceInfo(labelNode), toName); + toLabel.setStmt(mapStatement(fromStmt)); + return toLabel; + } + + /** + * Creates a reference to a name that may or may not be obfuscatable, based on + * whether it matches a known name in the scope. + */ + private JsNameRef mapName(Node node) { + String ident = node.getString(); + return new JsNameRef(makeSourceInfo(node), ident); + } + + private JsNew mapNew(Node newNode) throws JsParserException { + // Map the constructor expression, which is often just the name of + // some lambda. + // + Node fromCtorExpr = newNode.getFirstChild(); + JsNew newExpr = new JsNew(makeSourceInfo(newNode), + mapExpression(fromCtorExpr)); + + // Iterate over and map the arguments. + // + List args = newExpr.getArguments(); + Node fromArg = fromCtorExpr.getNext(); + while (fromArg != null) { + args.add(mapExpression(fromArg)); + fromArg = fromArg.getNext(); + } + + return newExpr; + } + + private JsExpression mapNumber(Node numberNode) { + return new JsNumberLiteral(makeSourceInfo(numberNode), + numberNode.getDouble()); + } + + private JsExpression mapObjectLit(Node objLitNode) throws JsParserException { + JsObjectLiteral toLit = new JsObjectLiteral(makeSourceInfo(objLitNode)); + Node fromPropInit = objLitNode.getFirstChild(); + while (fromPropInit != null) { + + Node fromLabelExpr = fromPropInit; + JsExpression toLabelExpr = mapExpression(fromLabelExpr); + + // Advance to the initializer expression. + // + fromPropInit = fromPropInit.getNext(); + Node fromValueExpr = fromPropInit; + if (fromValueExpr == null) { + throw createParserException("Expected an init expression for: " + + toLabelExpr, objLitNode); + } + JsExpression toValueExpr = mapExpression(fromValueExpr); + + JsPropertyInitializer toPropInit = new JsPropertyInitializer( + makeSourceInfo(fromLabelExpr), toLabelExpr, toValueExpr); + toLit.getPropertyInitializers().add(toPropInit); + + // Begin the next property initializer, if there is one. + // + fromPropInit = fromPropInit.getNext(); + } + + return toLit; + } + + private JsExpression mapOptionalExpression(Node exprNode) + throws JsParserException { + JsNode unknown = map(exprNode); + if (unknown != null) { + if (unknown instanceof JsExpression) { + return (JsExpression) unknown; + } else { + throw createParserException("Expecting an expression or null", exprNode); + } + } + return null; + } + + private JsExpression mapPostfixOperation(JsUnaryOperator op, Node node) + throws JsParserException { + Node from = node.getFirstChild(); + JsExpression to = mapExpression(from); + return new JsPostfixOperation(makeSourceInfo(node), op, to); + } + + private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node) + throws JsParserException { + Node from = node.getFirstChild(); + JsExpression to = mapExpression(from); + return new JsPrefixOperation(makeSourceInfo(node), op, to); + } + + private JsExpression mapPrimary(Node node) throws JsParserException { + switch (node.getIntDatum()) { + case TokenStream.THIS: + return new JsThisRef(makeSourceInfo(node)); + + case TokenStream.TRUE: + return JsBooleanLiteral.TRUE; + + case TokenStream.FALSE: + return JsBooleanLiteral.FALSE; + + case TokenStream.NULL: + return JsNullLiteral.INSTANCE; + + case TokenStream.UNDEFINED: + return new JsNameRef(makeSourceInfo(node), + JsRootScope.INSTANCE.getUndefined()); + + default: + throw createParserException("Unknown primary: " + node.getIntDatum(), + node); + } + } + + private JsNode mapRegExp(Node regExpNode) { + JsRegExp toRegExp = new JsRegExp(makeSourceInfo(regExpNode)); + + Node fromPattern = regExpNode.getFirstChild(); + toRegExp.setPattern(fromPattern.getString()); + + Node fromFlags = fromPattern.getNext(); + if (fromFlags != null) { + toRegExp.setFlags(fromFlags.getString()); + } + + return toRegExp; + } + + private JsExpression mapRelationalVariant(Node relNode) + throws JsParserException { + switch (relNode.getIntDatum()) { + case TokenStream.LT: + return mapBinaryOperation(JsBinaryOperator.LT, relNode); + + case TokenStream.LE: + return mapBinaryOperation(JsBinaryOperator.LTE, relNode); + + case TokenStream.GT: + return mapBinaryOperation(JsBinaryOperator.GT, relNode); + + case TokenStream.GE: + return mapBinaryOperation(JsBinaryOperator.GTE, relNode); + + case TokenStream.INSTANCEOF: + return mapBinaryOperation(JsBinaryOperator.INSTANCEOF, relNode); + + case TokenStream.IN: + return mapBinaryOperation(JsBinaryOperator.INOP, relNode); + + default: + throw createParserException("Unknown relational operator variant: " + + relNode.getIntDatum(), relNode); + } + } + + private JsReturn mapReturn(Node returnNode) throws JsParserException { + SourceInfo info = makeSourceInfo(returnNode); + JsReturn toReturn = new JsReturn(info); + pushSourceInfo(info); + Node from = returnNode.getFirstChild(); + if (from != null) { + JsExpression to = mapExpression(from); + toReturn.setExpr(to); + } + + popSourceInfo(); + return toReturn; + } + + private JsExpression mapSetElem(Node setElemNode) throws JsParserException { + // Reuse the get elem code. + // + JsArrayAccess lhs = mapGetElem(setElemNode); + + // Map the RHS. + // + Node fromRhs = setElemNode.getFirstChild().getNext().getNext(); + JsExpression toRhs = mapExpression(fromRhs); + + return new JsBinaryOperation(makeSourceInfo(setElemNode), + JsBinaryOperator.ASG, lhs, toRhs); + } + + private JsExpression mapSetProp(Node getPropNode) throws JsParserException { + // Reuse the get prop code. + // + JsNameRef lhs = mapGetProp(getPropNode); + + // Map the RHS. + // + Node fromRhs = getPropNode.getFirstChild().getNext().getNext(); + JsExpression toRhs = mapExpression(fromRhs); + + return new JsBinaryOperation(makeSourceInfo(getPropNode), + JsBinaryOperator.ASG, lhs, toRhs); + } + + private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException { + switch (shiftNode.getIntDatum()) { + case TokenStream.LSH: + return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode); + + case TokenStream.RSH: + return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode); + + case TokenStream.URSH: + return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode); + + default: + throw createParserException("Unknown equality operator variant: " + + shiftNode.getIntDatum(), shiftNode); + } + } + + private JsStatement mapStatement(Node nodeStmt) throws JsParserException { + JsNode unknown = map(nodeStmt); + if (unknown != null) { + if (unknown instanceof JsStatement) { + return (JsStatement) unknown; + } else if (unknown instanceof JsExpression) { + return ((JsExpression) unknown).makeStmt(); + } else { + throw createParserException("Expecting a statement", nodeStmt); + } + } else { + // When map() returns null, we return an empty statement. + // + return new JsEmpty(makeSourceInfo(nodeStmt)); + } + } + + private void mapStatements(List stmts, Node nodeStmts) + throws JsParserException { + Node curr = nodeStmts.getFirstChild(); + while (curr != null) { + JsStatement stmt = mapStatement(curr); + if (stmt != null) { + stmts.add(stmt); + } else { + // When mapStatement() returns null, we just ignore it. + // + } + curr = curr.getNext(); + } + } + + private List mapStatements(Node nodeStmts) + throws JsParserException { + List stmts = new ArrayList(); + mapStatements(stmts, nodeStmts); + return stmts; + } + + private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException { + SourceInfo info = makeSourceInfo(switchNode); + JsSwitch toSwitch = new JsSwitch(info); + pushSourceInfo(info); + + // The switch expression. + // + Node fromSwitchExpr = switchNode.getFirstChild(); + toSwitch.setExpr(mapExpression(fromSwitchExpr)); + + // The members. + // + Node fromMember = fromSwitchExpr.getNext(); + while (fromMember != null) { + if (fromMember.getType() == TokenStream.CASE) { + JsCase toCase = new JsCase(makeSourceInfo(fromMember)); + + // Set the case expression. In JS, this can be any expression. + // + Node fromCaseExpr = fromMember.getFirstChild(); + toCase.setCaseExpr(mapExpression(fromCaseExpr)); + + // Set the case statements. + // + Node fromCaseBlock = fromCaseExpr.getNext(); + mapStatements(toCase.getStmts(), fromCaseBlock); + + // Attach the case to the switch. + // + toSwitch.getCases().add(toCase); + } else { + // This should be the only default statement. + // If more than one is present, we keep the last one. + // + assert (fromMember.getType() == TokenStream.DEFAULT); + JsDefault toDefault = new JsDefault(makeSourceInfo(fromMember)); + + // Set the default statements. + // + Node fromDefaultBlock = fromMember.getFirstChild(); + mapStatements(toDefault.getStmts(), fromDefaultBlock); + + // Attach the default to the switch. + // + toSwitch.getCases().add(toDefault); + } + fromMember = fromMember.getNext(); + } + + popSourceInfo(); + return toSwitch; + } + + private JsThrow mapThrowStatement(Node throwNode) throws JsParserException { + SourceInfo info = makeSourceInfo(throwNode); + pushSourceInfo(info); + + // Create, map, and attach. + // + Node fromExpr = throwNode.getFirstChild(); + JsThrow toThrow = new JsThrow(info, mapExpression(fromExpr)); + + popSourceInfo(); + return toThrow; + } + + private JsTry mapTryStatement(Node tryNode) throws JsParserException { + JsTry toTry = new JsTry(makeSourceInfo(tryNode)); + + // Map the "try" body. + // + Node fromTryBody = tryNode.getFirstChild(); + toTry.setTryBlock(mapBlock(fromTryBody)); + + // Map zero or more catch blocks. + // + Node fromCatchNodes = fromTryBody.getNext(); + Node fromCatchNode = fromCatchNodes.getFirstChild(); + while (fromCatchNode != null) { + assert (fromCatchNode.getType() == TokenStream.CATCH); + // Map the catch variable. + // + Node fromCatchVarName = fromCatchNode.getFirstChild(); + JsCatch catchBlock = new JsCatch(makeSourceInfo(fromCatchNode), + getScope(), fromCatchVarName.getString()); + + // Pre-advance to the next catch block, if any. + // We do this here to decide whether or not this is the last one. + // + fromCatchNode = fromCatchNode.getNext(); + + // Map the condition, with a little fixup based on whether or not + // this is the last catch block. + // + Node fromCondition = fromCatchVarName.getNext(); + JsExpression toCondition = mapExpression(fromCondition); + catchBlock.setCondition(toCondition); + if (fromCatchNode == null) { + if (toCondition instanceof JsBooleanLiteral) { + if (((JsBooleanLiteral) toCondition).getValue()) { + // Actually, this is an unconditional catch block. + // Indicate that by nulling the condition. + // + catchBlock.setCondition(null); + } + } + } + + // Map the catch body. + // + Node fromCatchBody = fromCondition.getNext(); + pushScope(catchBlock.getScope(), catchBlock.getSourceInfo()); + catchBlock.setBody(mapBlock(fromCatchBody)); + popScope(); + + // Attach it. + // + toTry.getCatches().add(catchBlock); + } + + Node fromFinallyNode = fromCatchNodes.getNext(); + if (fromFinallyNode != null) { + toTry.setFinallyBlock(mapBlock(fromFinallyNode)); + } + + return toTry; + } + + private JsExpression mapUnaryVariant(Node unOp) throws JsParserException { + switch (unOp.getIntDatum()) { + case TokenStream.SUB: + return mapPrefixOperation(JsUnaryOperator.NEG, unOp); + + case TokenStream.NOT: + return mapPrefixOperation(JsUnaryOperator.NOT, unOp); + + case TokenStream.BITNOT: + return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp); + + case TokenStream.TYPEOF: + return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp); + + case TokenStream.ADD: + if (unOp.getFirstChild().getType() != TokenStream.NUMBER) { + return mapPrefixOperation(JsUnaryOperator.POS, unOp); + } else { + // Pretend we didn't see it. + return mapExpression(unOp.getFirstChild()); + } + + case TokenStream.VOID: + return mapPrefixOperation(JsUnaryOperator.VOID, unOp); + + default: + throw createParserException( + "Unknown unary operator variant: " + unOp.getIntDatum(), unOp); + } + } + + private JsVars mapVar(Node varNode) throws JsParserException { + SourceInfo info = makeSourceInfo(varNode); + pushSourceInfo(info); + JsVars toVars = new JsVars(info); + Node fromVar = varNode.getFirstChild(); + while (fromVar != null) { + // Use a conservative name allocation strategy that allocates all names + // from the function's scope, even the names of properties in field + // literals. + // + String fromName = fromVar.getString(); + JsName toName = getScope().declareName(fromName); + JsVars.JsVar toVar = new JsVars.JsVar(makeSourceInfo(fromVar), toName); + + Node fromInit = fromVar.getFirstChild(); + if (fromInit != null) { + JsExpression toInit = mapExpression(fromInit); + toVar.setInitExpr(toInit); + } + toVars.add(toVar); + + fromVar = fromVar.getNext(); + } + + popSourceInfo(); + return toVars; + } + + private JsNode mapWithStatement(Node withNode) throws JsParserException { + // The "with" statement is unsupported because it introduces ambiguity + // related to whether or not a name is obfuscatable that we cannot resolve + // statically. This is modified in our copy of the Rhino Parser to provide + // detailed source & line info. So, this method should never actually be + // called. + // + throw createParserException("Internal error: unexpected token 'with'", + withNode); + } + + private void popScope() { + scopeStack.pop(); + sourceInfoStack.pop(); + } + + private void popSourceInfo() { + sourceInfoStack.pop(); + } + + private void pushScope(JsScope scope, SourceInfo sourceInfo) { + scopeStack.push(scope); + sourceInfoStack.push(sourceInfo); + } + + /** + * This should be called when processing any Rhino statement Node that has + * line number data so that enclosed expressions will have a useful source + * location. + * + * @see Node#hasLineno + */ + private void pushSourceInfo(SourceInfo sourceInfo) { + assert sourceInfo.getStartLine() >= 0 : "Bad SourceInfo line number"; + sourceInfoStack.push(sourceInfo); + } +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/JsParserException.java b/js/js.parser/src/com/google/gwt/dev/js/JsParserException.java new file mode 100644 index 00000000000..1c42668e9fe --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/JsParserException.java @@ -0,0 +1,106 @@ +/* + * Copyright 2007 Google Inc. + * + * 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. + */ +package com.google.gwt.dev.js; + +/** + * Indicates inability to parse JavaScript source. + */ +public class JsParserException extends Exception { + + /** + * Represents the location of a parser exception. + */ + public static class SourceDetail { + private final String fileName; + private final int line; + private final int lineOffset; + private final String lineSource; + + public SourceDetail(int line, String lineSource, int lineOffset, + String fileName) { + this.line = line; + this.lineSource = lineSource; + this.lineOffset = lineOffset; + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } + + public int getLine() { + return line; + } + + public int getLineOffset() { + return lineOffset; + } + + public String getLineSource() { + return lineSource; + } + } + + private static String createMessageWithDetail(String msg, + SourceDetail sourceDetail) { + if (sourceDetail == null) { + return msg; + } + StringBuffer sb = new StringBuffer(); + sb.append(sourceDetail.getFileName()); + sb.append('('); + sb.append(sourceDetail.getLine()); + sb.append(')'); + sb.append(": "); + sb.append(msg); + if (sourceDetail.getLineSource() != null) { + sb.append("\n> "); + sb.append(sourceDetail.getLineSource()); + sb.append("\n> "); + for (int i = 0, n = sourceDetail.getLineOffset(); i < n; ++i) { + sb.append('-'); + } + sb.append('^'); + } + return sb.toString(); + } + + private final SourceDetail sourceDetail; + + public JsParserException(String msg) { + this(msg, null); + } + + public JsParserException(String msg, int line, String lineSource, + int lineOffset, String fileName) { + this(msg, new SourceDetail(line, lineSource, lineOffset, fileName)); + } + + public JsParserException(String msg, SourceDetail sourceDetail) { + super(createMessageWithDetail(msg, sourceDetail)); + this.sourceDetail = sourceDetail; + } + + /** + * Provides additional source detail in some cases. + * + * @return additional detail regarding the error, or null if no + * additional detail is available + */ + public SourceDetail getSourceDetail() { + return sourceDetail; + } +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/UncheckedJsParserException.java b/js/js.parser/src/com/google/gwt/dev/js/UncheckedJsParserException.java new file mode 100644 index 00000000000..a88462c8bb4 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/UncheckedJsParserException.java @@ -0,0 +1,30 @@ +/* + * Copyright 2007 Google Inc. + * + * 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. + */ +package com.google.gwt.dev.js; + +// An unchecked wrapper exception to interop with Rhino. +class UncheckedJsParserException extends RuntimeException { + + private final JsParserException parserException; + + public UncheckedJsParserException(JsParserException parserException) { + this.parserException = parserException; + } + + public JsParserException getParserException() { + return parserException; + } +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/BinaryDigitReader.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/BinaryDigitReader.java new file mode 100644 index 00000000000..ca7d7b6459f --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/BinaryDigitReader.java @@ -0,0 +1,76 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Waldemar Horwat + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +final class BinaryDigitReader { + int lgBase; // Logarithm of base of number + int digit; // Current digit value in radix given by base + int digitPos; // Bit position of last bit extracted from digit + String digits; // String containing the digits + int start; // Index of the first remaining digit + int end; // Index past the last remaining digit + + BinaryDigitReader(int base, String digits, int start, int end) { + lgBase = 0; + while (base != 1) { + lgBase++; + base >>= 1; + } + digitPos = 0; + this.digits = digits; + this.start = start; + this.end = end; + } + + /* Return the next binary digit from the number or -1 if done */ + int getNextBinaryDigit() + { + if (digitPos == 0) { + if (start == end) + return -1; + + char c = digits.charAt(start++); + if ('0' <= c && c <= '9') + digit = c - '0'; + else if ('a' <= c && c <= 'z') + digit = c - 'a' + 10; + else digit = c - 'A' + 10; + digitPos = lgBase; + } + return digit >> --digitPos & 1; + } +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/COPYING b/js/js.parser/src/com/google/gwt/dev/js/rhino/COPYING new file mode 100644 index 00000000000..96114e4d9c1 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/COPYING @@ -0,0 +1,36 @@ +The files in this package are taken from mozilla's Rhino project. +See http://www.mozilla.org/rhino/ + +The files modified from Rhino 1.5R3 +(ftp://ftp.mozilla.org/pub/js/rhino15R3.zip). + +-- + +The contents of this package are subject to the Netscape Public +License Version 1.1 (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.mozilla.org/NPL/ + +Software distributed under the License is distributed on an "AS +IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +implied. See the License for the specific language governing +rights and limitations under the License. + +The Original Code is Rhino code, released +May 6, 1999. + +The Initial Developer of the Original Code is Netscape +Communications Corporation. Portions created by Netscape are +Copyright (C) 1997-2000 Netscape Communications Corporation. All +Rights Reserved. + +Alternatively, the contents of this file may be used under the +terms of the GNU Public License (the "GPL"), in which case the +provisions of the GPL are applicable instead of those above. +If you wish to allow use of your version of this file only +under the terms of the GPL and not to allow others to use your +version of this file under the NPL, indicate your decision by +deleting the provisions above and replace them with the notice +and other provisions required by the GPL. If you do not delete +the provisions above, a recipient may use your version of this +file under either the NPL or the GPL. diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Context.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Context.java new file mode 100644 index 00000000000..ec41a9204ff --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Context.java @@ -0,0 +1,802 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Patrick Beard + * Norris Boyd + * Igor Bukanov + * Brendan Eich + * Roger Lawrence + * Mike McCabe + * Ian D. Stewart + * Andi Vajda + * Andrew Wason + * Kemal Bayram + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +// API class + +package com.google.gwt.dev.js.rhino; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.reflect.Method; +import java.text.MessageFormat; +import java.util.Hashtable; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * This class represents the runtime context of an executing script. + * + * Before executing a script, an instance of Context must be created + * and associated with the thread that will be executing the script. + * The Context will be used to store information about the executing + * of the script such as the call stack. Contexts are associated with + * the current thread using the enter() method.

+ * + * The behavior of the execution engine may be altered through methods + * such as setErrorReporter.

+ * + * Different forms of script execution are supported. Scripts may be + * evaluated from the source directly, or first compiled and then later + * executed. Interactive execution is also supported.

+ * + * Some aspects of script execution, such as type conversions and + * object creation, may be accessed directly through methods of + * Context. + * + * @see Scriptable + * @author Norris Boyd + * @author Brendan Eich + */ + +public class Context { + public static final String languageVersionProperty = "language version"; + public static final String errorReporterProperty = "error reporter"; + + /** + * Create a new Context. + * + * Note that the Context must be associated with a thread before + * it can be used to execute a script. + * + * @see org.mozilla.javascript.Context#enter + */ + public Context() { + setLanguageVersion(VERSION_DEFAULT); + } + + /** + * Get a context associated with the current thread, creating + * one if need be. + * + * The Context stores the execution state of the JavaScript + * engine, so it is required that the context be entered + * before execution may begin. Once a thread has entered + * a Context, then getCurrentContext() may be called to find + * the context that is associated with the current thread. + *

+ * Calling enter() will + * return either the Context currently associated with the + * thread, or will create a new context and associate it + * with the current thread. Each call to enter() + * must have a matching call to exit(). For example, + *

+     *      Context cx = Context.enter();
+     *      try {
+     *          ...
+     *          cx.evaluateString(...);
+     *      }
+     *      finally { Context.exit(); }
+     * 
+ * @return a Context associated with the current thread + * @see org.mozilla.javascript.Context#getCurrentContext + * @see org.mozilla.javascript.Context#exit + */ + public static Context enter() { + return enter(null); + } + + /** + * Get a Context associated with the current thread, using + * the given Context if need be. + *

+ * The same as enter() except that cx + * is associated with the current thread and returned if + * the current thread has no associated context and cx + * is not associated with any other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + */ + public static Context enter(Context cx) { + + Context old = getCurrentContext(); + + if (cx == null) { + if (old != null) { + cx = old; + } else { + cx = new Context(); + setThreadContext(cx); + } + } else { + if (cx.enterCount != 0) { + // The suplied context must be the context for + // the current thread if it is already entered + if (cx != old) { + throw new RuntimeException + ("Cannot enter Context active on another thread"); + } + } else { + if (old != null) { + cx = old; + } else { + setThreadContext(cx); + } + } + } + + ++cx.enterCount; + + return cx; + } + + /** + * Exit a block of code requiring a Context. + * + * Calling exit() will remove the association between + * the current thread and a Context if the prior call to + * enter() on this thread newly associated a Context + * with this thread. + * Once the current thread no longer has an associated Context, + * it cannot be used to execute JavaScript until it is again associated + * with a Context. + * + * @see org.mozilla.javascript.Context#enter + */ + public static void exit() { + boolean released = false; + Context cx = getCurrentContext(); + if (cx == null) { + throw new RuntimeException + ("Calling Context.exit without previous Context.enter"); + } + if (Context.check && cx.enterCount < 1) Context.codeBug(); + --cx.enterCount; + if (cx.enterCount == 0) { + released = true; + setThreadContext(null); + } + } + + /** + * Get the current Context. + * + * The current Context is per-thread; this method looks up + * the Context associated with the current thread.

+ * + * @return the Context associated with the current thread, or + * null if no context is associated with the current + * thread. + * @see org.mozilla.javascript.Context#enter + * @see org.mozilla.javascript.Context#exit + */ + public static Context getCurrentContext() { + if (threadLocalCx != null) { + try { + return (Context)threadLocalGet.invoke(threadLocalCx, (Object[]) null); + } catch (Exception ex) { } + } + Thread t = Thread.currentThread(); + return (Context) threadContexts.get(t); + } + + private static void setThreadContext(Context cx) { + if (threadLocalCx != null) { + try { + threadLocalSet.invoke(threadLocalCx, new Object[] { cx }); + return; + } catch (Exception ex) { } + } + Thread t = Thread.currentThread(); + if (cx != null) { + threadContexts.put(t, cx); + } else { + threadContexts.remove(t); + } + } + + /** + * Language versions + * + * All integral values are reserved for future version numbers. + */ + + /** + * The unknown version. + */ + public static final int VERSION_UNKNOWN = -1; + + /** + * The default version. + */ + public static final int VERSION_DEFAULT = 0; + + /** + * JavaScript 1.0 + */ + public static final int VERSION_1_0 = 100; + + /** + * JavaScript 1.1 + */ + public static final int VERSION_1_1 = 110; + + /** + * JavaScript 1.2 + */ + public static final int VERSION_1_2 = 120; + + /** + * JavaScript 1.3 + */ + public static final int VERSION_1_3 = 130; + + /** + * JavaScript 1.4 + */ + public static final int VERSION_1_4 = 140; + + /** + * JavaScript 1.5 + */ + public static final int VERSION_1_5 = 150; + + /** + * Get the current language version. + *

+ * The language version number affects JavaScript semantics as detailed + * in the overview documentation. + * + * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc. + */ + public int getLanguageVersion() { + return version; + } + + /** + * Set the language version. + * + *

+ * Setting the language version will affect functions and scripts compiled + * subsequently. See the overview documentation for version-specific + * behavior. + * + * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc. + */ + public void setLanguageVersion(int version) { + this.version = version; + } + + /** + * Get the implementation version. + * + *

+ * The implementation version is of the form + *

+     *    "name langVer release relNum date"
+     * 
+ * where name is the name of the product, langVer is + * the language version, relNum is the release number, and + * date is the release date for that specific + * release in the form "yyyy mm dd". + * + * @return a string that encodes the product, language version, release + * number, and date. + */ + public String getImplementationVersion() { + return "Rhino 1.5 release 4.1 2003 04 21"; + } + + /** + * Get the current error reporter. + * + * @see org.mozilla.javascript.ErrorReporter + */ + public ErrorReporter getErrorReporter() { + return errorReporter; + } + + /** + * Change the current error reporter. + * + * @return the previous error reporter + * @see org.mozilla.javascript.ErrorReporter + */ + public ErrorReporter setErrorReporter(ErrorReporter reporter) { + errorReporter = reporter; + return reporter; + } + + /** + * Get the current locale. Returns the default locale if none has + * been set. + * + * @see java.util.Locale + */ + + public Locale getLocale() { + if (locale == null) + locale = Locale.getDefault(); + return locale; + } + + /** + * Set the current locale. + * + * @see java.util.Locale + */ + public Locale setLocale(Locale loc) { + Locale result = locale; + locale = loc; + return result; + } + + /** + * Notify any registered listeners that a bounded property has changed + * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + * @see java.beans.PropertyChangeListener + * @see java.beans.PropertyChangeEvent + * @param property the bound property + * @param oldValue the old value + * @param newVale the new value + */ + void firePropertyChange(String property, Object oldValue, + Object newValue) + { + Object[] array = listeners; + if (array != null) { + firePropertyChangeImpl(array, property, oldValue, newValue); + } + } + + private void firePropertyChangeImpl(Object[] array, String property, + Object oldValue, Object newValue) + { + for (int i = array.length; i-- != 0;) { + Object obj = array[i]; + if (obj instanceof PropertyChangeListener) { + PropertyChangeListener l = (PropertyChangeListener)obj; + l.propertyChange(new PropertyChangeEvent( + this, property, oldValue, newValue)); + } + } + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportWarning(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = Context.getContext(); + cx.getErrorReporter().warning(message, sourceName, lineno, + lineSource, lineOffset); + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @see org.mozilla.javascript.ErrorReporter + */ + /* + public static void reportWarning(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportWarning(message, filename, linep[0], null, 0); + } + */ + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportError(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + cx.errorCount++; + cx.getErrorReporter().error(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message); + } + } + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + /* + public static void reportError(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportError(message, filename, linep[0], null, 0); + } + */ + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return a runtime exception that will be thrown to terminate the + * execution of the script + * @see org.mozilla.javascript.ErrorReporter + */ + /* + public static EvaluatorException reportRuntimeError(String message, + String sourceName, + int lineno, + String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + cx.errorCount++; + return cx.getErrorReporter(). + runtimeError(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message); + } + } + + static EvaluatorException reportRuntimeError0(String messageId) { + return reportRuntimeError(getMessage0(messageId)); + } + + static EvaluatorException reportRuntimeError1 + (String messageId, Object arg1) + { + return reportRuntimeError(getMessage1(messageId, arg1)); + } + + static EvaluatorException reportRuntimeError2 + (String messageId, Object arg1, Object arg2) + { + return reportRuntimeError(getMessage2(messageId, arg1, arg2)); + } + + static EvaluatorException reportRuntimeError3 + (String messageId, Object arg1, Object arg2, Object arg3) + { + return reportRuntimeError(getMessage3(messageId, arg1, arg2, arg3)); + } + */ + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + /* + public static EvaluatorException reportRuntimeError(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + return Context.reportRuntimeError(message, filename, linep[0], null, 0); + } + */ + + /** + * Get a value corresponding to a key. + *

+ * Since the Context is associated with a thread it can be + * used to maintain values that can be later retrieved using + * the current thread. + *

+ * Note that the values are maintained with the Context, so + * if the Context is disassociated from the thread the values + * cannot be retreived. Also, if private data is to be maintained + * in this manner the key should be a java.lang.Object + * whose reference is not divulged to untrusted code. + * @param key the key used to lookup the value + * @return a value previously stored using putThreadLocal. + */ + public final Object getThreadLocal(Object key) { + if (hashtable == null) + return null; + return hashtable.get(key); + } + + /** + * Put a value that can later be retrieved using a given key. + *

+ * @param key the key used to index the value + * @param value the value to save + */ + public void putThreadLocal(Object key, Object value) { + if (hashtable == null) + hashtable = new Hashtable(); + hashtable.put(key, value); + } + + /** + * Remove values from thread-local storage. + * @param key the key for the entry to remove. + * @since 1.5 release 2 + */ + public void removeThreadLocal(Object key) { + if (hashtable == null) + return; + hashtable.remove(key); + } + + /** + * Return whether functions are compiled by this context using + * dynamic scope. + *

+ * If functions are compiled with dynamic scope, then they execute + * in the scope of their caller, rather than in their parent scope. + * This is useful for sharing functions across multiple scopes. + * @since 1.5 Release 1 + */ + public final boolean hasCompileFunctionsWithDynamicScope() { + return compileFunctionsWithDynamicScopeFlag; + } + + /** + * Set whether functions compiled by this context should use + * dynamic scope. + *

+ * @param flag if true, compile functions with dynamic scope + * @since 1.5 Release 1 + */ + public void setCompileFunctionsWithDynamicScope(boolean flag) { + compileFunctionsWithDynamicScopeFlag = flag; + } + + /** + * if hasFeature(FEATURE_NON_ECMA_GET_YEAR) returns true, + * Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000 + * in deviation with Ecma B.2.4 + */ + public static final int FEATURE_NON_ECMA_GET_YEAR = 1; + + /** + * if hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME) returns true, + * allow 'function (...) { ... }' to be syntax sugar for + * ' = function(...) { ... }', when + * is not simply identifier. + * See Ecma-262, section 11.2 for definition of + */ + public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2; + + /** + * if hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER) returns true, + * treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary + * identifiers but warn about this usage + */ + public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3; + + /** + * if hasFeature(FEATURE_TO_STRING_AS_SOURCE) returns true, + * calling toString on JS objects gives JS source with code to create an + * object with all enumeratable fields of the original object instead of + * printing "[object ]". + * By default {@link #hasFeature(int)} returns true only if + * the current JS version is set to {@link #VERSION_1_2}. + */ + public static final int FEATURE_TO_STRING_AS_SOURCE = 4; + + /** + * Controls certain aspects of script semantics. + * Should be overwritten to alter default behavior. + * @param featureIndex feature index to check + * @return true if the featureIndex feature is turned on + * @see #FEATURE_NON_ECMA_GET_YEAR + * @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME + * @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER + * @see #FEATURE_TO_STRING_AS_SOURCE + */ + public boolean hasFeature(int featureIndex) { + switch (featureIndex) { + case FEATURE_NON_ECMA_GET_YEAR: + /* + * During the great date rewrite of 1.3, we tried to track the + * evolving ECMA standard, which then had a definition of + * getYear which always subtracted 1900. Which we + * implemented, not realizing that it was incompatible with + * the old behavior... now, rather than thrash the behavior + * yet again, we've decided to leave it with the - 1900 + * behavior and point people to the getFullYear method. But + * we try to protect existing scripts that have specified a + * version... + */ + return (version == Context.VERSION_1_0 + || version == Context.VERSION_1_1 + || version == Context.VERSION_1_2); + + case FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME: + return false; + + case FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER: + return false; + + case FEATURE_TO_STRING_AS_SOURCE: + return version == VERSION_1_2; + } + // It is a bug to call the method with unknown featureIndex + throw new IllegalArgumentException(); + } + + /********** end of API **********/ + + static String getMessage0(String messageId) { + return getMessage(messageId, null); + } + + static String getMessage1(String messageId, Object arg1) { + Object[] arguments = {arg1}; + return getMessage(messageId, arguments); + } + + static String getMessage2(String messageId, Object arg1, Object arg2) { + Object[] arguments = {arg1, arg2}; + return getMessage(messageId, arguments); + } + + static String getMessage3 + (String messageId, Object arg1, Object arg2, Object arg3) { + Object[] arguments = {arg1, arg2, arg3}; + return getMessage(messageId, arguments); + } + /** + * Internal method that reports an error for missing calls to + * enter(). + */ + static Context getContext() { + Context cx = getCurrentContext(); + if (cx == null) { + throw new RuntimeException( + "No Context associated with current Thread"); + } + return cx; + } + + /* OPT there's a noticable delay for the first error! Maybe it'd + * make sense to use a ListResourceBundle instead of a properties + * file to avoid (synchronized) text parsing. + */ + // bruce: removed referenced to the initial "java" package name + // that used to be there due to a build artifact + static final String defaultResource = + "com.google.gwt.dev.js.rhino.Messages"; + + + static String getMessage(String messageId, Object[] arguments) { + Context cx = getCurrentContext(); + Locale locale = cx != null ? cx.getLocale() : Locale.getDefault(); + + // ResourceBundle does cacheing. + ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale); + + String formatString; + try { + formatString = rb.getString(messageId); + } catch (java.util.MissingResourceException mre) { + throw new RuntimeException + ("no message resource found for message property "+ messageId); + } + + /* + * It's OK to format the string, even if 'arguments' is null; + * we need to format it anyway, to make double ''s collapse to + * single 's. + */ + // TODO: MessageFormat is not available on pJava + MessageFormat formatter = new MessageFormat(formatString); + return formatter.format(arguments); + } + + // debug flags + static final boolean printTrees = true; + static final boolean printICode = true; + + final boolean isVersionECMA1() { + return version == VERSION_DEFAULT || version >= VERSION_1_3; + } + + +// Rudimentary support for Design-by-Contract + static void codeBug() { + throw new RuntimeException("FAILED ASSERTION"); + } + + static final boolean check = true; + + private static Hashtable threadContexts = new Hashtable(11); + private static Object threadLocalCx; + private static Method threadLocalGet; + private static Method threadLocalSet; + + int version; + int errorCount; + + private ErrorReporter errorReporter; + private Locale locale; + private boolean generatingDebug; + private boolean generatingDebugChanged; + private boolean generatingSource=true; + private boolean compileFunctionsWithDynamicScopeFlag; + private int enterCount; + private Object[] listeners; + private Hashtable hashtable; + private ClassLoader applicationClassLoader; + + /** + * This is the list of names of objects forcing the creation of + * function activation records. + */ + private Hashtable activationNames; + + // For instruction counting (interpreter only) + int instructionCount; + int instructionThreshold; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/DToA.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/DToA.java new file mode 100644 index 00000000000..43500db51bb --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/DToA.java @@ -0,0 +1,1216 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Waldemar Horwat + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +package com.google.gwt.dev.js.rhino; + +import java.math.BigInteger; + +class DToA { + + +/* "-0.0000...(1073 zeros after decimal point)...0001\0" is the longest string that we could produce, + * which occurs when printing -5e-324 in binary. We could compute a better estimate of the size of + * the output string and malloc fewer bytes depending on d and base, but why bother? */ + + static final int DTOBASESTR_BUFFER_SIZE = 1078; + + static char BASEDIGIT(int digit) { + return (char)((digit >= 10) ? 'a' - 10 + digit : '0' + digit); + } + + static final int + DTOSTR_STANDARD = 0, /* Either fixed or exponential format; round-trip */ + DTOSTR_STANDARD_EXPONENTIAL = 1, /* Always exponential format; round-trip */ + DTOSTR_FIXED = 2, /* Round to digits after the decimal point; exponential if number is large */ + DTOSTR_EXPONENTIAL = 3, /* Always exponential format; significant digits */ + DTOSTR_PRECISION = 4; /* Either fixed or exponential format; significant digits */ + + + static final int Frac_mask = 0xfffff; + static final int Exp_shift = 20; + static final int Exp_msk1 = 0x100000; + static final int Bias = 1023; + static final int P = 53; + + static final int Exp_shift1 = 20; + static final int Exp_mask = 0x7ff00000; + static final int Bndry_mask = 0xfffff; + static final int Log2P = 1; + + static final int Sign_bit = 0x80000000; + static final int Exp_11 = 0x3ff00000; + static final int Ten_pmax = 22; + static final int Quick_max = 14; + static final int Bletch = 0x10; + static final int Frac_mask1 = 0xfffff; + static final int Int_max = 14; + static final int n_bigtens = 5; + + + static final double tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 + }; + + static final double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; + + static int lo0bits(int y) + { + int k; + int x = y; + + if ((x & 7) != 0) { + if ((x & 1) != 0) + return 0; + if ((x & 2) != 0) { + return 1; + } + return 2; + } + k = 0; + if ((x & 0xffff) == 0) { + k = 16; + x >>>= 16; + } + if ((x & 0xff) == 0) { + k += 8; + x >>>= 8; + } + if ((x & 0xf) == 0) { + k += 4; + x >>>= 4; + } + if ((x & 0x3) == 0) { + k += 2; + x >>>= 2; + } + if ((x & 1) == 0) { + k++; + x >>>= 1; + if ((x & 1) == 0) + return 32; + } + return k; + } + + /* Return the number (0 through 32) of most significant zero bits in x. */ + static int hi0bits(int x) + { + int k = 0; + + if ((x & 0xffff0000) == 0) { + k = 16; + x <<= 16; + } + if ((x & 0xff000000) == 0) { + k += 8; + x <<= 8; + } + if ((x & 0xf0000000) == 0) { + k += 4; + x <<= 4; + } + if ((x & 0xc0000000) == 0) { + k += 2; + x <<= 2; + } + if ((x & 0x80000000) == 0) { + k++; + if ((x & 0x40000000) == 0) + return 32; + } + return k; + } + + static void stuffBits(byte bits[], int offset, int val) + { + bits[offset] = (byte)(val >> 24); + bits[offset + 1] = (byte)(val >> 16); + bits[offset + 2] = (byte)(val >> 8); + bits[offset + 3] = (byte)(val); + } + + /* Convert d into the form b*2^e, where b is an odd integer. b is the returned + * Bigint and e is the returned binary exponent. Return the number of significant + * bits in b in bits. d must be finite and nonzero. */ + static BigInteger d2b(double d, int[] e, int[] bits) + { + byte dbl_bits[]; + int i, k, y, z, de; + long dBits = Double.doubleToLongBits(d); + int d0 = (int)(dBits >>> 32); + int d1 = (int)(dBits); + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ + + if ((de = (int)(d0 >>> Exp_shift)) != 0) + z |= Exp_msk1; + + if ((y = d1) != 0) { + dbl_bits = new byte[8]; + k = lo0bits(y); + y >>>= k; + if (k != 0) { + stuffBits(dbl_bits, 4, y | z << (32 - k)); + z >>= k; + } + else + stuffBits(dbl_bits, 4, y); + stuffBits(dbl_bits, 0, z); + i = (z != 0) ? 2 : 1; + } + else { + // JS_ASSERT(z); + dbl_bits = new byte[4]; + k = lo0bits(z); + z >>>= k; + stuffBits(dbl_bits, 0, z); + k += 32; + i = 1; + } + if (de != 0) { + e[0] = de - Bias - (P-1) + k; + bits[0] = P - k; + } + else { + e[0] = de - Bias - (P-1) + 1 + k; + bits[0] = 32*i - hi0bits(z); + } + return new BigInteger(dbl_bits); + } + + public static String JS_dtobasestr(int base, double d) + { + char[] buffer; /* The output string */ + int p; /* index to current position in the buffer */ + int pInt; /* index to the beginning of the integer part of the string */ + + int q; + int digit; + double di; /* d truncated to an integer */ + double df; /* The fractional part of d */ + +// JS_ASSERT(base >= 2 && base <= 36); + + buffer = new char[DTOBASESTR_BUFFER_SIZE]; + + p = 0; + if (d < 0.0) { + buffer[p++] = '-'; + d = -d; + } + + /* Check for Infinity and NaN */ + if (Double.isNaN(d)) + return "NaN"; + else + if (Double.isInfinite(d)) + return "Infinity"; + + /* Output the integer part of d with the digits in reverse order. */ + pInt = p; + di = (int)d; + BigInteger b = BigInteger.valueOf((int)di); + String intDigits = b.toString(base); + intDigits.getChars(0, intDigits.length(), buffer, p); + p += intDigits.length(); + + df = d - di; + if (df != 0.0) { + /* We have a fraction. */ + buffer[p++] = '.'; + + long dBits = Double.doubleToLongBits(d); + int word0 = (int)(dBits >> 32); + int word1 = (int)(dBits); + + int[] e = new int[1]; + int[] bbits = new int[1]; + + b = d2b(df, e, bbits); +// JS_ASSERT(e < 0); + /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */ + + int s2 = -(word0 >>> Exp_shift1 & Exp_mask >> Exp_shift1); + if (s2 == 0) + s2 = -1; + s2 += Bias + P; + /* 1/2^s2 = (nextDouble(d) - d)/2 */ +// JS_ASSERT(-s2 < e); + BigInteger mlo = BigInteger.valueOf(1); + BigInteger mhi = mlo; + if ((word1 == 0) && ((word0 & Bndry_mask) == 0) + && ((word0 & (Exp_mask & Exp_mask << 1)) != 0)) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the output string's value is less than d. */ + s2 += Log2P; + mhi = BigInteger.valueOf(1< df = b/2^s2 > 0; + * (d - prevDouble(d))/2 = mlo/2^s2; + * (nextDouble(d) - d)/2 = mhi/2^s2. */ + BigInteger bigBase = BigInteger.valueOf(base); + + boolean done = false; + do { + b = b.multiply(bigBase); + BigInteger[] divResult = b.divideAndRemainder(s); + b = divResult[1]; + digit = (char)(divResult[0].intValue()); + if (mlo == mhi) + mlo = mhi = mlo.multiply(bigBase); + else { + mlo = mlo.multiply(bigBase); + mhi = mhi.multiply(bigBase); + } + + /* Do we yet have the shortest string that will round to d? */ + int j = b.compareTo(mlo); + /* j is b/2^s2 compared with mlo/2^s2. */ + BigInteger delta = s.subtract(mhi); + int j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */ + if (j1 == 0 && ((word1 & 1) == 0)) { + if (j > 0) + digit++; + done = true; + } else + if (j < 0 || (j == 0 && ((word1 & 1) == 0))) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant digit. + Use whichever would produce an output value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(s); + if (j1 > 0) /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output + * such as 3.5 in base 3. */ + digit++; + } + done = true; + } else if (j1 > 0) { + digit++; + done = true; + } +// JS_ASSERT(digit < (uint32)base); + buffer[p++] = BASEDIGIT(digit); + } while (!done); + } + + return new String(buffer, 0, p); + } + + /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + + static int word0(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits >> 32); + } + + static double setWord0(double d, int i) + { + long dBits = Double.doubleToLongBits(d); + dBits = ((long)i << 32) | (dBits & 0x0FFFFFFFFL); + return Double.longBitsToDouble(dBits); + } + + static int word1(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits); + } + + /* Return b * 5^k. k must be nonnegative. */ + // XXXX the C version built a cache of these + static BigInteger pow5mult(BigInteger b, int k) + { + return b.multiply(BigInteger.valueOf(5).pow(k)); + } + + static boolean roundOff(StringBuffer buf) + { + char lastCh; + while ((lastCh = buf.charAt(buf.length() - 1)) == '9') { + buf.setLength(buf.length() - 1); + if (buf.length() == 0) { + return true; + } + } + buf.append((char)(lastCh + 1)); + return false; + } + + /* Always emits at least one digit. */ + /* If biasUp is set, then rounding in modes 2 and 3 will round away from zero + * when the number is exactly halfway between two representable values. For example, + * rounding 2.5 to zero digits after the decimal point will return 3 and not 2. + * 2.49 will still round to 2, and 2.51 will still round to 3. */ + /* bufsize should be at least 20 for modes 0 and 1. For the other modes, + * bufsize should be two greater than the maximum number of output characters expected. */ + static int + JS_dtoa(double d, int mode, boolean biasUp, int ndigits, + boolean[] sign, StringBuffer buf) + { + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4-9 should give the same return values as 2-3, i.e., + 4 <= mode <= 9 ==> same return as mode + 2 + (mode & 1). These modes are mainly for + debugging; often they run slower but sometimes + faster than modes 2-3. + 4,5,8,9 ==> left-to-right digit generation. + 6-9 ==> don't try fast floating-point estimate + (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int b2, b5, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, m2, m5, s2, s5; + char dig; + long L; + long x; + BigInteger b, b1, delta, mlo, mhi, S; + int[] be = new int[1]; + int[] bbits = new int[1]; + double d2, ds, eps; + boolean spec_case, denorm, k_check, try_quick, leftright; + + if ((word0(d) & Sign_bit) != 0) { + /* set sign for everything, including 0's and NaNs */ + sign[0] = true; + // word0(d) &= ~Sign_bit; /* clear sign bit */ + d = setWord0(d, word0(d) & ~Sign_bit); + } + else + sign[0] = false; + + if ((word0(d) & Exp_mask) == Exp_mask) { + /* Infinity or NaN */ + buf.append(((word1(d) == 0) && ((word0(d) & Frac_mask) == 0)) ? "Infinity" : "NaN"); + return 9999; + } + if (d == 0) { +// no_digits: + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + + b = d2b(d, be, bbits); + if ((i = (int)(word0(d) >>> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { + d2 = setWord0(d, (word0(d) & Frac_mask1) | Exp_11); + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + i -= Bias; + denorm = false; + } + else { + /* d is denormalized */ + i = bbits[0] + be[0] + (Bias + (P-1) - 1); + x = (i > 32) ? word0(d) << (64 - i) | word1(d) >>> (i - 32) : word1(d) << (32 - i); +// d2 = x; +// word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + d2 = setWord0(x, word0(x) - 31*Exp_msk1); + i -= (Bias + (P-1) - 1) + 1; + denorm = true; + } + /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */ + ds = (d2-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0.0 && ds != k) + k--; /* want k = floor(ds) */ + k_check = true; + if (k >= 0 && k <= Ten_pmax) { + if (d < tens[k]) + k--; + k_check = false; + } + /* At this point floor(log10(d)) <= k <= floor(log10(d))+1. + If k_check is zero, we're guaranteed that k = floor(log10(d)). */ + j = bbits[0] - i - 1; + /* At this point d = b/2^j, where b is an odd integer. */ + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer, + b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */ + if (mode < 0 || mode > 9) + mode = 0; + try_quick = true; + if (mode > 5) { + mode -= 4; + try_quick = false; + } + leftright = true; + ilim = ilim1 = 0; + switch(mode) { + case 0: + case 1: + ilim = ilim1 = -1; + i = 18; + ndigits = 0; + break; + case 2: + leftright = false; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = false; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + /* ilim is the maximum number of significant digits we want, based on k and ndigits. */ + /* ilim1 is the maximum number of significant digits we want, based on k and ndigits, + when it turns out that k was computed too high by one. */ + + boolean fast_failed = false; + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + d2 = d; + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if ((j & Bletch) != 0) { + /* prevent overflows */ + j &= Bletch - 1; + d /= bigtens[n_bigtens-1]; + ieps++; + } + for(; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + ds *= bigtens[i]; + } + d /= ds; + } + else if ((j1 = -k) != 0) { + d *= tens[j1 & 0xf]; + for(j = j1 >> 4; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + d *= bigtens[i]; + } + } + /* Check that k was computed correctly. */ + if (k_check && d < 1.0 && ilim > 0) { + if (ilim1 <= 0) + fast_failed = true; + else { + ilim = ilim1; + k--; + d *= 10.; + ieps++; + } + } + /* eps bounds the cumulative error. */ +// eps = ieps*d + 7.0; +// word0(eps) -= (P-1)*Exp_msk1; + eps = ieps*d + 7.0; + eps = setWord0(eps, word0(eps) - (P-1)*Exp_msk1); + if (ilim == 0) { + S = mhi = null; + d -= 5.0; + if (d > eps) { + buf.append('1'); + k++; + return k + 1; + } + if (d < -eps) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + fast_failed = true; + } + if (!fast_failed) { + fast_failed = true; + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + eps = 0.5/tens[ilim-1] - eps; + for(i = 0;;) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (d < eps) { + return k + 1; + } + if (1.0 - d < eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + if (++i >= ilim) + break; + eps *= 10.0; + d *= 10.0; + } + } + else { + /* Generate ilim digits, then fix them up. */ + eps *= tens[ilim-1]; + for(i = 1;; i++, d *= 10.0) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (i == ilim) { + if (d > 0.5 + eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + else + if (d < 0.5 - eps) { + while (buf.charAt(buf.length() - 1) == '0') + buf.setLength(buf.length() - 1); +// while(*--s == '0') ; +// s++; + return k + 1; + } + break; + } + } + } + } + if (fast_failed) { + buf.setLength(0); + d = d2; + k = k0; + ilim = ilim0; + } + } + + /* Do we have a "small" integer? */ + + if (be[0] >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = null; + if (ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds)) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + buf.append('1'); + k++; + return k + 1; + } + for(i = 1;; i++) { + L = (long) (d / ds); + d -= L*ds; + buf.append((char)('0' + L)); + if (i == ilim) { + d += d; + if ((d > ds) || (d == ds && (((L & 1) != 0) || biasUp))) { +// bump_up: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s = '0'; +// break; +// } +// ++*s++; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + } + break; + } + d *= 10.0; + if (d == 0) + break; + } + return k + 1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = null; + if (leftright) { + if (mode < 2) { + i = (denorm) ? be[0] + (Bias + (P-1) - 1 + 1) : 1 + P - bbits[0]; + /* i is 1 plus the number of trailing zero bits in d's significand. Thus, + (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */ + } + else { + j = ilim - 1; + if (m5 >= j) + m5 -= j; + else { + s5 += j -= m5; + b5 += j; + m5 = 0; + } + if ((i = ilim) < 0) { + m2 -= i; + i = 0; + } + /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */ + } + b2 += i; + s2 += i; + mhi = BigInteger.valueOf(1); + /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or + input (when mode < 2) significant digit, divided by 10^k. */ + } + /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in + b2, m2, and s2 without changing the equalities. */ + if (m2 > 0 && s2 > 0) { + i = (m2 < s2) ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + + /* Fold b5 into b and m5 into mhi. */ + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mhi.multiply(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and + (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */ + + S = BigInteger.valueOf(1); + if (s5 > 0) + S = pow5mult(S, s5); + /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and + (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */ + + /* Check for special case that d is a normalized power of 2. */ + spec_case = false; + if (mode < 2) { + if ( (word1(d) == 0) && ((word0(d) & Bndry_mask) == 0) + && ((word0(d) & (Exp_mask & Exp_mask << 1)) != 0) + ) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the decimal output string's value is less than d. */ + b2 += Log2P; + s2 += Log2P; + spec_case = true; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + byte [] S_bytes = S.toByteArray(); + int S_hiWord = 0; + for (int idx = 0; idx < 4; idx++) { + S_hiWord = (S_hiWord << 8); + if (idx < S_bytes.length) + S_hiWord |= (S_bytes[idx] & 0xFF); + } + if ((i = (((s5 != 0) ? 32 - hi0bits(S_hiWord) : 1) + s2) & 0x1f) != 0) + i = 32 - i; + /* i is the number of leading zero bits in the most significant word of S*2^s2. */ + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */ + if (b2 > 0) + b = b.shiftLeft(b2); + if (s2 > 0) + S = S.shiftLeft(s2); + /* Now we have d/10^k = b/S and + (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */ + if (k_check) { + if (b.compareTo(S) < 0) { + k--; + b = b.multiply(BigInteger.valueOf(10)); /* we botched the k estimate */ + if (leftright) + mhi = mhi.multiply(BigInteger.valueOf(10)); + ilim = ilim1; + } + } + /* At this point 1 <= d/10^k = b/S < 10. */ + + if (ilim <= 0 && mode > 2) { + /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode. + Output either zero or the minimum nonzero output depending on which is closer to d. */ + if ((ilim < 0 ) + || ((i = b.compareTo(S = S.multiply(BigInteger.valueOf(5)))) < 0) + || ((i == 0 && !biasUp))) { + /* Always emit at least one digit. If the number appears to be zero + using the current mode, then emit one '0' digit and set decpt to 1. */ + /*no_digits: + k = -1 - ndigits; + goto ret; */ + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; +// goto no_digits; + } +// one_digit: + buf.append('1'); + k++; + return k + 1; + } + if (leftright) { + if (m2 > 0) + mhi = mhi.shiftLeft(m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = mlo; + mhi = mhi.shiftLeft(Log2P); + } + /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */ + /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */ + + for(i = 1;;i++) { + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = b.compareTo(mlo); + /* j is b/S compared with mlo/S. */ + delta = S.subtract(mhi); + j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/S compared with 1 - mhi/S. */ + if ((j1 == 0) && (mode == 0) && ((word1(d) & 1) == 0)) { + if (dig == '9') { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + if (j > 0) + dig++; + buf.append(dig); + return k + 1; + } + if ((j < 0) + || ((j == 0) + && (mode == 0) + && ((word1(d) & 1) == 0) + )) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant decimal digit. + Use whichever would produce a decimal value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(S); + if (((j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp))) + && (dig++ == '9')) { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + } + buf.append(dig); + return k + 1; + } + if (j1 > 0) { + if (dig == '9') { /* possible if i == 1 */ +// round_9_up: +// *s++ = '9'; +// goto roundoff; + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; + } + buf.append((char)(dig + 1)); + return k + 1; + } + buf.append(dig); + if (i == ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + if (mlo == mhi) + mlo = mhi = mhi.multiply(BigInteger.valueOf(10)); + else { + mlo = mlo.multiply(BigInteger.valueOf(10)); + mhi = mhi.multiply(BigInteger.valueOf(10)); + } + } + } + else + for(i = 1;; i++) { +// (char)(dig = quorem(b,S) + '0'); + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + buf.append(dig); + if (i >= ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + } + + /* Round off last digit */ + + b = b.shiftLeft(1); + j = b.compareTo(S); + if ((j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp))) { +// roundoff: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s++ = '1'; +// goto ret; +// } +// ++*s++; + if (roundOff(buf)) { + k++; + buf.append('1'); + return k + 1; + } + } + else { + /* Strip trailing zeros */ + while (buf.charAt(buf.length() - 1) == '0') + buf.setLength(buf.length() - 1); +// while(*--s == '0') ; +// s++; + } +// ret: +// Bfree(S); +// if (mhi) { +// if (mlo && mlo != mhi) +// Bfree(mlo); +// Bfree(mhi); +// } +// ret1: +// Bfree(b); +// JS_ASSERT(s < buf + bufsize); + return k + 1; + } + + /* Mapping of JSDToStrMode -> JS_dtoa mode */ + private static final int dtoaModes[] = { + 0, /* DTOSTR_STANDARD */ + 0, /* DTOSTR_STANDARD_EXPONENTIAL, */ + 3, /* DTOSTR_FIXED, */ + 2, /* DTOSTR_EXPONENTIAL, */ + 2}; /* DTOSTR_PRECISION */ + + static void + JS_dtostr(StringBuffer buffer, int mode, int precision, double d) + { + int decPt; /* Position of decimal point relative to first digit returned by JS_dtoa */ + boolean[] sign = new boolean[1]; /* true if the sign bit was set in d */ + int nDigits; /* Number of significand digits returned by JS_dtoa */ + +// JS_ASSERT(bufferSize >= (size_t)(mode <= DTOSTR_STANDARD_EXPONENTIAL ? DTOSTR_STANDARD_BUFFER_SIZE : +// DTOSTR_VARIABLE_BUFFER_SIZE(precision))); + + if (mode == DTOSTR_FIXED && (d >= 1e21 || d <= -1e21)) + mode = DTOSTR_STANDARD; /* Change mode here rather than below because the buffer may not be large enough to hold a large integer. */ + + decPt = JS_dtoa(d, dtoaModes[mode], mode >= DTOSTR_FIXED, precision, sign, buffer); + nDigits = buffer.length(); + + /* If Infinity, -Infinity, or NaN, return the string regardless of the mode. */ + if (decPt != 9999) { + boolean exponentialNotation = false; + int minNDigits = 0; /* Minimum number of significand digits required by mode and precision */ + int p; + int q; + + switch (mode) { + case DTOSTR_STANDARD: + if (decPt < -5 || decPt > 21) + exponentialNotation = true; + else + minNDigits = decPt; + break; + + case DTOSTR_FIXED: + if (precision >= 0) + minNDigits = decPt + precision; + else + minNDigits = decPt; + break; + + case DTOSTR_EXPONENTIAL: +// JS_ASSERT(precision > 0); + minNDigits = precision; + /* Fall through */ + case DTOSTR_STANDARD_EXPONENTIAL: + exponentialNotation = true; + break; + + case DTOSTR_PRECISION: +// JS_ASSERT(precision > 0); + minNDigits = precision; + if (decPt < -5 || decPt > precision) + exponentialNotation = true; + break; + } + + /* If the number has fewer than minNDigits, pad it with zeros at the end */ + if (nDigits < minNDigits) { + p = minNDigits; + nDigits = minNDigits; + do { + buffer.append('0'); + } while (buffer.length() != p); + } + + if (exponentialNotation) { + /* Insert a decimal point if more than one significand digit */ + if (nDigits != 1) { + buffer.insert(1, '.'); + } + buffer.append('e'); + if ((decPt - 1) >= 0) + buffer.append('+'); + buffer.append(decPt - 1); +// JS_snprintf(numEnd, bufferSize - (numEnd - buffer), "e%+d", decPt-1); + } else if (decPt != nDigits) { + /* Some kind of a fraction in fixed notation */ +// JS_ASSERT(decPt <= nDigits); + if (decPt > 0) { + /* dd...dd . dd...dd */ + buffer.insert(decPt, '.'); + } else { + /* 0 . 00...00dd...dd */ + for (int i = 0; i < 1 - decPt; i++) + buffer.insert(0, '0'); + buffer.insert(1, '.'); + } + } + } + + /* If negative and neither -0.0 nor NaN, output a leading '-'. */ + if (sign[0] && + !(word0(d) == Sign_bit && word1(d) == 0) && + !((word0(d) & Exp_mask) == Exp_mask && + ((word1(d) != 0) || ((word0(d) & Frac_mask) != 0)))) { + buffer.insert(0, '-'); + } + } + +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/ErrorReporter.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/ErrorReporter.java new file mode 100644 index 00000000000..e7ac6e17826 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/ErrorReporter.java @@ -0,0 +1,104 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +// API class + +package com.google.gwt.dev.js.rhino; + +/** + * This is interface defines a protocol for the reporting of + * errors during JavaScript translation or execution. + * + * @author Norris Boyd + */ + +public interface ErrorReporter { + + /** + * Report a warning. + * + * The implementing class may choose to ignore the warning + * if it desires. + * + * @param message a String describing the warning + * @param sourceName a String describing the JavaScript source + * where the warning occured; typically a filename or URL + * @param line the line number associated with the warning + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void warning(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Report an error. + * + * The implementing class is free to throw an exception if + * it desires. + * + * If execution has not yet begun, the JavaScript engine is + * free to find additional errors rather than terminating + * the translation. It will not execute a script that had + * errors, however. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void error(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Creates an EvaluatorException that may be thrown. + * + * runtimeErrors, unlike errors, will always terminate the + * current script. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return an EvaluatorException that will be thrown. + */ + EvaluatorException runtimeError(String message, String sourceName, + int line, String lineSource, + int lineOffset); +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/EvaluatorException.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/EvaluatorException.java new file mode 100644 index 00000000000..bbc8d452aa8 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/EvaluatorException.java @@ -0,0 +1,56 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public class EvaluatorException extends RuntimeException { + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param detail a message with detail about the exception + */ + public EvaluatorException(String detail) { + super(detail); + } + +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java new file mode 100644 index 00000000000..59be80b202b --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/IRFactory.java @@ -0,0 +1,448 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package com.google.gwt.dev.js.rhino; + +/** + * This class allows the creation of nodes, and follows the Factory pattern. + * + * @see Node + * @author Mike McCabe + * @author Norris Boyd + */ +public class IRFactory { + + public IRFactory(TokenStream ts) { + this.ts = ts; + } + + /** + * Script (for associating file/url names with toplevel scripts.) + */ + public Object createScript(Object body, String sourceName, + int baseLineno, int endLineno, Object source) + { + Node result = new Node(TokenStream.SCRIPT); + Node children = ((Node) body).getFirstChild(); + if (children != null) + result.addChildrenToBack(children); + + result.putProp(Node.SOURCENAME_PROP, sourceName); + result.putIntProp(Node.BASE_LINENO_PROP, baseLineno); + result.putIntProp(Node.END_LINENO_PROP, endLineno); + if (source != null) + result.putProp(Node.SOURCE_PROP, source); + return result; + } + + /** + * Leaf + */ + public Object createLeaf(int nodeType) { + return new Node(nodeType); + } + + public Object createLeaf(int nodeType, int nodeOp) { + return new Node(nodeType, nodeOp); + } + + public int getLeafType(Object leaf) { + Node n = (Node) leaf; + return n.getType(); + } + + /** + * Statement leaf nodes. + */ + + public Object createSwitch(int lineno) { + return new Node(TokenStream.SWITCH, lineno); + } + + public Object createVariables(int lineno) { + return new Node(TokenStream.VAR, lineno); + } + + public Object createExprStatement(Object expr, int lineno) { + return new Node(TokenStream.EXPRSTMT, (Node) expr, lineno); + } + + /** + * Name + */ + public Object createName(String name) { + return Node.newString(TokenStream.NAME, name); + } + + /** + * String (for literals) + */ + public Object createString(String string) { + return Node.newString(string); + } + + /** + * Number (for literals) + */ + public Object createNumber(double number) { + return Node.newNumber(number); + } + + /** + * Catch clause of try/catch/finally + * @param varName the name of the variable to bind to the exception + * @param catchCond the condition under which to catch the exception. + * May be null if no condition is given. + * @param stmts the statements in the catch clause + * @param lineno the starting line number of the catch clause + */ + public Object createCatch(String varName, Object catchCond, Object stmts, + int lineno) + { + if (catchCond == null) { + catchCond = new Node(TokenStream.PRIMARY, TokenStream.TRUE); + } + return new Node(TokenStream.CATCH, (Node)createName(varName), + (Node)catchCond, (Node)stmts, lineno); + } + + /** + * Throw + */ + public Object createThrow(Object expr, int lineno) { + return new Node(TokenStream.THROW, (Node)expr, lineno); + } + + /** + * Return + */ + public Object createReturn(Object expr, int lineno) { + return expr == null + ? new Node(TokenStream.RETURN, lineno) + : new Node(TokenStream.RETURN, (Node)expr, lineno); + } + + /** + * Label + */ + public Object createLabel(String label, int lineno) { + Node result = new Node(TokenStream.LABEL, lineno); + Node name = Node.newString(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + + /** + * Break (possibly labeled) + */ + public Object createBreak(String label, int lineno) { + Node result = new Node(TokenStream.BREAK, lineno); + if (label == null) { + return result; + } else { + Node name = Node.newString(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + } + + /** + * Continue (possibly labeled) + */ + public Object createContinue(String label, int lineno) { + Node result = new Node(TokenStream.CONTINUE, lineno); + if (label == null) { + return result; + } else { + Node name = Node.newString(TokenStream.NAME, label); + result.addChildToBack(name); + return result; + } + } + + /** + * debugger + */ + public Object createDebugger(int lineno) { + Node result = new Node(TokenStream.DEBUGGER, lineno); + return result; + } + + /** + * Statement block + * Creates the empty statement block + * Must make subsequent calls to add statements to the node + */ + public Object createBlock(int lineno) { + return new Node(TokenStream.BLOCK, lineno); + } + + public Object createFunction(String name, Object args, Object statements, + String sourceName, int baseLineno, + int endLineno, Object source, + boolean isExpr) + { + Node f = new Node(TokenStream.FUNCTION, + Node.newString(TokenStream.NAME, + name == null ? "" : name), + (Node)args, (Node)statements, baseLineno); + + f.putProp(Node.SOURCENAME_PROP, sourceName); + f.putIntProp(Node.BASE_LINENO_PROP, baseLineno); + f.putIntProp(Node.END_LINENO_PROP, endLineno); + if (source != null) + f.putProp(Node.SOURCE_PROP, source); + + return f; + } + + /** + * Add a child to the back of the given node. This function + * breaks the Factory abstraction, but it removes a requirement + * from implementors of Node. + */ + public void addChildToBack(Object parent, Object child) { + ((Node)parent).addChildToBack((Node)child); + } + + /** + * While + */ + public Object createWhile(Object cond, Object body, int lineno) { + return new Node(TokenStream.WHILE, (Node)cond, (Node)body, lineno); + } + + /** + * DoWhile + */ + public Object createDoWhile(Object body, Object cond, int lineno) { + return new Node(TokenStream.DO, (Node)body, (Node)cond, lineno); + } + + /** + * For + */ + public Object createFor(Object init, Object test, Object incr, + Object body, int lineno) + { + return new Node(TokenStream.FOR, (Node)init, (Node)test, (Node)incr, + (Node)body); + } + + /** + * For .. In + * + */ + public Object createForIn(Object lhs, Object obj, Object body, int lineno) { + return new Node(TokenStream.FOR, (Node)lhs, (Node)obj, (Node)body); + } + + /** + * Try/Catch/Finally + */ + public Object createTryCatchFinally(Object tryblock, Object catchblocks, + Object finallyblock, int lineno) + { + if (finallyblock == null) { + return new Node(TokenStream.TRY, (Node)tryblock, (Node)catchblocks); + } + return new Node(TokenStream.TRY, (Node)tryblock, + (Node)catchblocks, (Node)finallyblock); + } + + /** + * Throw, Return, Label, Break and Continue are defined in ASTFactory. + */ + + /** + * With + */ + public Object createWith(Object obj, Object body, int lineno) { + return new Node(TokenStream.WITH, (Node)obj, (Node)body, lineno); + } + + /** + * Array Literal + */ + public Object createArrayLiteral(Object obj) { + return obj; + } + + /** + * Object Literals + */ + public Object createObjectLiteral(Object obj) { + return obj; + } + + /** + * Regular expressions + */ + public Object createRegExp(String string, String flags) { + return flags.length() == 0 + ? new Node(TokenStream.REGEXP, + Node.newString(string)) + : new Node(TokenStream.REGEXP, + Node.newString(string), + Node.newString(flags)); + } + + /** + * If statement + */ + public Object createIf(Object cond, Object ifTrue, Object ifFalse, + int lineno) + { + if (ifFalse == null) + return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue); + return new Node(TokenStream.IF, (Node)cond, (Node)ifTrue, (Node)ifFalse); + } + + public Object createTernary(Object cond, Object ifTrue, Object ifFalse) { + return new Node(TokenStream.HOOK, + (Node)cond, (Node)ifTrue, (Node)ifFalse); + } + + /** + * Unary + */ + public Object createUnary(int nodeType, Object child) { + Node childNode = (Node) child; + return new Node(nodeType, childNode); + } + + public Object createUnary(int nodeType, int nodeOp, Object child) { + return new Node(nodeType, (Node)child, nodeOp); + } + + /** + * Binary + */ + public Object createBinary(int nodeType, Object left, Object right) { + Node temp; + switch (nodeType) { + + case TokenStream.DOT: + nodeType = TokenStream.GETPROP; + Node idNode = (Node) right; + idNode.setType(TokenStream.STRING); + String id = idNode.getString(); + if (id.equals("__proto__") || id.equals("__parent__")) { + Node result = new Node(nodeType, (Node) left); + result.putProp(Node.SPECIAL_PROP_PROP, id); + return result; + } + break; + + case TokenStream.LB: + // OPT: could optimize to GETPROP iff string can't be a number + nodeType = TokenStream.GETELEM; + break; + } + return new Node(nodeType, (Node)left, (Node)right); + } + + public Object createBinary(int nodeType, int nodeOp, Object left, + Object right) + { + if (nodeType == TokenStream.ASSIGN) { + return createAssignment(nodeOp, (Node) left, (Node) right, + null, false); + } + return new Node(nodeType, (Node) left, (Node) right, nodeOp); + } + + public Object createAssignment(int nodeOp, Node left, Node right, + Class convert, boolean postfix) + { + int nodeType = left.getType(); + switch (nodeType) { + case TokenStream.NAME: + case TokenStream.GETPROP: + case TokenStream.GETELEM: + break; + default: + // TODO: This should be a ReferenceError--but that's a runtime + // exception. Should we compile an exception into the code? + reportError("msg.bad.lhs.assign"); + } + + return new Node(TokenStream.ASSIGN, left, right, nodeOp); + } + + private Node createConvert(Class toType, Node expr) { + if (toType == null) + return expr; + Node result = new Node(TokenStream.CONVERT, expr); + result.putProp(Node.TYPE_PROP, Number.class); + return result; + } + + public static boolean hasSideEffects(Node exprTree) { + switch (exprTree.getType()) { + case TokenStream.INC: + case TokenStream.DEC: + case TokenStream.SETPROP: + case TokenStream.SETELEM: + case TokenStream.SETNAME: + case TokenStream.CALL: + case TokenStream.NEW: + return true; + default: + Node child = exprTree.getFirstChild(); + while (child != null) { + if (hasSideEffects(child)) + return true; + else + child = child.getNext(); + } + break; + } + return false; + } + + private void reportError(String msgResource) { + + String message = Context.getMessage0(msgResource); + Context.reportError( + message, ts.getSourceName(), ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + // Only needed to get file/line information. Could create an interface + // that TokenStream implements if we want to make the connection less + // direct. + private TokenStream ts; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/JavaScriptException.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/JavaScriptException.java new file mode 100644 index 00000000000..2ce6957141f --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/JavaScriptException.java @@ -0,0 +1,85 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +// API class + +package com.google.gwt.dev.js.rhino; + + +/** + * Java reflection of JavaScript exceptions. (Possibly wrapping a Java exception.) + * + * @author Mike McCabe + */ +public class JavaScriptException extends Exception { + + /** + * Create a JavaScript exception wrapping the given JavaScript value. + * + * Instances of this class are thrown by the JavaScript 'throw' keyword. + * + * @param value the JavaScript value thrown. + */ + public JavaScriptException(Object value) { + super(value.toString()); + this.value = value; + } + + /** + * Get the exception value originally thrown. This may be a + * JavaScript value (null, undefined, Boolean, Number, String, + * Scriptable or Function) or a Java exception value thrown from a + * host object or from Java called through LiveConnect. + * + * @return the value wrapped by this exception + */ + public Object getValue() { + return value; + } + + /** + * The JavaScript exception value. This value is not + * intended for general use; if the JavaScriptException wraps a + * Java exception, getScriptableValue may return a Scriptable + * wrapping the original Java exception object. + * + * We would prefer to go through a getter to encapsulate the value, + * however that causes the bizarre error "nanosecond timeout value + * out of range" on the MS JVM. + * @serial + */ + Object value; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/LineBuffer.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/LineBuffer.java new file mode 100644 index 00000000000..4424d18ba09 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/LineBuffer.java @@ -0,0 +1,342 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.Reader; +import java.io.IOException; + +/** + * An input buffer that combines fast character-based access with + * (slower) support for retrieving the text of the current line. It + * also supports building strings directly out of the internal buffer + * to support fast scanning with minimal object creation. + * + * Note that it is customized in several ways to support the + * TokenStream class, and should not be considered general. + * + * Credits to Kipp Hickman and John Bandhauer. + * + * @author Mike McCabe + */ +final class LineBuffer { + /* + * for smooth operation of getLine(), this should be greater than + * the length of any expected line. Currently, 256 is 3% slower + * than 4096 for large compiles, but seems safer given evaluateString. + * Strings for the scanner are are built with StringBuffers + * instead of directly out of the buffer whenever a string crosses + * a buffer boundary, so small buffer sizes will mean that more + * objects are created. + */ + static final int BUFLEN = 256; + + LineBuffer(Reader in, int lineno) { + this.in = in; + this.lineno = lineno; + } + + int read() throws IOException { + for(;;) { + if (end == offset && !fill()) + return -1; + + int c = buffer[offset]; + ++offset; + + if ((c & EOL_HINT_MASK) == 0) { + switch (c) { + case '\r': + // if the next character is a newline, skip past it. + if (offset != end) { + if (buffer[offset] == '\n') + ++offset; + } else { + // set a flag for fill(), in case the first char + // of the next fill is a newline. + lastWasCR = true; + } + // NO break here! + case '\n': case '\u2028': case '\u2029': + prevStart = lineStart; + lineStart = offset; + lineno++; + return '\n'; + } + } + + if (c < 128 || !formatChar(c)) { + return c; + } + } + } + + void unread() { + // offset can only be 0 when we're asked to unread() an implicit + // EOF_CHAR. + + // This would be wrong behavior in the general case, + // because a peek() could map a buffer.length offset to 0 + // in the process of a fill(), and leave it there. But + // the scanner never calls peek() or a failed match() + // followed by unread()... this would violate 1-character + // lookahead. + if (offset == 0 && !hitEOF) Context.codeBug(); + + if (offset == 0) // Same as if (hitEOF) + return; + offset--; + int c = buffer[offset]; + if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) { + lineStart = prevStart; + lineno--; + } + } + + private void skipFormatChar() { + if (checkSelf && !formatChar(buffer[offset])) Context.codeBug(); + + // swap prev character with format one so possible call to + // startString can assume that previous non-format char is at + // offset - 1. Note it causes getLine to return not exactly the + // source LineBuffer read, but it is used only in error reporting + // and should not be a problem. + if (offset != 0) { + char tmp = buffer[offset]; + buffer[offset] = buffer[offset - 1]; + buffer[offset - 1] = tmp; + } + else if (otherEnd != 0) { + char tmp = buffer[offset]; + buffer[offset] = otherBuffer[otherEnd - 1]; + otherBuffer[otherEnd - 1] = tmp; + } + + ++offset; + } + + int peek() throws IOException { + for (;;) { + if (end == offset && !fill()) { + return -1; + } + + int c = buffer[offset]; + if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) { + return '\n'; + } + if (c < 128 || !formatChar(c)) { + return c; + } + + skipFormatChar(); + } + } + + boolean match(int test) throws IOException { + // TokenStream never looks ahead for '\n', which allows simple code + if ((test & EOL_HINT_MASK) == 0 && eolChar(test)) + Context.codeBug(); + // Format chars are not allowed either + if (test >= 128 && formatChar(test)) + Context.codeBug(); + + for (;;) { + if (end == offset && !fill()) + return false; + + int c = buffer[offset]; + if (test == c) { + ++offset; + return true; + } + if (c < 128 || !formatChar(c)) { + return false; + } + skipFormatChar(); + } + } + + // Reconstruct a source line from the buffers. This can be slow... + String getLine() { + // Look for line end in the unprocessed buffer + int i = offset; + while(true) { + if (i == end) { + // if we're out of buffer, let's just expand it. We do + // this instead of reading into a StringBuffer to + // preserve the stream for later reads. + if (end == buffer.length) { + char[] tmp = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, tmp, 0, end); + buffer = tmp; + } + int charsRead = 0; + try { + charsRead = in.read(buffer, end, buffer.length - end); + } catch (IOException ioe) { + // ignore it, we're already displaying an error... + break; + } + if (charsRead < 0) + break; + end += charsRead; + } + int c = buffer[i]; + if ((c & EOL_HINT_MASK) == 0 && eolChar(c)) + break; + i++; + } + + int start = lineStart; + if (lineStart < 0) { + // the line begins somewhere in the other buffer; get that first. + StringBuffer sb = new StringBuffer(otherEnd - otherStart + i); + sb.append(otherBuffer, otherStart, otherEnd - otherStart); + sb.append(buffer, 0, i); + return sb.toString(); + } else { + return new String(buffer, lineStart, i - lineStart); + } + } + + // Get the offset of the current character, relative to + // the line that getLine() returns. + int getOffset() { + if (lineStart < 0) + // The line begins somewhere in the other buffer. + return offset + (otherEnd - otherStart); + else + return offset - lineStart; + } + + private boolean fill() throws IOException { + // fill should be caled only for emty buffer + if (checkSelf && !(end == offset)) Context.codeBug(); + + // swap buffers + char[] tempBuffer = buffer; + buffer = otherBuffer; + otherBuffer = tempBuffer; + + // allocate the buffers lazily, in case we're handed a short string. + if (buffer == null) { + buffer = new char[BUFLEN]; + } + + // buffers have switched, so move the newline marker. + if (lineStart >= 0) { + otherStart = lineStart; + } else { + // discard beging of the old line + otherStart = 0; + } + + otherEnd = end; + + // set lineStart to a sentinel value, unless this is the first + // time around. + prevStart = lineStart = (otherBuffer == null) ? 0 : -1; + + offset = 0; + end = in.read(buffer, 0, buffer.length); + if (end < 0) { + end = 0; + + // can't null buffers here, because a string might be retrieved + // out of the other buffer, and a 0-length string might be + // retrieved out of this one. + + hitEOF = true; + return false; + } + + // If the last character of the previous fill was a carriage return, + // then ignore a newline. + + // There's another bizzare special case here. If lastWasCR is + // true, and we see a newline, and the buffer length is + // 1... then we probably just read the last character of the + // file, and returning after advancing offset is not the right + // thing to do. Instead, we try to ignore the newline (and + // likely get to EOF for real) by doing yet another fill(). + if (lastWasCR) { + if (buffer[0] == '\n') { + offset++; + if (end == 1) + return fill(); + } + lineStart = offset; + lastWasCR = false; + } + return true; + } + + int getLineno() { return lineno; } + boolean eof() { return hitEOF; } + + private static boolean formatChar(int c) { + return Character.getType((char)c) == Character.FORMAT; + } + + private static boolean eolChar(int c) { + return c == '\r' || c == '\n' || c == '\u2028' || c == '\u2029'; + } + + // Optimization for faster check for eol character: eolChar(c) returns + // true only when (c & EOL_HINT_MASK) == 0 + private static final int EOL_HINT_MASK = 0xdfd0; + + private Reader in; + private char[] otherBuffer = null; + private char[] buffer = null; + + // Yes, there are too too many of these. + private int offset = 0; + private int end = 0; + private int otherEnd; + private int lineno; + + private int lineStart = 0; + private int otherStart = 0; + private int prevStart = 0; + + private boolean lastWasCR = false; + private boolean hitEOF = false; + +// Rudimentary support for Design-by-Contract + private static final boolean checkSelf = true; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Messages.properties b/js/js.parser/src/com/google/gwt/dev/js/rhino/Messages.properties new file mode 100644 index 00000000000..7225327a8e7 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Messages.properties @@ -0,0 +1,533 @@ +# +# Default JavaScript messages file. +# +# The contents of this file are subject to the Netscape Public +# License Version 1.1 (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.mozilla.org/NPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is Rhino code, released +# May 6, 1999. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1997-1999 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# Norris Boyd +# +# Alternatively, the contents of this file may be used under the +# terms of the GNU Public License (the "GPL"), in which case the +# provisions of the GPL are applicable instead of those above. +# If you wish to allow use of your version of this file only +# under the terms of the GPL and not to allow others to use your +# version of this file under the NPL, indicate your decision by +# deleting the provisions above and replace them with the notice +# and other provisions required by the GPL. If you do not delete +# the provisions above, a recipient may use your version of this +# file under either the NPL or the GPL. + +# +# To add JavaScript error messages for a particular locale, create a +# new Messages_[locale].properties file, where [locale] is the Java +# string abbreviation for that locale. For example, JavaScript +# messages for the Polish locale should be located in +# Messages_pl.properties, and messages for the Italian Swiss locale +# should be located in Messages_it_CH.properties. Message properties +# files should be accessible through the classpath under +# org.mozilla.javascript.resources +# +# See: +# java.util.ResourceBundle +# java.text.MessageFormat +# + +# SomeJavaClassWhereUsed + +# Codegen +msg.dup.parms =\ + Duplicate parameter name "{0}". + +# Context +msg.ctor.not.found =\ + Constructor for "{0}" not found. + +msg.not.ctor =\ + "{0}" is not a constructor. + +# FunctionObject +msg.varargs.ctor =\ + Method or constructor "{0}" must be static with the signature \ + "(Context cx, Object[] args, Function ctorObj, boolean inNewExpr)" \ + to define a variable arguments constructor. + +msg.varargs.fun =\ + Method "{0}" must be static with the signature \ + "(Context cx, Scriptable thisObj, Object[] args, Function funObj)" \ + to define a variable arguments function. + +msg.incompat.call =\ + Method "{0}" called on incompatible object. + +msg.bad.parms =\ + Bad method parameters for "{0}". + +msg.no.overload =\ + Method "{0}" occurs multiple times in class "{1}". + +msg.method.not.found =\ + Method "{0}" not found in "{1}". + +# IRFactory + +msg.bad.for.in.lhs =\ + Invalid left-hand side of for..in loop. + +msg.bad.lhs.assign =\ + Invalid assignment left-hand side. + +msg.mult.index =\ + Only one variable allowed in for..in loop. + +msg.cant.convert =\ + Can''t convert to type "{0}". + +# NativeGlobal +msg.cant.call.indirect =\ + Function "{0}" must be called directly, and not by way of a \ + function of another name. + +msg.eval.nonstring =\ + Calling eval() with anything other than a primitive string value will \ + simply return the value. Is this what you intended? + +# NativeCall +msg.only.from.new =\ + "{0}" may only be invoked from a "new" expression. + +msg.deprec.ctor =\ + The "{0}" constructor is deprecated. + +# NativeFunction +msg.no.function.ref.found.in =\ + no source found in {1} to decompile function reference {0} + +msg.no.function.ref.found =\ + no source found to decompile function reference {0} + +msg.arg.isnt.array =\ + second argument to Function.prototype.apply must be an array + +# NativeGlobal +msg.bad.esc.mask =\ + invalid string escape mask + +# NativeJavaClass +msg.cant.instantiate =\ + error instantiating ({0}): class {1} is interface or abstract + +msg.bad.ctor.sig =\ + Found constructor with wrong signature: \ + {0} calling {1} with signature {2} + +msg.not.java.obj =\ + Expected argument to getClass() to be a Java object. + +msg.no.java.ctor =\ + Java constructor for "{0}" with arguments "{1}" not found. + +# NativeJavaMethod +msg.method.ambiguous =\ + The choice of Java method {0}.{1} matching JavaScript argument types ({2}) is ambiguous; \ + candidate methods are: {3} + +msg.constructor.ambiguous =\ + The choice of Java constructor {0} matching JavaScript argument types ({1}) is ambiguous; \ + candidate constructors are: {2} + +# NativeJavaObject +msg.conversion.not.allowed =\ + Cannot convert {0} to {1} + +# NativeRegExp +msg.bad.quant =\ + Invalid quantifier {0} + +msg.overlarge.max =\ + Overly large maximum {0} + +msg.zero.quant =\ + Zero quantifier {0} + +msg.max.lt.min =\ + Maximum {0} less than minimum + +msg.unterm.quant =\ + Unterminated quantifier {0} + +msg.unterm.paren =\ + Unterminated parenthetical {0} + +msg.unterm.class =\ + Unterminated character class {0} + +msg.bad.range =\ + Invalid range in character class. + +msg.trail.backslash =\ + Trailing \\ in regular expression. + +msg.no.regexp =\ + Regular expressions are not available. + +msg.bad.backref =\ + back-reference exceeds number of capturing parentheses. + +# NodeTransformer +msg.dup.label =\ + Duplicate label {0}. + +msg.undef.label =\ + Undefined label {0}. + +msg.bad.break =\ + Unlabelled break must be inside loop or switch. + +msg.continue.outside =\ + continue must be inside loop. + +msg.continue.nonloop =\ + Can only continue to labeled iteration statement. + +msg.fn.redecl =\ + Function "{0}" redeclared; prior definition will be ignored. + +# Parser +msg.no.paren.parms =\ + missing ( before function parameters + +msg.no.parm =\ + missing formal parameter + +msg.no.paren.after.parms =\ + missing ) after formal parameters + +msg.no.brace.body =\ + missing '{' before function body + +msg.no.brace.after.body =\ + missing } after function body + +msg.no.paren.cond =\ + missing ( before condition + +msg.no.paren.after.cond =\ + missing ) after condition + +msg.no.semi.stmt =\ + missing ; before statement + +msg.no.name.after.dot =\ + missing name after . operator + +msg.no.bracket.index =\ + missing ] in index expression + +msg.no.paren.switch =\ + missing ( before switch expression + +msg.no.paren.after.switch =\ + missing ) after switch expression + +msg.no.brace.switch =\ + missing '{' before switch body + +msg.bad.switch =\ + invalid switch statement + +msg.no.colon.case =\ + missing : after case expression + +msg.no.while.do =\ + missing while after do-loop body + +msg.no.paren.for =\ + missing ( after for + +msg.no.semi.for =\ + missing ; after for-loop initializer + +msg.no.semi.for.cond =\ + missing ; after for-loop condition + +msg.no.paren.for.ctrl =\ + missing ) after for-loop control + +msg.no.paren.with =\ + missing ( before with-statement object + +msg.no.paren.after.with =\ + missing ) after with-statement object + +msg.bad.return =\ + invalid return + +msg.no.brace.block =\ + missing } in compound statement + +msg.bad.label =\ + invalid label + +msg.bad.var =\ + missing variable name + +msg.bad.var.init =\ + invalid variable initialization + +msg.no.colon.cond =\ + missing : in conditional expression + +msg.no.paren.arg =\ + missing ) after argument list + +msg.no.bracket.arg =\ + missing ] after element list + +msg.bad.prop =\ + invalid property id + +msg.no.colon.prop =\ + missing : after property id + +msg.no.brace.prop =\ + missing } after property list + +msg.no.paren =\ + missing ) in parenthetical + +msg.reserved.id =\ + identifier is a reserved word + +msg.no.paren.catch =\ + missing ( before catch-block condition + +msg.bad.catchcond =\ + invalid catch block condition + +msg.catch.unreachable =\ + any catch clauses following an unqualified catch are unreachable + +msg.no.brace.catchblock =\ + missing '{' before catch-block body + +msg.try.no.catchfinally =\ + ''try'' without ''catch'' or ''finally'' + +msg.syntax =\ + syntax error + +# ScriptRuntime +msg.assn.create =\ + Assignment to undefined "{0}" will create a new variable. \ + Add a variable statement at the top level scope to remove this warning. + +msg.prop.not.found =\ + Property not found. + +msg.invalid.type =\ + Invalid JavaScript value of type {0} + +msg.primitive.expected =\ + Primitive type expected (had {0} instead) + +msg.null.to.object =\ + Cannot convert null to an object. + +msg.undef.to.object =\ + Cannot convert undefined to an object. + +msg.cyclic.value =\ + Cyclic {0} value not allowed. + +msg.is.not.defined =\ + "{0}" is not defined. + +msg.isnt.function =\ + {0} is not a function. + +msg.bad.default.value =\ + Object''s getDefaultValue() method returned an object. + +msg.instanceof.not.object = \ + Can''t use instanceof on a non-object. + +msg.instanceof.bad.prototype = \ + ''prototype'' property of {0} is not an object. + +msg.bad.radix = \ + illegal radix {0}. + +# ScriptableObject +msg.default.value =\ + Cannot find default value for object. + +msg.zero.arg.ctor =\ + Cannot load class "{0}" which has no zero-parameter constructor. + +msg.multiple.ctors =\ + Cannot have more than one constructor method, but found both {0} and {1}. + +msg.ctor.multiple.parms =\ + Can''t define constructor or class {0} since more than one \ + constructor has multiple parameters. + +msg.extend.scriptable =\ + {0} must extend ScriptableObject in order to define property {1}. + +msg.bad.getter.parms =\ + In order to define a property, getter {0} must have zero parameters \ + or a single ScriptableObject parameter. + +msg.obj.getter.parms =\ + Expected static or delegated getter {0} to take a ScriptableObject parameter. + +msg.getter.static =\ + Getter and setter must both be static or neither be static. + +msg.setter2.parms =\ + Two-parameter setter must take a ScriptableObject as its first parameter. + +msg.setter1.parms =\ + Expected single parameter setter for {0} + +msg.setter2.expected =\ + Expected static or delegated setter {0} to take two parameters. + +msg.setter.parms =\ + Expected either one or two parameters for setter. + +msg.add.sealed =\ + Cannot add a property to a sealed object. + +msg.remove.sealed =\ + Cannot remove a property from a sealed object. + +# TokenStream +msg.token.replaces.pushback =\ + ungot token {0} replaces pushback token {1} + +msg.missing.exponent =\ + missing exponent + +msg.caught.nfe =\ + number format error: {0} + +msg.unterminated.string.lit =\ + unterminated string literal + +msg.oct.esc.too.large =\ + octal escape too large + +msg.unterminated.comment =\ + unterminated comment + +msg.unterminated.re.lit =\ + unterminated regular expression literal + +msg.invalid.re.flag =\ + invalid flag after regular expression + +msg.no.re.input.for =\ + no input for {0} + +msg.illegal.character =\ + illegal character + +msg.invalid.escape =\ + invalid Unicode escape sequence + +# TokensStream warnings +msg.bad.octal.literal =\ + illegal octal literal digit {0}; interpreting it as a decimal digit + +msg.reserved.keyword =\ + illegal usage of future reserved keyword {0}; interpreting it as ordinary identifier + +# Undefined +msg.undefined =\ + The undefined value has no properties. + +# LiveConnect errors +msg.java.internal.field.type =\ + Internal error: type conversion of {0} to assign to {1} on {2} failed. + +msg.java.conversion.implicit_method =\ + Can''t find converter method "{0}" on class {1}. + +msg.java.method.assign =\ + Java method "{0}" cannot be assigned to. + +msg.java.internal.private =\ + Internal error: attempt to access private/protected field "{0}". + +msg.java.no_such_method =\ + Can''t find method {0}. + +msg.script.is.not.constructor =\ + Script objects are not constructors. + +msg.nonjava.method =\ + Java method "{0}" was invoked with a ''this'' value that was not a Java object. + +msg.java.member.not.found =\ + Java class "{0}" has no public instance field or method named "{1}". + +msg.pkg.int =\ + Java package names may not be numbers. + +# ImporterTopLevel +msg.ambig.import =\ + Ambiguous import: "{0}" and and "{1}". + +msg.not.pkg =\ + Function importPackage must be called with a package; had "{0}" instead. + +msg.not.class =\ + Function importClass must be called with a class; had "{0}" instead. + +msg.prop.defined =\ + Cannot import "{0}" since a property by that name is already defined. + +# Arrays +msg.arraylength.bad =\ + Inappropriate array length. + +# URI +msg.bad.uri =\ + Malformed URI sequence. + +# Number +msg.bad.precision =\ + Precision {0} out of range. + +# JSNI +msg.jsni.expected.char =\ + Expected "{0}" in JSNI reference + +msg.jsni.expected.identifier =\ + Expected an identifier in JSNI reference + +msg.jsni.unexpected.identifier =\ + Unexpected character in JSNI reference + +msg.jsni.expected.param.type =\ + Expected a valid parameter type signature in JSNI method reference + +msg.jsni.unsupported.with =\ + The ''with'' statement is unsupported in JSNI blocks (perhaps you could use a local variable instead?) diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java new file mode 100644 index 00000000000..1c279647753 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Node.java @@ -0,0 +1,725 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +/** + * This class implements the root of the intermediate representation. + * + * @author Norris Boyd + * @author Mike McCabe + */ + +public class Node implements Cloneable { + + private static class NumberNode extends Node { + + NumberNode(double number) { + super(TokenStream.NUMBER); + this.number = number; + } + + @Override + public double getDouble() { + return this.number; + } + + @Override + public boolean equals(Object o) { + return o instanceof NumberNode + && getDouble() == ((NumberNode) o).getDouble(); + } + + private double number; + } + + private static class StringNode extends Node { + + StringNode(int type, String str) { + super(type); + if (null == str) { + throw new IllegalArgumentException("StringNode: str is null"); + } + this.str = str; + } + + /** returns the string content. + * @return non null. + */ + @Override + public String getString() { + return this.str; + } + + /** sets the string content. + * @param str the new value. Non null. + */ + @Override + public void setString(String str) { + if (null == str) { + throw new IllegalArgumentException("StringNode: str is null"); + } + this.str = str; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof StringNode)) { return false; } + return o instanceof StringNode + && this.str.equals(((StringNode) o).str); + } + + private String str; + } + + public Node(int nodeType) { + type = nodeType; + } + + public Node(int nodeType, Node child) { + type = nodeType; + first = last = child; + child.next = null; + } + + public Node(int nodeType, Node left, Node right) { + type = nodeType; + first = left; + last = right; + left.next = right; + right.next = null; + } + + public Node(int nodeType, Node left, Node mid, Node right) { + type = nodeType; + first = left; + last = right; + left.next = mid; + mid.next = right; + right.next = null; + } + + public Node(int nodeType, Node left, Node mid, Node mid2, Node right) { + type = nodeType; + first = left; + last = right; + left.next = mid; + mid.next = mid2; + mid2.next = right; + right.next = null; + } + + public Node(int nodeType, Node[] children) { + this.type = nodeType; + if (children.length != 0) { + this.first = children[0]; + this.last = children[children.length - 1]; + + for (int i = 1; i < children.length; i++) { + if (null != children[i - 1].next) { + // fail early on loops. implies same node in array twice + throw new IllegalArgumentException("duplicate child"); + } + children[i - 1].next = children[i]; + } + if (null != this.last.next) { + // fail early on loops. implies same node in array twice + throw new IllegalArgumentException("duplicate child"); + } + } + } + + public Node(int nodeType, int value) { + type = nodeType; + intDatum = value; + } + + public Node(int nodeType, Node child, int value) { + this(nodeType, child); + intDatum = value; + } + + public Node(int nodeType, Node left, Node right, int value) { + this(nodeType, left, right); + intDatum = value; + } + + public Node(int nodeType, Node left, Node mid, Node right, int value) { + this(nodeType, left, mid, right); + intDatum = value; + } + + public static Node newNumber(double number) { + return new NumberNode(number); + } + + public static Node newString(String str) { + return new StringNode(TokenStream.STRING, str); + } + + public static Node newString(int type, String str) { + return new StringNode(type, str); + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getIntDatum() { + return this.intDatum; + } + + public boolean hasChildren() { + return first != null; + } + + public Node getFirstChild() { + return first; + } + + public Node getLastChild() { + return last; + } + + public Node getNext() { + return next; + } + + public int getChildCount() { + int c = 0; + for (Node n = first; n != null; n = n.next) + c++; + + return c; + } + + + public Node getChildBefore(Node child) { + if (child == first) + return null; + Node n = first; + while (n.next != child) { + n = n.next; + if (n == null) + throw new RuntimeException("node is not a child"); + } + return n; + } + + public Node getLastSibling() { + Node n = this; + while (n.next != null) { + n = n.next; + } + return n; + } + + public void addChildToFront(Node child) { + child.next = first; + first = child; + if (last == null) { + last = child; + } + } + + public void addChildToBack(Node child) { + child.next = null; + if (last == null) { + first = last = child; + return; + } + last.next = child; + last = child; + } + + public void addChildrenToFront(Node children) { + Node lastSib = children.getLastSibling(); + lastSib.next = first; + first = children; + if (last == null) { + last = lastSib; + } + } + + public void addChildrenToBack(Node children) { + if (last != null) { + last.next = children; + } + last = children.getLastSibling(); + if (first == null) { + first = children; + } + } + + /** + * Add 'child' before 'node'. + */ + public void addChildBefore(Node newChild, Node node) { + if (newChild.next != null) + throw new RuntimeException( + "newChild had siblings in addChildBefore"); + if (first == node) { + newChild.next = first; + first = newChild; + return; + } + Node prev = getChildBefore(node); + addChildAfter(newChild, prev); + } + + /** + * Add 'child' after 'node'. + */ + public void addChildAfter(Node newChild, Node node) { + if (newChild.next != null) + throw new RuntimeException( + "newChild had siblings in addChildAfter"); + newChild.next = node.next; + node.next = newChild; + if (last == node) + last = newChild; + } + + public void removeChild(Node child) { + Node prev = getChildBefore(child); + if (prev == null) + first = first.next; + else + prev.next = child.next; + if (child == last) last = prev; + child.next = null; + } + + public void replaceChild(Node child, Node newChild) { + newChild.next = child.next; + if (child == first) { + first = newChild; + } else { + Node prev = getChildBefore(child); + prev.next = newChild; + } + if (child == last) + last = newChild; + child.next = null; + } + + public void replaceChildAfter(Node prevChild, Node newChild) { + Node child = prevChild.next; + newChild.next = child.next; + prevChild.next = newChild; + if (child == last) + last = newChild; + child.next = null; + } + + public static final int + TARGET_PROP = 1, + BREAK_PROP = 2, + CONTINUE_PROP = 3, + ENUM_PROP = 4, + FUNCTION_PROP = 5, + TEMP_PROP = 6, + LOCAL_PROP = 7, + CODEOFFSET_PROP = 8, + FIXUPS_PROP = 9, + VARS_PROP = 10, + USES_PROP = 11, + REGEXP_PROP = 12, + CASES_PROP = 13, + DEFAULT_PROP = 14, + CASEARRAY_PROP = 15, + SOURCENAME_PROP = 16, + SOURCE_PROP = 17, + TYPE_PROP = 18, + SPECIAL_PROP_PROP = 19, + LABEL_PROP = 20, + FINALLY_PROP = 21, + LOCALCOUNT_PROP = 22, + /* + the following properties are defined and manipulated by the + optimizer - + TARGETBLOCK_PROP - the block referenced by a branch node + VARIABLE_PROP - the variable referenced by a BIND or NAME node + LASTUSE_PROP - that variable node is the last reference before + a new def or the end of the block + ISNUMBER_PROP - this node generates code on Number children and + delivers a Number result (as opposed to Objects) + DIRECTCALL_PROP - this call node should emit code to test the function + object against the known class and call diret if it + matches. + */ + + TARGETBLOCK_PROP = 23, + VARIABLE_PROP = 24, + LASTUSE_PROP = 25, + ISNUMBER_PROP = 26, + DIRECTCALL_PROP = 27, + + BASE_LINENO_PROP = 28, + END_LINENO_PROP = 29, + SPECIALCALL_PROP = 30, + DEBUGSOURCE_PROP = 31; + + public static final int // this value of the ISNUMBER_PROP specifies + BOTH = 0, // which of the children are Number types + LEFT = 1, + RIGHT = 2; + + private static final String propToString(int propType) { + switch (propType) { + case TARGET_PROP: return "target"; + case BREAK_PROP: return "break"; + case CONTINUE_PROP: return "continue"; + case ENUM_PROP: return "enum"; + case FUNCTION_PROP: return "function"; + case TEMP_PROP: return "temp"; + case LOCAL_PROP: return "local"; + case CODEOFFSET_PROP: return "codeoffset"; + case FIXUPS_PROP: return "fixups"; + case VARS_PROP: return "vars"; + case USES_PROP: return "uses"; + case REGEXP_PROP: return "regexp"; + case CASES_PROP: return "cases"; + case DEFAULT_PROP: return "default"; + case CASEARRAY_PROP: return "casearray"; + case SOURCENAME_PROP: return "sourcename"; + case SOURCE_PROP: return "source"; + case TYPE_PROP: return "type"; + case SPECIAL_PROP_PROP: return "special_prop"; + case LABEL_PROP: return "label"; + case FINALLY_PROP: return "finally"; + case LOCALCOUNT_PROP: return "localcount"; + + case TARGETBLOCK_PROP: return "targetblock"; + case VARIABLE_PROP: return "variable"; + case LASTUSE_PROP: return "lastuse"; + case ISNUMBER_PROP: return "isnumber"; + case DIRECTCALL_PROP: return "directcall"; + + case BASE_LINENO_PROP: return "base_lineno"; + case END_LINENO_PROP: return "end_lineno"; + case SPECIALCALL_PROP: return "specialcall"; + case DEBUGSOURCE_PROP: return "debugsource"; + + default: Context.codeBug(); + + } + return null; + } + + public Object getProp(int propType) { + if (props == null) + return null; + return props.getObject(propType); + } + + public int getIntProp(int propType, int defaultValue) { + if (props == null) + return defaultValue; + return props.getInt(propType, defaultValue); + } + + public int getExistingIntProp(int propType) { + return props.getExistingInt(propType); + } + + public void putProp(int propType, Object prop) { + if (prop == null) { + removeProp(propType); + } + else { + if (props == null) { + props = new UintMap(2); + } + props.put(propType, prop); + } + } + + public void putIntProp(int propType, int prop) { + if (props == null) + props = new UintMap(2); + props.put(propType, prop); + } + + public void removeProp(int propType) { + if (props != null) { + props.remove(propType); + } + } + + public int getOperation() { + switch (type) { + case TokenStream.EQOP: + case TokenStream.RELOP: + case TokenStream.UNARYOP: + case TokenStream.PRIMARY: + return intDatum; + } + Context.codeBug(); + return 0; + } + + public int getLineno() { + if (hasLineno()) { + return intDatum; + } + return -1; + } + + private boolean hasLineno() { + switch (type) { + case TokenStream.EXPRSTMT: + case TokenStream.BLOCK: + case TokenStream.VAR: + case TokenStream.WHILE: + case TokenStream.DO: + case TokenStream.SWITCH: + case TokenStream.CATCH: + case TokenStream.THROW: + case TokenStream.RETURN: + case TokenStream.BREAK: + case TokenStream.CONTINUE: + case TokenStream.WITH: + return true; + } + return false; + } + + /** Can only be called when getType() == TokenStream.NUMBER */ + public double getDouble() throws UnsupportedOperationException { + throw new UnsupportedOperationException(this + " is not a number node"); + } + + /** Can only be called when node has String context. */ + public String getString() throws UnsupportedOperationException { + throw new UnsupportedOperationException(this + " is not a string node"); + } + + /** Can only be called when node has String context. */ + public void setString(String s) throws UnsupportedOperationException { + throw new UnsupportedOperationException(this + " is not a string node"); + } + + /** + * Not usefully implemented. + */ + @Override + public final int hashCode() { + assert false : "hashCode not designed"; + return 42; // any arbitrary constant will do + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Node)) { return false; } + return hasLineno() + || this.getIntDatum() == ((Node) o).getIntDatum(); + } + + public Node cloneNode() { + Node result; + try { + result = (Node) super.clone(); + result.next = null; + result.first = null; + result.last = null; + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e.getMessage()); + } + return result; + } + + public Node cloneTree() { + Node result = cloneNode(); + for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) { + Node n2clone = n2.cloneTree(); + if (result.last != null) { + result.last.next = n2clone; + } + if (result.first == null) { + result.first = n2clone; + } + result.last = n2clone; + } + return result; + } + + + + @Override + public String toString() { + if (Context.printTrees) { + StringBuffer sb = new StringBuffer(TokenStream.tokenToName(type)); + if (this instanceof StringNode) { + sb.append(' '); + sb.append(getString()); + } else { + switch (type) { + case TokenStream.TARGET: + sb.append(' '); + sb.append(hashCode()); + break; + case TokenStream.NUMBER: + sb.append(' '); + sb.append(getDouble()); + break; + case TokenStream.FUNCTION: + sb.append(' '); + sb.append(first.getString()); + break; + } + } + if (intDatum != -1) { + sb.append(' '); + sb.append(intDatum); + } + if (props == null) + return sb.toString(); + + int[] keys = props.getKeys(); + for (int i = 0; i != keys.length; ++i) { + int key = keys[i]; + sb.append(" ["); + sb.append(propToString(key)); + sb.append(": "); + switch (key) { + case FIXUPS_PROP : // can't add this as it recurses + sb.append("fixups property"); + break; + case SOURCE_PROP : // can't add this as it has unprintables + sb.append("source property"); + break; + case TARGETBLOCK_PROP : // can't add this as it recurses + sb.append("target block property"); + break; + case LASTUSE_PROP : // can't add this as it is dull + sb.append("last use property"); + break; + default : + Object obj = props.getObject(key); + if (obj != null) { + sb.append(obj.toString()); + } else { + sb.append(props.getExistingInt(key)); + } + break; + } + sb.append(']'); + } + return sb.toString(); + } + return null; + } + + public String toStringTree() { + return toStringTreeHelper(0); + } + + + private String toStringTreeHelper(int level) { + if (Context.printTrees) { + StringBuffer s = new StringBuffer(); + for (int i=0; i < level; i++) { + s.append(" "); + } + s.append(toString()); + s.append('\n'); + for (Node cursor = getFirstChild(); cursor != null; + cursor = cursor.getNext()) + { + Node n = cursor; + s.append(n.toStringTreeHelper(level+1)); + } + return s.toString(); + } + return ""; + } + + /** + * Checks if the subtree under this node is the same as another subtree. + * Returns null if it's equal, or a message describing the differences. + */ + public String checkTreeEquals(Node node2) { + boolean eq = false; + + if (type == node2.getType() && + getChildCount() == node2.getChildCount() && + getClass() == node2.getClass()) { + + eq = this.equals(node2); + } + + if (!eq) { + return "Node tree inequality:\nTree1:\n" + toStringTreeHelper(1) + + "\n\nTree2:\n" + node2.toStringTreeHelper(1); + } + + String res = null; + Node n, n2; + for (n = first, n2 = node2.first; + res == null && n != null; + n = n.next, n2 = n2.next) { + res = n.checkTreeEquals(n2); + if (res != null) { + return res; + } + } + return res; + } + + public void setIsSyntheticBlock(boolean val) { + isSyntheticBlock = val; + } + + public boolean isSyntheticBlock() { + return isSyntheticBlock; + } + + int type; // type of the node; TokenStream.NAME for example + Node next; // next sibling + private Node first; // first element of a linked list of children + private Node last; // last element of a linked list of children + private int intDatum = -1; // encapsulated int data; depends on type + private UintMap props; + private boolean isSyntheticBlock = false; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/ObjToIntMap.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/ObjToIntMap.java new file mode 100644 index 00000000000..fd2fac44ed7 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/ObjToIntMap.java @@ -0,0 +1,697 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Map to associate objects to integers. + * The map does not synchronize any of its operation, so either use + * it from a single thread or do own synchronization or perform all mutation + * operations on one thread before passing the map to others + * + * @author Igor Bukanov + * + */ + +public class ObjToIntMap implements Serializable { + + public static final Object NULL_VALUE = new String(""); + +// Map implementation via hashtable, +// follows "The Art of Computer Programming" by Donald E. Knuth + +// ObjToIntMap is a copy cat of ObjToIntMap with API adjusted to object keys + + public static class Iterator { + + Iterator(ObjToIntMap master) { + this.master = master; + } + + final void init(Object[] keys, int[] values, int keyCount) { + this.keys = keys; + this.values = values; + this.cursor = -1; + this.remaining = keyCount; + } + + public void start() { + master.initIterator(this); + next(); + } + + public boolean done() { + return remaining < 0; + } + + public void next() { + if (remaining == -1) Context.codeBug(); + if (remaining == 0) { + remaining = -1; + cursor = -1; + }else { + for (++cursor; ; ++cursor) { + Object key = keys[cursor]; + if (key != null && key != DELETED) { + --remaining; + break; + } + } + } + } + + public Object getKey() { + Object key = keys[cursor]; + if (key == NULL_VALUE) { key = null; } + return key; + } + + public int getValue() { + return values[cursor]; + } + + public void setValue(int value) { + values[cursor] = value; + } + + ObjToIntMap master; + private int cursor; + private int remaining; + private Object[] keys; + private int[] values; + } + + public ObjToIntMap() { + this(4); + } + + public ObjToIntMap(int keyCountHint) { + if (keyCountHint < 0) Context.codeBug(); + // Table grow when number of stored keys >= 3/4 of max capacity + int minimalCapacity = keyCountHint * 4 / 3; + int i; + for (i = 2; (1 << i) < minimalCapacity; ++i) { } + power = i; + if (check && power < 2) Context.codeBug(); + } + + public boolean isEmpty() { + return keyCount == 0; + } + + public int size() { + return keyCount; + } + + public boolean has(Object key) { + if (key == null) { key = NULL_VALUE; } + return 0 <= findIndex(key); + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key is absent + */ + public int get(Object key, int defaultValue) { + if (key == null) { key = NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + return defaultValue; + } + + /** + * Get integer value assigned with key. + * @return key integer value + * @throws RuntimeException if key does not exist + */ + public int getExisting(Object key) { + if (key == null) { key = NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + // Key must exist + Context.codeBug(); + return 0; + } + + public void put(Object key, int value) { + if (key == null) { key = NULL_VALUE; } + int index = ensureIndex(key); + values[index] = value; + } + + /** + * If table already contains a key that equals to keyArg, return that key + * while setting its value to zero, otherwise add keyArg with 0 value to + * the table and return it. + */ + public Object intern(Object keyArg) { + boolean nullKey = false; + if (keyArg == null) { + nullKey = true; + keyArg = NULL_VALUE; + } + int index = ensureIndex(keyArg); + values[index] = 0; + return (nullKey) ? null : keys[index]; + } + + public void remove(Object key) { + if (key == null) { key = NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + keys[index] = DELETED; + --keyCount; + } + } + + public void clear() { + int i = keys.length; + while (i != 0) { + keys[--i] = null; + } + keyCount = 0; + occupiedCount = 0; + } + + public Iterator newIterator() { + return new Iterator(this); + } + + // The sole purpose of the method is to avoid accessing private fields + // from the Iterator inner class to workaround JDK 1.1 compiler bug which + // generates code triggering VerifierError on recent JVMs + final void initIterator(Iterator i) { + i.init(keys, values, keyCount); + } + + /** Return array of present keys */ + public Object[] getKeys() { + Object[] array = new Object[keyCount]; + getKeys(array, 0); + return array; + } + + public void getKeys(Object[] array, int offset) { + int count = keyCount; + for (int i = 0; count != 0; ++i) { + Object key = keys[i]; + if (key != null && key != DELETED) { + if (key == NULL_VALUE) { key = null; } + array[offset] = key; + ++offset; + --count; + } + } + } + + private static int tableLookupStep(int fraction, int mask, int power) { + int shift = 32 - 2 * power; + if (shift >= 0) { + return ((fraction >>> shift) & mask) | 1; + } + else { + return (fraction & (mask >>> -shift)) | 1; + } + } + + private int findIndex(Object key) { + if (keys != null) { + int hash = key.hashCode(); + int fraction = hash * A; + int index = fraction >>> (32 - power); + Object test = keys[index]; + if (test != null) { + int N = 1 << power; + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + // Search in table after first failed attempt + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + for (;;) { + if (check) { + if (n >= occupiedCount) Context.codeBug(); + ++n; + } + index = (index + step) & mask; + test = keys[index]; + if (test == null) { + break; + } + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + } + } + } + return -1; + } + +// Insert key that is not present to table without deleted entries +// and enough free space + private int insertNewKey(Object key, int hash) { + if (check && occupiedCount != keyCount) Context.codeBug(); + if (check && keyCount == 1 << power) Context.codeBug(); + int fraction = hash * A; + int index = fraction >>> (32 - power); + int N = 1 << power; + if (keys[index] != null) { + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int firstIndex = index; + do { + if (check && keys[index] == DELETED) Context.codeBug(); + index = (index + step) & mask; + if (check && firstIndex == index) Context.codeBug(); + } while (keys[index] != null); + } + keys[index] = key; + values[N + index] = hash; + ++occupiedCount; + ++keyCount; + + return index; + } + + private void rehashTable() { + if (keys == null) { + if (check && keyCount != 0) Context.codeBug(); + if (check && occupiedCount != 0) Context.codeBug(); + int N = 1 << power; + keys = new Object[N]; + values = new int[2 * N]; + } + else { + // Check if removing deleted entries would free enough space + if (keyCount * 2 >= occupiedCount) { + // Need to grow: less then half of deleted entries + ++power; + } + int N = 1 << power; + Object[] oldKeys = keys; + int[] oldValues = values; + int oldN = oldKeys.length; + keys = new Object[N]; + values = new int[2 * N]; + + int remaining = keyCount; + occupiedCount = keyCount = 0; + for (int i = 0; remaining != 0; ++i) { + Object key = oldKeys[i]; + if (key != null && key != DELETED) { + int keyHash = oldValues[oldN + i]; + int index = insertNewKey(key, keyHash); + values[index] = oldValues[i]; + --remaining; + } + } + } + } + +// Ensure key index creating one if necessary + private int ensureIndex(Object key) { + int hash = key.hashCode(); + int index = -1; + int firstDeleted = -1; + if (keys != null) { + int fraction = hash * A; + index = fraction >>> (32 - power); + Object test = keys[index]; + if (test != null) { + int N = 1 << power; + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + if (test == DELETED) { + firstDeleted = index; + } + + // Search in table after first failed attempt + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + for (;;) { + if (check) { + if (n >= occupiedCount) Context.codeBug(); + ++n; + } + index = (index + step) & mask; + test = keys[index]; + if (test == null) { + break; + } + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + if (test == DELETED && firstDeleted < 0) { + firstDeleted = index; + } + } + } + } + // Inserting of new key + if (check && keys != null && keys[index] != null) + Context.codeBug(); + if (firstDeleted >= 0) { + index = firstDeleted; + } + else { + // Need to consume empty entry: check occupation level + if (keys == null || occupiedCount * 4 >= (1 << power) * 3) { + // Too litle unused entries: rehash + rehashTable(); + return insertNewKey(key, hash); + } + ++occupiedCount; + } + keys[index] = key; + values[(1 << power) + index] = hash; + ++keyCount; + return index; + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + + int count = keyCount; + for (int i = 0; count != 0; ++i) { + Object key = keys[i]; + if (key != null && key != DELETED) { + --count; + out.writeObject(key); + out.writeInt(values[i]); + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + int writtenKeyCount = keyCount; + if (writtenKeyCount != 0) { + keyCount = 0; + int N = 1 << power; + keys = new Object[N]; + values = new int[2 * N]; + for (int i = 0; i != writtenKeyCount; ++i) { + Object key = in.readObject(); + int hash = key.hashCode(); + int index = insertNewKey(key, hash); + values[index] = in.readInt(); + } + } + } + + static final long serialVersionUID = 3396438333234169727L; + +// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32) +// See Knuth etc. + private static final int A = 0x9e3779b9; + + private static final Object DELETED = new Object(); + +// Structure of kyes and values arrays (N == 1 << power): +// keys[0 <= i < N]: key value or null or DELETED mark +// values[0 <= i < N]: value of key at keys[i] +// values[N <= i < 2*N]: hash code of key at keys[i-N] + + private transient Object[] keys; + private transient int[] values; + + private int power; + private int keyCount; + private transient int occupiedCount; // == keyCount + deleted_count + +// If true, enables consitency checks + private static final boolean check = false; + +/* TEST START + + public static void main(String[] args) { + if (!check) { + System.err.println("Set check to true and re-run"); + throw new RuntimeException("Set check to true and re-run"); + } + + ObjToIntMap map; + map = new ObjToIntMap(0); + testHash(map, 3); + map = new ObjToIntMap(0); + testHash(map, 10 * 1000); + map = new ObjToIntMap(); + testHash(map, 10 * 1000); + map = new ObjToIntMap(30 * 1000); + testHash(map, 10 * 100); + map.clear(); + testHash(map, 4); + map = new ObjToIntMap(0); + testHash(map, 10 * 100); + } + + private static void testHash(ObjToIntMap map, int N) { + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(-1 == map.get(key, -1)); + map.put(key, i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + Object[] keys = map.getKeys(); + check(keys.length == N); + for (int i = 0; i != N; ++i) { + Object key = keys[i]; + check(map.has(key)); + } + + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == map.get(key, -1)); + } + + int Nsqrt = -1; + for (int i = 0; ; ++i) { + if (i * i >= N) { + Nsqrt = i; + break; + } + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == 2 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(-1 - i * i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == 3 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(-1 - i * i); + map.remove(key); + check(!map.has(key)); + } + + check(map.size() == 2 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + int j = intSqrt(i); + if (j * j == i) { + check(j == map.get(key, -1)); + }else { + check(i == map.get(key, -1)); + } + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + map.remove(key); + check(-2 == map.get(key, -2)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + map.put(key, i); + check(i == map.get(key, -2)); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + ObjToIntMap copy = (ObjToIntMap)writeAndRead(map); + check(copy.size() == N); + + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == copy.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + checkSameMaps(copy, map); + + System.out.println(); System.out.flush(); + } + + private static void checkSameMaps(ObjToIntMap map1, ObjToIntMap map2) { + check(map1.size() == map2.size()); + Object[] keys = map1.getKeys(); + check(keys.length == map1.size()); + for (int i = 0; i != keys.length; ++i) { + check(map1.get(keys[i], -1) == map2.get(keys[i], -1)); + } + } + + private static void check(boolean condition) { + if (!condition) Context.codeBug(); + } + + private static Object[] testPool; + + private static Object testKey(int i) { + int MAX_POOL = 100; + if (0 <= i && i < MAX_POOL) { + if (testPool != null && testPool[i] != null) { + return testPool[i]; + } + } + Object x = new Double(i + 0.5); + if (0 <= i && i < MAX_POOL) { + if (testPool == null) { + testPool = new Object[MAX_POOL]; + } + testPool[i] = x; + } + return x; + } + + private static int intSqrt(int i) { + int approx = (int)Math.sqrt(i) + 1; + while (approx * approx > i) { + --approx; + } + return approx; + } + + private static Object writeAndRead(Object obj) { + try { + java.io.ByteArrayOutputStream + bos = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream + out = new java.io.ObjectOutputStream(bos); + out.writeObject(obj); + out.close(); + byte[] data = bos.toByteArray(); + java.io.ByteArrayInputStream + bis = new java.io.ByteArrayInputStream(data); + java.io.ObjectInputStream + in = new java.io.ObjectInputStream(bis); + Object result = in.readObject(); + in.close(); + return result; + }catch (Exception ex) { + throw new RuntimeException("Unexpected"); + } + } + +// TEST END */ + +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java new file mode 100644 index 00000000000..6d571da6afe --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/Parser.java @@ -0,0 +1,1510 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Mike Ang + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.IOException; + +/** + * This class implements the JavaScript parser. + * + * It is based on the C source files jsparse.c and jsparse.h in the jsref + * package. + * + * @see TokenStream + * + * @author Mike McCabe + * @author Brendan Eich + */ + +public class Parser { + + public Parser(IRFactory nf) { + this.nf = nf; + } + + private void mustMatchToken(TokenStream ts, int toMatch, String messageId) + throws IOException, JavaScriptException { + int tt; + if ((tt = ts.getToken()) != toMatch) { + reportError(ts, messageId); + ts.ungetToken(tt); // In case the parser decides to continue + } + } + + private void reportError(TokenStream ts, String messageId) + throws JavaScriptException { + this.ok = false; + ts.reportSyntaxError(messageId, null); + + /* + * Throw an exception to unwind the recursive descent parse. We use + * JavaScriptException here even though it is really a different use of the + * exception than it is usually used for. + */ + throw new JavaScriptException(messageId); + } + + /* + * Build a parse tree from the given TokenStream. + * + * @param ts the TokenStream to parse + * + * @return an Object representing the parsed program. If the parse fails, null + * will be returned. (The parse failure will result in a call to the current + * Context's ErrorReporter.) + */ + public Object parse(TokenStream ts) throws IOException { + this.ok = true; + sourceTop = 0; + functionNumber = 0; + + int tt; // last token from getToken(); + int baseLineno = ts.getLineno(); // line number where source starts + + /* + * so we have something to add nodes to until we've collected all the source + */ + Object tempBlock = nf.createLeaf(TokenStream.BLOCK); + ((Node) tempBlock).setIsSyntheticBlock(true); + + while (true) { + ts.flags |= ts.TSF_REGEXP; + tt = ts.getToken(); + ts.flags &= ~ts.TSF_REGEXP; + + if (tt <= ts.EOF) { + break; + } + + if (tt == ts.FUNCTION) { + try { + nf.addChildToBack(tempBlock, function(ts, false)); + } catch (JavaScriptException e) { + this.ok = false; + break; + } + } else { + ts.ungetToken(tt); + nf.addChildToBack(tempBlock, statement(ts)); + } + } + + if (!this.ok) { + // XXX ts.clearPushback() call here? + return null; + } + + Object pn = nf.createScript(tempBlock, ts.getSourceName(), baseLineno, ts + .getLineno(), sourceToString(0)); + ((Node) pn).setIsSyntheticBlock(true); + return pn; + } + + /* + * The C version of this function takes an argument list, which doesn't seem + * to be needed for tree generation... it'd only be useful for checking + * argument hiding, which I'm not doing anyway... + */ + private Object parseFunctionBody(TokenStream ts) throws IOException { + int oldflags = ts.flags; + ts.flags &= ~(TokenStream.TSF_RETURN_EXPR | TokenStream.TSF_RETURN_VOID); + ts.flags |= TokenStream.TSF_FUNCTION; + + Object pn = nf.createBlock(ts.getLineno()); + try { + int tt; + while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) { + if (tt == TokenStream.FUNCTION) { + ts.getToken(); + nf.addChildToBack(pn, function(ts, false)); + } else { + nf.addChildToBack(pn, statement(ts)); + } + } + } catch (JavaScriptException e) { + this.ok = false; + } finally { + // also in finally block: + // flushNewLines, clearPushback. + + ts.flags = oldflags; + } + + return pn; + } + + private Object function(TokenStream ts, boolean isExpr) throws IOException, + JavaScriptException { + int baseLineno = ts.getLineno(); // line number where source starts + + String name; + Object memberExprNode = null; + if (ts.matchToken(ts.NAME)) { + name = ts.getString(); + if (!ts.matchToken(ts.LP)) { + if (Context.getContext().hasFeature( + Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { + // Extension to ECMA: if 'function ' does not follow + // by '(', assume starts memberExpr + sourceAddString(ts.NAME, name); + Object memberExprHead = nf.createName(name); + name = null; + memberExprNode = memberExprTail(ts, false, memberExprHead); + } + mustMatchToken(ts, ts.LP, "msg.no.paren.parms"); + } + } else if (ts.matchToken(ts.LP)) { + // Anonymous function + name = null; + } else { + name = null; + if (Context.getContext().hasFeature( + Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)) { + // Note that memberExpr can not start with '(' like + // in (1+2).toString, because 'function (' already + // processed as anonymous function + memberExprNode = memberExpr(ts, false); + } + mustMatchToken(ts, ts.LP, "msg.no.paren.parms"); + } + + if (memberExprNode != null) { + // transform 'function' to = function + // even in the decompilated source + sourceAdd((char) ts.ASSIGN); + sourceAdd((char) ts.NOP); + } + + // save a reference to the function in the enclosing source. + sourceAdd((char) ts.FUNCTION); + sourceAdd((char) functionNumber); + ++functionNumber; + + // Save current source top to restore it on exit not to include + // function to parent source + int savedSourceTop = sourceTop; + int savedFunctionNumber = functionNumber; + Object args; + Object body; + String source; + try { + functionNumber = 0; + + // FUNCTION as the first token in a Source means it's a function + // definition, and not a reference. + sourceAdd((char) ts.FUNCTION); + if (name != null) { + sourceAddString(ts.NAME, name); + } + sourceAdd((char) ts.LP); + args = nf.createLeaf(ts.LP); + + if (!ts.matchToken(ts.GWT)) { + boolean first = true; + do { + if (!first) + sourceAdd((char) ts.COMMA); + first = false; + mustMatchToken(ts, ts.NAME, "msg.no.parm"); + String s = ts.getString(); + nf.addChildToBack(args, nf.createName(s)); + + sourceAddString(ts.NAME, s); + } while (ts.matchToken(ts.COMMA)); + + mustMatchToken(ts, ts.GWT, "msg.no.paren.after.parms"); + } + sourceAdd((char) ts.GWT); + + mustMatchToken(ts, ts.LC, "msg.no.brace.body"); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + body = parseFunctionBody(ts); + mustMatchToken(ts, ts.RC, "msg.no.brace.after.body"); + sourceAdd((char) ts.RC); + // skip the last EOL so nested functions work... + + // name might be null; + source = sourceToString(savedSourceTop); + } finally { + sourceTop = savedSourceTop; + functionNumber = savedFunctionNumber; + } + + Object pn = nf.createFunction(name, args, body, ts.getSourceName(), + baseLineno, ts.getLineno(), source, isExpr || memberExprNode != null); + if (memberExprNode != null) { + pn = nf.createBinary(ts.ASSIGN, ts.NOP, memberExprNode, pn); + } + + // Add EOL but only if function is not part of expression, in which + // case it gets SEMI + EOL from Statement. + if (!isExpr) { + if (memberExprNode != null) { + // Add ';' to make 'function x.f(){}' and 'x.f = function(){}' + // to print the same strings when decompiling + sourceAdd((char) ts.SEMI); + } + sourceAdd((char) ts.EOL); + wellTerminated(ts, ts.FUNCTION); + } + + return pn; + } + + private Object statements(TokenStream ts) throws IOException { + Object pn = nf.createBlock(ts.getLineno()); + + int tt; + while ((tt = ts.peekToken()) > ts.EOF && tt != ts.RC) { + nf.addChildToBack(pn, statement(ts)); + } + + return pn; + } + + private Object condition(TokenStream ts) throws IOException, + JavaScriptException { + Object pn; + mustMatchToken(ts, ts.LP, "msg.no.paren.cond"); + sourceAdd((char) ts.LP); + pn = expr(ts, false); + mustMatchToken(ts, ts.GWT, "msg.no.paren.after.cond"); + sourceAdd((char) ts.GWT); + + // there's a check here in jsparse.c that corrects = to == + + return pn; + } + + private boolean wellTerminated(TokenStream ts, int lastExprType) + throws IOException, JavaScriptException { + int tt = ts.peekTokenSameLine(); + if (tt == ts.ERROR) { + return false; + } + + if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) { + int version = Context.getContext().getLanguageVersion(); + if ((tt == ts.FUNCTION || lastExprType == ts.FUNCTION) + && (version < Context.VERSION_1_2)) { + /* + * Checking against version < 1.2 and version >= 1.0 in the above line + * breaks old javascript, so we keep it this way for now... XXX warning + * needed? + */ + return true; + } else { + reportError(ts, "msg.no.semi.stmt"); + } + } + return true; + } + + // match a NAME; return null if no match. + private String matchLabel(TokenStream ts) throws IOException, + JavaScriptException { + int lineno = ts.getLineno(); + + String label = null; + int tt; + tt = ts.peekTokenSameLine(); + if (tt == ts.NAME) { + ts.getToken(); + label = ts.getString(); + } + + if (lineno == ts.getLineno()) + wellTerminated(ts, ts.ERROR); + + return label; + } + + private Object statement(TokenStream ts) throws IOException { + try { + return statementHelper(ts); + } catch (JavaScriptException e) { + // skip to end of statement + int lineno = ts.getLineno(); + int t; + do { + t = ts.getToken(); + } while (t != TokenStream.SEMI && t != TokenStream.EOL + && t != TokenStream.EOF && t != TokenStream.ERROR); + return nf.createExprStatement(nf.createName("error"), lineno); + } + } + + /** + * Whether the "catch (e: e instanceof Exception) { ... }" syntax is + * implemented. + */ + + private Object statementHelper(TokenStream ts) throws IOException, + JavaScriptException { + Object pn = null; + + // If skipsemi == true, don't add SEMI + EOL to source at the + // end of this statment. For compound statements, IF/FOR etc. + boolean skipsemi = false; + + int tt; + + int lastExprType = 0; // For wellTerminated. 0 to avoid warning. + + tt = ts.getToken(); + + switch (tt) { + case TokenStream.IF: { + skipsemi = true; + + sourceAdd((char) ts.IF); + int lineno = ts.getLineno(); + Object cond = condition(ts); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + Object ifTrue = statement(ts); + Object ifFalse = null; + if (ts.matchToken(ts.ELSE)) { + sourceAdd((char) ts.RC); + sourceAdd((char) ts.ELSE); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + ifFalse = statement(ts); + } + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + pn = nf.createIf(cond, ifTrue, ifFalse, lineno); + break; + } + + case TokenStream.SWITCH: { + skipsemi = true; + + sourceAdd((char) ts.SWITCH); + pn = nf.createSwitch(ts.getLineno()); + + Object cur_case = null; // to kill warning + Object case_statements; + + mustMatchToken(ts, ts.LP, "msg.no.paren.switch"); + sourceAdd((char) ts.LP); + nf.addChildToBack(pn, expr(ts, false)); + mustMatchToken(ts, ts.GWT, "msg.no.paren.after.switch"); + sourceAdd((char) ts.GWT); + mustMatchToken(ts, ts.LC, "msg.no.brace.switch"); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + + while ((tt = ts.getToken()) != ts.RC && tt != ts.EOF) { + switch (tt) { + case TokenStream.CASE: + sourceAdd((char) ts.CASE); + cur_case = nf.createUnary(ts.CASE, expr(ts, false)); + sourceAdd((char) ts.COLON); + sourceAdd((char) ts.EOL); + break; + + case TokenStream.DEFAULT: + cur_case = nf.createLeaf(ts.DEFAULT); + sourceAdd((char) ts.DEFAULT); + sourceAdd((char) ts.COLON); + sourceAdd((char) ts.EOL); + // XXX check that there isn't more than one default + break; + + default: + reportError(ts, "msg.bad.switch"); + break; + } + mustMatchToken(ts, ts.COLON, "msg.no.colon.case"); + + case_statements = nf.createLeaf(TokenStream.BLOCK); + ((Node) case_statements).setIsSyntheticBlock(true); + + while ((tt = ts.peekToken()) != ts.RC && tt != ts.CASE + && tt != ts.DEFAULT && tt != ts.EOF) { + nf.addChildToBack(case_statements, statement(ts)); + } + // assert cur_case + nf.addChildToBack(cur_case, case_statements); + + nf.addChildToBack(pn, cur_case); + } + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + break; + } + + case TokenStream.WHILE: { + skipsemi = true; + + sourceAdd((char) ts.WHILE); + int lineno = ts.getLineno(); + Object cond = condition(ts); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + Object body = statement(ts); + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + + pn = nf.createWhile(cond, body, lineno); + break; + + } + + case TokenStream.DO: { + sourceAdd((char) ts.DO); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + + int lineno = ts.getLineno(); + + Object body = statement(ts); + + sourceAdd((char) ts.RC); + mustMatchToken(ts, ts.WHILE, "msg.no.while.do"); + sourceAdd((char) ts.WHILE); + Object cond = condition(ts); + + pn = nf.createDoWhile(body, cond, lineno); + break; + } + + case TokenStream.FOR: { + skipsemi = true; + + sourceAdd((char) ts.FOR); + int lineno = ts.getLineno(); + + Object init; // Node init is also foo in 'foo in Object' + Object cond; // Node cond is also object in 'foo in Object' + Object incr = null; // to kill warning + Object body; + + mustMatchToken(ts, ts.LP, "msg.no.paren.for"); + sourceAdd((char) ts.LP); + tt = ts.peekToken(); + if (tt == ts.SEMI) { + init = nf.createLeaf(ts.VOID); + } else { + if (tt == ts.VAR) { + // set init to a var list or initial + ts.getToken(); // throw away the 'var' token + init = variables(ts, true); + } else { + init = expr(ts, true); + } + } + + tt = ts.peekToken(); + if (tt == ts.RELOP && ts.getOp() == ts.IN) { + ts.matchToken(ts.RELOP); + sourceAdd((char) ts.IN); + // 'cond' is the object over which we're iterating + cond = expr(ts, false); + } else { // ordinary for loop + mustMatchToken(ts, ts.SEMI, "msg.no.semi.for"); + sourceAdd((char) ts.SEMI); + if (ts.peekToken() == ts.SEMI) { + // no loop condition + cond = nf.createLeaf(ts.VOID); + } else { + cond = expr(ts, false); + } + + mustMatchToken(ts, ts.SEMI, "msg.no.semi.for.cond"); + sourceAdd((char) ts.SEMI); + if (ts.peekToken() == ts.GWT) { + incr = nf.createLeaf(ts.VOID); + } else { + incr = expr(ts, false); + } + } + + mustMatchToken(ts, ts.GWT, "msg.no.paren.for.ctrl"); + sourceAdd((char) ts.GWT); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + body = statement(ts); + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + + if (incr == null) { + // cond could be null if 'in obj' got eaten by the init node. + pn = nf.createForIn(init, cond, body, lineno); + } else { + pn = nf.createFor(init, cond, incr, body, lineno); + } + break; + } + + case TokenStream.TRY: { + int lineno = ts.getLineno(); + + Object tryblock; + Object catchblocks = null; + Object finallyblock = null; + + skipsemi = true; + sourceAdd((char) ts.TRY); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + tryblock = statement(ts); + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + + catchblocks = nf.createLeaf(TokenStream.BLOCK); + + boolean sawDefaultCatch = false; + int peek = ts.peekToken(); + if (peek == ts.CATCH) { + while (ts.matchToken(ts.CATCH)) { + if (sawDefaultCatch) { + reportError(ts, "msg.catch.unreachable"); + } + sourceAdd((char) ts.CATCH); + mustMatchToken(ts, ts.LP, "msg.no.paren.catch"); + sourceAdd((char) ts.LP); + + mustMatchToken(ts, ts.NAME, "msg.bad.catchcond"); + String varName = ts.getString(); + sourceAddString(ts.NAME, varName); + + Object catchCond = null; + if (ts.matchToken(ts.IF)) { + sourceAdd((char) ts.IF); + catchCond = expr(ts, false); + } else { + sawDefaultCatch = true; + } + + mustMatchToken(ts, ts.GWT, "msg.bad.catchcond"); + sourceAdd((char) ts.GWT); + mustMatchToken(ts, ts.LC, "msg.no.brace.catchblock"); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + + nf.addChildToBack(catchblocks, nf.createCatch(varName, catchCond, + statements(ts), ts.getLineno())); + + mustMatchToken(ts, ts.RC, "msg.no.brace.after.body"); + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + } + } else if (peek != ts.FINALLY) { + mustMatchToken(ts, ts.FINALLY, "msg.try.no.catchfinally"); + } + + if (ts.matchToken(ts.FINALLY)) { + sourceAdd((char) ts.FINALLY); + + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + finallyblock = statement(ts); + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + } + + pn = nf.createTryCatchFinally(tryblock, catchblocks, finallyblock, + lineno); + + break; + } + case TokenStream.THROW: { + int lineno = ts.getLineno(); + sourceAdd((char) ts.THROW); + pn = nf.createThrow(expr(ts, false), lineno); + if (lineno == ts.getLineno()) + wellTerminated(ts, ts.ERROR); + break; + } + case TokenStream.BREAK: { + int lineno = ts.getLineno(); + + sourceAdd((char) ts.BREAK); + + // matchLabel only matches if there is one + String label = matchLabel(ts); + if (label != null) { + sourceAddString(ts.NAME, label); + } + pn = nf.createBreak(label, lineno); + break; + } + case TokenStream.CONTINUE: { + int lineno = ts.getLineno(); + + sourceAdd((char) ts.CONTINUE); + + // matchLabel only matches if there is one + String label = matchLabel(ts); + if (label != null) { + sourceAddString(ts.NAME, label); + } + pn = nf.createContinue(label, lineno); + break; + } + case TokenStream.DEBUGGER: { + int lineno = ts.getLineno(); + + sourceAdd((char) ts.DEBUGGER); + + pn = nf.createDebugger(lineno); + break; + } + case TokenStream.WITH: { + // bruce: we don't support this is JSNI code because it's impossible + // to identify bindings even passably well + // + + reportError(ts, "msg.jsni.unsupported.with"); + + skipsemi = true; + + sourceAdd((char) ts.WITH); + int lineno = ts.getLineno(); + mustMatchToken(ts, ts.LP, "msg.no.paren.with"); + sourceAdd((char) ts.LP); + Object obj = expr(ts, false); + mustMatchToken(ts, ts.GWT, "msg.no.paren.after.with"); + sourceAdd((char) ts.GWT); + sourceAdd((char) ts.LC); + sourceAdd((char) ts.EOL); + + Object body = statement(ts); + + sourceAdd((char) ts.RC); + sourceAdd((char) ts.EOL); + + pn = nf.createWith(obj, body, lineno); + break; + } + case TokenStream.VAR: { + int lineno = ts.getLineno(); + pn = variables(ts, false); + if (ts.getLineno() == lineno) + wellTerminated(ts, ts.ERROR); + break; + } + case TokenStream.RETURN: { + Object retExpr = null; + int lineno = 0; + + sourceAdd((char) ts.RETURN); + + // bail if we're not in a (toplevel) function + if ((ts.flags & ts.TSF_FUNCTION) == 0) + reportError(ts, "msg.bad.return"); + + /* This is ugly, but we don't want to require a semicolon. */ + ts.flags |= ts.TSF_REGEXP; + tt = ts.peekTokenSameLine(); + ts.flags &= ~ts.TSF_REGEXP; + + if (tt != ts.EOF && tt != ts.EOL && tt != ts.SEMI && tt != ts.RC) { + lineno = ts.getLineno(); + retExpr = expr(ts, false); + if (ts.getLineno() == lineno) + wellTerminated(ts, ts.ERROR); + ts.flags |= ts.TSF_RETURN_EXPR; + } else { + ts.flags |= ts.TSF_RETURN_VOID; + } + + // XXX ASSERT pn + pn = nf.createReturn(retExpr, lineno); + break; + } + case TokenStream.LC: + skipsemi = true; + + pn = statements(ts); + mustMatchToken(ts, ts.RC, "msg.no.brace.block"); + break; + + case TokenStream.ERROR: + // Fall thru, to have a node for error recovery to work on + case TokenStream.EOL: + case TokenStream.SEMI: + pn = nf.createLeaf(ts.VOID); + skipsemi = true; + break; + + default: { + lastExprType = tt; + int tokenno = ts.getTokenno(); + ts.ungetToken(tt); + int lineno = ts.getLineno(); + + pn = expr(ts, false); + + if (ts.peekToken() == ts.COLON) { + /* + * check that the last thing the tokenizer returned was a NAME and + * that only one token was consumed. + */ + if (lastExprType != ts.NAME || (ts.getTokenno() != tokenno)) + reportError(ts, "msg.bad.label"); + + ts.getToken(); // eat the COLON + + /* + * in the C source, the label is associated with the statement that + * follows: nf.addChildToBack(pn, statement(ts)); + */ + String name = ts.getString(); + pn = nf.createLabel(name, lineno); + + // bruce: added to make it easier to bind labels to the + // statements they modify + // + nf.addChildToBack(pn, statement(ts)); + + // depend on decompiling lookahead to guess that that + // last name was a label. + sourceAdd((char) ts.COLON); + sourceAdd((char) ts.EOL); + return pn; + } + + if (lastExprType == ts.FUNCTION) { + if (nf.getLeafType(pn) != ts.FUNCTION) { + reportError(ts, "msg.syntax"); + } + } + + pn = nf.createExprStatement(pn, lineno); + + /* + * Check explicitly against (multi-line) function statement. + * + * lastExprEndLine is a hack to fix an automatic semicolon insertion + * problem with function expressions; the ts.getLineno() == lineno check + * was firing after a function definition even though the next statement + * was on a new line, because speculative getToken calls advanced the + * line number even when they didn't succeed. + */ + if (ts.getLineno() == lineno + || (lastExprType == ts.FUNCTION && ts.getLineno() == lastExprEndLine)) { + wellTerminated(ts, lastExprType); + } + break; + } + } + ts.matchToken(ts.SEMI); + if (!skipsemi) { + sourceAdd((char) ts.SEMI); + sourceAdd((char) ts.EOL); + } + + return pn; + } + + private Object variables(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object pn = nf.createVariables(ts.getLineno()); + boolean first = true; + + sourceAdd((char) ts.VAR); + + for (;;) { + Object name; + Object init; + mustMatchToken(ts, ts.NAME, "msg.bad.var"); + String s = ts.getString(); + + if (!first) + sourceAdd((char) ts.COMMA); + first = false; + + sourceAddString(ts.NAME, s); + name = nf.createName(s); + + // omitted check for argument hiding + + if (ts.matchToken(ts.ASSIGN)) { + if (ts.getOp() != ts.NOP) + reportError(ts, "msg.bad.var.init"); + + sourceAdd((char) ts.ASSIGN); + sourceAdd((char) ts.NOP); + + init = assignExpr(ts, inForInit); + nf.addChildToBack(name, init); + } + nf.addChildToBack(pn, name); + if (!ts.matchToken(ts.COMMA)) + break; + } + return pn; + } + + private Object expr(TokenStream ts, boolean inForInit) throws IOException, + JavaScriptException { + Object pn = assignExpr(ts, inForInit); + while (ts.matchToken(ts.COMMA)) { + sourceAdd((char) ts.COMMA); + pn = nf.createBinary(ts.COMMA, pn, assignExpr(ts, inForInit)); + } + return pn; + } + + private Object assignExpr(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object pn = condExpr(ts, inForInit); + + if (ts.matchToken(ts.ASSIGN)) { + // omitted: "invalid assignment left-hand side" check. + sourceAdd((char) ts.ASSIGN); + sourceAdd((char) ts.getOp()); + pn = nf + .createBinary(ts.ASSIGN, ts.getOp(), pn, assignExpr(ts, inForInit)); + } + + return pn; + } + + private Object condExpr(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object ifTrue; + Object ifFalse; + + Object pn = orExpr(ts, inForInit); + + if (ts.matchToken(ts.HOOK)) { + sourceAdd((char) ts.HOOK); + ifTrue = assignExpr(ts, false); + mustMatchToken(ts, ts.COLON, "msg.no.colon.cond"); + sourceAdd((char) ts.COLON); + ifFalse = assignExpr(ts, inForInit); + return nf.createTernary(pn, ifTrue, ifFalse); + } + + return pn; + } + + private Object orExpr(TokenStream ts, boolean inForInit) throws IOException, + JavaScriptException { + Object pn = andExpr(ts, inForInit); + if (ts.matchToken(ts.OR)) { + sourceAdd((char) ts.OR); + pn = nf.createBinary(ts.OR, pn, orExpr(ts, inForInit)); + } + + return pn; + } + + private Object andExpr(TokenStream ts, boolean inForInit) throws IOException, + JavaScriptException { + Object pn = bitOrExpr(ts, inForInit); + if (ts.matchToken(ts.AND)) { + sourceAdd((char) ts.AND); + pn = nf.createBinary(ts.AND, pn, andExpr(ts, inForInit)); + } + + return pn; + } + + private Object bitOrExpr(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object pn = bitXorExpr(ts, inForInit); + while (ts.matchToken(ts.BITOR)) { + sourceAdd((char) ts.BITOR); + pn = nf.createBinary(ts.BITOR, pn, bitXorExpr(ts, inForInit)); + } + return pn; + } + + private Object bitXorExpr(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object pn = bitAndExpr(ts, inForInit); + while (ts.matchToken(ts.BITXOR)) { + sourceAdd((char) ts.BITXOR); + pn = nf.createBinary(ts.BITXOR, pn, bitAndExpr(ts, inForInit)); + } + return pn; + } + + private Object bitAndExpr(TokenStream ts, boolean inForInit) + throws IOException, JavaScriptException { + Object pn = eqExpr(ts, inForInit); + while (ts.matchToken(ts.BITAND)) { + sourceAdd((char) ts.BITAND); + pn = nf.createBinary(ts.BITAND, pn, eqExpr(ts, inForInit)); + } + return pn; + } + + private Object eqExpr(TokenStream ts, boolean inForInit) throws IOException, + JavaScriptException { + Object pn = relExpr(ts, inForInit); + while (ts.matchToken(ts.EQOP)) { + sourceAdd((char) ts.EQOP); + sourceAdd((char) ts.getOp()); + pn = nf.createBinary(ts.EQOP, ts.getOp(), pn, relExpr(ts, inForInit)); + } + return pn; + } + + private Object relExpr(TokenStream ts, boolean inForInit) throws IOException, + JavaScriptException { + Object pn = shiftExpr(ts); + while (ts.matchToken(ts.RELOP)) { + int op = ts.getOp(); + if (inForInit && op == ts.IN) { + ts.ungetToken(ts.RELOP); + break; + } + sourceAdd((char) ts.RELOP); + sourceAdd((char) op); + pn = nf.createBinary(ts.RELOP, op, pn, shiftExpr(ts)); + } + return pn; + } + + private Object shiftExpr(TokenStream ts) throws IOException, + JavaScriptException { + Object pn = addExpr(ts); + while (ts.matchToken(ts.SHOP)) { + sourceAdd((char) ts.SHOP); + sourceAdd((char) ts.getOp()); + pn = nf.createBinary(ts.SHOP, ts.getOp(), pn, addExpr(ts)); + } + return pn; + } + + private Object addExpr(TokenStream ts) throws IOException, + JavaScriptException { + int tt; + Object pn = mulExpr(ts); + + while ((tt = ts.getToken()) == ts.ADD || tt == ts.SUB) { + sourceAdd((char) tt); + // flushNewLines + pn = nf.createBinary(tt, pn, mulExpr(ts)); + } + ts.ungetToken(tt); + + return pn; + } + + private Object mulExpr(TokenStream ts) throws IOException, + JavaScriptException { + int tt; + + Object pn = unaryExpr(ts); + + while ((tt = ts.peekToken()) == ts.MUL || tt == ts.DIV || tt == ts.MOD) { + tt = ts.getToken(); + sourceAdd((char) tt); + pn = nf.createBinary(tt, pn, unaryExpr(ts)); + } + + return pn; + } + + private Object unaryExpr(TokenStream ts) throws IOException, + JavaScriptException { + int tt; + + ts.flags |= ts.TSF_REGEXP; + tt = ts.getToken(); + ts.flags &= ~ts.TSF_REGEXP; + + switch (tt) { + case TokenStream.UNARYOP: + sourceAdd((char) ts.UNARYOP); + sourceAdd((char) ts.getOp()); + return nf.createUnary(ts.UNARYOP, ts.getOp(), unaryExpr(ts)); + + case TokenStream.ADD: + case TokenStream.SUB: + sourceAdd((char) ts.UNARYOP); + sourceAdd((char) tt); + return nf.createUnary(ts.UNARYOP, tt, unaryExpr(ts)); + + case TokenStream.INC: + case TokenStream.DEC: + sourceAdd((char) tt); + return nf.createUnary(tt, ts.PRE, memberExpr(ts, true)); + + case TokenStream.DELPROP: + sourceAdd((char) ts.DELPROP); + return nf.createUnary(ts.DELPROP, unaryExpr(ts)); + + case TokenStream.ERROR: + break; + + default: + ts.ungetToken(tt); + + int lineno = ts.getLineno(); + + Object pn = memberExpr(ts, true); + + /* + * don't look across a newline boundary for a postfix incop. + * + * the rhino scanner seems to work differently than the js scanner here; + * in js, it works to have the line number check precede the peekToken + * calls. It'd be better if they had similar behavior... + */ + int peeked; + if (((peeked = ts.peekToken()) == ts.INC || peeked == ts.DEC) + && ts.getLineno() == lineno) { + int pf = ts.getToken(); + sourceAdd((char) pf); + return nf.createUnary(pf, ts.POST, pn); + } + return pn; + } + return nf.createName("err"); // Only reached on error. Try to continue. + + } + + private Object argumentList(TokenStream ts, Object listNode) + throws IOException, JavaScriptException { + boolean matched; + ts.flags |= ts.TSF_REGEXP; + matched = ts.matchToken(ts.GWT); + ts.flags &= ~ts.TSF_REGEXP; + if (!matched) { + boolean first = true; + do { + if (!first) + sourceAdd((char) ts.COMMA); + first = false; + nf.addChildToBack(listNode, assignExpr(ts, false)); + } while (ts.matchToken(ts.COMMA)); + + mustMatchToken(ts, ts.GWT, "msg.no.paren.arg"); + } + sourceAdd((char) ts.GWT); + return listNode; + } + + private Object memberExpr(TokenStream ts, boolean allowCallSyntax) + throws IOException, JavaScriptException { + int tt; + + Object pn; + + /* Check for new expressions. */ + ts.flags |= ts.TSF_REGEXP; + tt = ts.peekToken(); + ts.flags &= ~ts.TSF_REGEXP; + if (tt == ts.NEW) { + /* Eat the NEW token. */ + ts.getToken(); + sourceAdd((char) ts.NEW); + + /* Make a NEW node to append to. */ + pn = nf.createLeaf(ts.NEW); + nf.addChildToBack(pn, memberExpr(ts, false)); + + if (ts.matchToken(ts.LP)) { + sourceAdd((char) ts.LP); + /* Add the arguments to pn, if any are supplied. */ + pn = argumentList(ts, pn); + } + + /* + * XXX there's a check in the C source against "too many constructor + * arguments" - how many do we claim to support? + */ + + /* + * Experimental syntax: allow an object literal to follow a new + * expression, which will mean a kind of anonymous class built with the + * JavaAdapter. the object literal will be passed as an additional + * argument to the constructor. + */ + tt = ts.peekToken(); + if (tt == ts.LC) { + nf.addChildToBack(pn, primaryExpr(ts)); + } + } else { + pn = primaryExpr(ts); + } + + return memberExprTail(ts, allowCallSyntax, pn); + } + + private Object memberExprTail(TokenStream ts, boolean allowCallSyntax, + Object pn) throws IOException, JavaScriptException { + lastExprEndLine = ts.getLineno(); + int tt; + while ((tt = ts.getToken()) > ts.EOF) { + if (tt == ts.DOT) { + sourceAdd((char) ts.DOT); + mustMatchToken(ts, ts.NAME, "msg.no.name.after.dot"); + String s = ts.getString(); + sourceAddString(ts.NAME, s); + pn = nf.createBinary(ts.DOT, pn, nf.createName(ts.getString())); + /* + * pn = nf.createBinary(ts.DOT, pn, memberExpr(ts)) is the version in + * Brendan's IR C version. Not in ECMA... does it reflect the 'new' + * operator syntax he mentioned? + */ + lastExprEndLine = ts.getLineno(); + } else if (tt == ts.LB) { + sourceAdd((char) ts.LB); + pn = nf.createBinary(ts.LB, pn, expr(ts, false)); + + mustMatchToken(ts, ts.RB, "msg.no.bracket.index"); + sourceAdd((char) ts.RB); + lastExprEndLine = ts.getLineno(); + } else if (allowCallSyntax && tt == ts.LP) { + /* make a call node */ + + pn = nf.createUnary(ts.CALL, pn); + sourceAdd((char) ts.LP); + + /* Add the arguments to pn, if any are supplied. */ + pn = argumentList(ts, pn); + lastExprEndLine = ts.getLineno(); + } else { + ts.ungetToken(tt); + + break; + } + } + return pn; + } + + private Object primaryExpr(TokenStream ts) throws IOException, + JavaScriptException { + int tt; + + Object pn; + + ts.flags |= ts.TSF_REGEXP; + tt = ts.getToken(); + ts.flags &= ~ts.TSF_REGEXP; + + switch (tt) { + + case TokenStream.FUNCTION: + return function(ts, true); + + case TokenStream.LB: { + sourceAdd((char) ts.LB); + pn = nf.createLeaf(ts.ARRAYLIT); + + ts.flags |= ts.TSF_REGEXP; + boolean matched = ts.matchToken(ts.RB); + ts.flags &= ~ts.TSF_REGEXP; + + if (!matched) { + boolean first = true; + do { + ts.flags |= ts.TSF_REGEXP; + tt = ts.peekToken(); + ts.flags &= ~ts.TSF_REGEXP; + + if (!first) + sourceAdd((char) ts.COMMA); + else + first = false; + + if (tt == ts.RB) { // to fix [,,,].length behavior... + break; + } + + if (tt == ts.COMMA) { + nf.addChildToBack(pn, nf.createLeaf(ts.PRIMARY, ts.UNDEFINED)); + } else { + nf.addChildToBack(pn, assignExpr(ts, false)); + } + + } while (ts.matchToken(ts.COMMA)); + mustMatchToken(ts, ts.RB, "msg.no.bracket.arg"); + } + sourceAdd((char) ts.RB); + return nf.createArrayLiteral(pn); + } + + case TokenStream.LC: { + pn = nf.createLeaf(ts.OBJLIT); + + sourceAdd((char) ts.LC); + if (!ts.matchToken(ts.RC)) { + + boolean first = true; + commaloop : do { + Object property; + + if (!first) + sourceAdd((char) ts.COMMA); + else + first = false; + + tt = ts.getToken(); + switch (tt) { + // map NAMEs to STRINGs in object literal context. + case TokenStream.NAME: + case TokenStream.STRING: + String s = ts.getString(); + sourceAddString(ts.NAME, s); + property = nf.createString(ts.getString()); + break; + case TokenStream.NUMBER: + double n = ts.getNumber(); + sourceAddNumber(n); + property = nf.createNumber(n); + break; + case TokenStream.RC: + // trailing comma is OK. + ts.ungetToken(tt); + break commaloop; + default: + reportError(ts, "msg.bad.prop"); + break commaloop; + } + mustMatchToken(ts, ts.COLON, "msg.no.colon.prop"); + + // OBJLIT is used as ':' in object literal for + // decompilation to solve spacing ambiguity. + sourceAdd((char) ts.OBJLIT); + nf.addChildToBack(pn, property); + nf.addChildToBack(pn, assignExpr(ts, false)); + + } while (ts.matchToken(ts.COMMA)); + + mustMatchToken(ts, ts.RC, "msg.no.brace.prop"); + } + sourceAdd((char) ts.RC); + return nf.createObjectLiteral(pn); + } + + case TokenStream.LP: + + /* + * Brendan's IR-jsparse.c makes a new node tagged with TOK_LP here... + * I'm not sure I understand why. Isn't the grouping already implicit in + * the structure of the parse tree? also TOK_LP is already overloaded (I + * think) in the C IR as 'function call.' + */ + sourceAdd((char) ts.LP); + pn = expr(ts, false); + sourceAdd((char) ts.GWT); + mustMatchToken(ts, ts.GWT, "msg.no.paren"); + return pn; + + case TokenStream.NAME: + String name = ts.getString(); + sourceAddString(ts.NAME, name); + return nf.createName(name); + + case TokenStream.NUMBER: + double n = ts.getNumber(); + sourceAddNumber(n); + return nf.createNumber(n); + + case TokenStream.STRING: + String s = ts.getString(); + sourceAddString(ts.STRING, s); + return nf.createString(s); + + case TokenStream.REGEXP: { + String flags = ts.regExpFlags; + ts.regExpFlags = null; + String re = ts.getString(); + sourceAddString(ts.REGEXP, '/' + re + '/' + flags); + return nf.createRegExp(re, flags); + } + + case TokenStream.PRIMARY: + sourceAdd((char) ts.PRIMARY); + sourceAdd((char) ts.getOp()); + return nf.createLeaf(ts.PRIMARY, ts.getOp()); + + case TokenStream.RESERVED: + reportError(ts, "msg.reserved.id"); + break; + + case TokenStream.ERROR: + /* the scanner or one of its subroutines reported the error. */ + break; + + default: + reportError(ts, "msg.syntax"); + break; + + } + return null; // should never reach here + } + + /** + * The following methods save decompilation information about the source. + * Source information is returned from the parser as a String associated with + * function nodes and with the toplevel script. When saved in the constant + * pool of a class, this string will be UTF-8 encoded, and token values will + * occupy a single byte. + * + * Source is saved (mostly) as token numbers. The tokens saved pretty much + * correspond to the token stream of a 'canonical' representation of the input + * program, as directed by the parser. (There were a few cases where tokens + * could have been left out where decompiler could easily reconstruct them, + * but I left them in for clarity). (I also looked adding source collection to + * TokenStream instead, where I could have limited the changes to a few lines + * in getToken... but this wouldn't have saved any space in the resulting + * source representation, and would have meant that I'd have to duplicate + * parser logic in the decompiler to disambiguate situations where newlines + * are important.) NativeFunction.decompile expands the tokens back into their + * string representations, using simple lookahead to correct spacing and + * indentation. + * + * Token types with associated ops (ASSIGN, SHOP, PRIMARY, etc.) are saved as + * two-token pairs. Number tokens are stored inline, as a NUMBER token, a + * character representing the type, and either 1 or 4 characters representing + * the bit-encoding of the number. String types NAME, STRING and OBJECT are + * currently stored as a token type, followed by a character giving the length + * of the string (assumed to be less than 2^16), followed by the characters of + * the string inlined into the source string. Changing this to some reference + * to to the string in the compiled class' constant pool would probably save a + * lot of space... but would require some method of deriving the final + * constant pool entry from information available at parse time. + * + * Nested functions need a similar mechanism... fortunately the nested + * functions for a given function are generated in source order. Nested + * functions are encoded as FUNCTION followed by a function number (encoded as + * a character), which is enough information to find the proper generated + * NativeFunction instance. + * + */ + private void sourceAdd(char c) { + if (sourceTop == sourceBuffer.length) { + increaseSourceCapacity(sourceTop + 1); + } + sourceBuffer[sourceTop] = c; + ++sourceTop; + } + + private void sourceAddString(int type, String str) { + int L = str.length(); + // java string length < 2^16? + if (Context.check && L > Character.MAX_VALUE) + Context.codeBug(); + + if (sourceTop + L + 2 > sourceBuffer.length) { + increaseSourceCapacity(sourceTop + L + 2); + } + sourceAdd((char) type); + sourceAdd((char) L); + str.getChars(0, L, sourceBuffer, sourceTop); + sourceTop += L; + } + + private void sourceAddNumber(double n) { + sourceAdd((char) TokenStream.NUMBER); + + /* + * encode the number in the source stream. Save as NUMBER type (char | char + * char char char) where type is 'D' - double, 'S' - short, 'J' - long. + * + * We need to retain float vs. integer type info to keep the behavior of + * liveconnect type-guessing the same after decompilation. (Liveconnect + * tries to present 1.0 to Java as a float/double) OPT: This is no longer + * true. We could compress the format. + * + * This may not be the most space-efficient encoding; the chars created + * below may take up to 3 bytes in constant pool UTF-8 encoding, so a Double + * could take up to 12 bytes. + */ + + long lbits = (long) n; + if (lbits != n) { + // if it's floating point, save as a Double bit pattern. + // (12/15/97 our scanner only returns Double for f.p.) + lbits = Double.doubleToLongBits(n); + sourceAdd('D'); + sourceAdd((char) (lbits >> 48)); + sourceAdd((char) (lbits >> 32)); + sourceAdd((char) (lbits >> 16)); + sourceAdd((char) lbits); + } else { + // we can ignore negative values, bc they're already prefixed + // by UNARYOP SUB + if (Context.check && lbits < 0) + Context.codeBug(); + + // will it fit in a char? + // this gives a short encoding for integer values up to 2^16. + if (lbits <= Character.MAX_VALUE) { + sourceAdd('S'); + sourceAdd((char) lbits); + } else { // Integral, but won't fit in a char. Store as a long. + sourceAdd('J'); + sourceAdd((char) (lbits >> 48)); + sourceAdd((char) (lbits >> 32)); + sourceAdd((char) (lbits >> 16)); + sourceAdd((char) lbits); + } + } + } + + private void increaseSourceCapacity(int minimalCapacity) { + // Call this only when capacity increase is must + if (Context.check && minimalCapacity <= sourceBuffer.length) + Context.codeBug(); + int newCapacity = sourceBuffer.length * 2; + if (newCapacity < minimalCapacity) { + newCapacity = minimalCapacity; + } + char[] tmp = new char[newCapacity]; + System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop); + sourceBuffer = tmp; + } + + private String sourceToString(int offset) { + if (Context.check && (offset < 0 || sourceTop < offset)) + Context.codeBug(); + return new String(sourceBuffer, offset, sourceTop - offset); + } + + private int lastExprEndLine; // Hack to handle function expr termination. + private IRFactory nf; + private ErrorReporter er; + private boolean ok; // Did the parse encounter an error? + + private char[] sourceBuffer = new char[128]; + private int sourceTop; + private int functionNumber; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/ScriptRuntime.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/ScriptRuntime.java new file mode 100644 index 00000000000..d9599e3d815 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/ScriptRuntime.java @@ -0,0 +1,240 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Patrick Beard + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * Frank Mitchell + * Andrew Wason + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +/** + * This is the class that implements the runtime. + * + * @author Norris Boyd + */ + +public class ScriptRuntime { + + public static double NaN = 0.0d / 0.0; + + public static String numberToString(double d, int base) { + if (Double.isNaN(d)) + return "NaN"; + if (d == Double.POSITIVE_INFINITY) + return "Infinity"; + if (d == Double.NEGATIVE_INFINITY) + return "-Infinity"; + if (d == 0.0) + return "0"; + + if ((base < 2) || (base > 36)) { + throw new Error(Context.getMessage1("msg.bad.radix", + Integer.toString(base))); + } + + if (base != 10) { + return DToA.JS_dtobasestr(base, d); + } else { + StringBuffer result = new StringBuffer(); + DToA.JS_dtostr(result, DToA.DTOSTR_STANDARD, 0, d); + return result.toString(); + } + + } + + /* + * Helper function for toNumber, parseInt, and TokenStream.getToken. + */ + static double stringToNumber(String s, int start, int radix) { + char digitMax = '9'; + char lowerCaseBound = 'a'; + char upperCaseBound = 'A'; + int len = s.length(); + if (radix < 10) { + digitMax = (char) ('0' + radix - 1); + } + if (radix > 10) { + lowerCaseBound = (char) ('a' + radix - 10); + upperCaseBound = (char) ('A' + radix - 10); + } + int end; + double sum = 0.0; + for (end=start; end < len; end++) { + char c = s.charAt(end); + int newDigit; + if ('0' <= c && c <= digitMax) + newDigit = c - '0'; + else if ('a' <= c && c < lowerCaseBound) + newDigit = c - 'a' + 10; + else if ('A' <= c && c < upperCaseBound) + newDigit = c - 'A' + 10; + else + break; + sum = sum*radix + newDigit; + } + if (start == end) { + return NaN; + } + if (sum >= 9007199254740992.0) { + if (radix == 10) { + /* If we're accumulating a decimal number and the number + * is >= 2^53, then the result from the repeated multiply-add + * above may be inaccurate. Call Java to get the correct + * answer. + */ + try { + return Double.valueOf(s.substring(start, end)).doubleValue(); + } catch (NumberFormatException nfe) { + return NaN; + } + } else if (radix == 2 || radix == 4 || radix == 8 || + radix == 16 || radix == 32) + { + /* The number may also be inaccurate for one of these bases. + * This happens if the addition in value*radix + digit causes + * a round-down to an even least significant mantissa bit + * when the first dropped bit is a one. If any of the + * following digits in the number (which haven't been added + * in yet) are nonzero then the correct action would have + * been to round up instead of down. An example of this + * occurs when reading the number 0x1000000000000081, which + * rounds to 0x1000000000000000 instead of 0x1000000000000100. + */ + BinaryDigitReader bdr = new BinaryDigitReader(radix, s, start, end); + int bit; + sum = 0.0; + + /* Skip leading zeros. */ + do { + bit = bdr.getNextBinaryDigit(); + } while (bit == 0); + + if (bit == 1) { + /* Gather the 53 significant bits (including the leading 1) */ + sum = 1.0; + for (int j = 52; j != 0; j--) { + bit = bdr.getNextBinaryDigit(); + if (bit < 0) + return sum; + sum = sum*2 + bit; + } + /* bit54 is the 54th bit (the first dropped from the mantissa) */ + int bit54 = bdr.getNextBinaryDigit(); + if (bit54 >= 0) { + double factor = 2.0; + int sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */ + int bit3; + + while ((bit3 = bdr.getNextBinaryDigit()) >= 0) { + sticky |= bit3; + factor *= 2; + } + sum += bit54 & (bit | sticky); + sum *= factor; + } + } + } + /* We don't worry about inaccurate numbers for any other base. */ + } + return sum; + } + + /** + * For escaping strings printed by object and array literals; not quite + * the same as 'escape.' + */ + public static String escapeString(String s) { + + StringBuffer sb = null; + + for(int i = 0, L = s.length(); i != L; ++i) { + int c = s.charAt(i); + + if (' ' <= c && c <= '~' && c != '"' && c != '\\') { + // an ordinary print character (like C isprint()) and not " + // or \ . Note single quote ' is not escaped + if (sb != null) { + sb.append((char)c); + } + continue; + } + if (sb == null) { + sb = new StringBuffer(L + 3); + sb.append(s); + sb.setLength(i); + } + + int escape = -1; + switch (c) { + case '\b': escape = 'b'; break; + case '\f': escape = 'f'; break; + case '\n': escape = 'n'; break; + case '\r': escape = 'r'; break; + case '\t': escape = 't'; break; + case 0xb: escape = 'v'; break; // Java lacks \v. + case '"': escape = '"'; break; + case ' ': escape = ' '; break; + case '\\': escape = '\\'; break; + } + if (escape >= 0) { + // an \escaped sort of character + sb.append('\\'); + sb.append((char)escape); + } else { + int hexSize; + if (c < 256) { + // 2-digit hex + sb.append("\\x"); + hexSize = 2; + } else { + // Unicode. + sb.append("\\u"); + hexSize = 4; + } + // append hexadecimal form of c left-padded with 0 + for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) { + int digit = 0xf & (c >> shift); + int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit; + sb.append((char)hc); + } + } + } + + return (sb == null) ? s : sb.toString(); + } + +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java new file mode 100644 index 00000000000..a677b31aa35 --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/TokenStream.java @@ -0,0 +1,1640 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.*; + +/** + * This class implements the JavaScript scanner. + * + * It is based on the C source files jsscan.c and jsscan.h + * in the jsref package. + * + * @see org.mozilla.javascript.Parser + * + * @author Mike McCabe + * @author Brendan Eich + */ + +public class TokenStream { + + static final boolean RESERVED_KEYWORD_AS_IDENTIFIER = false; + + /* + * JSTokenStream flags, mirroring those in jsscan.h. These are used + * by the parser to change/check the state of the scanner. + */ + + final static int + TSF_NEWLINES = 1 << 0, // tokenize newlines + TSF_FUNCTION = 1 << 1, // scanning inside function body + TSF_RETURN_EXPR = 1 << 2, // function has 'return expr;' + TSF_RETURN_VOID = 1 << 3, // function has 'return;' + TSF_REGEXP = 1 << 4, // looking for a regular expression + TSF_DIRTYLINE = 1 << 5; // stuff other than whitespace since + // start of line + + /* + * For chars - because we need something out-of-range + * to check. (And checking EOF by exception is annoying.) + * Note distinction from EOF token type! + */ + private final static int + EOF_CHAR = -1; + + /** + * Token types. These values correspond to JSTokenType values in + * jsscan.c. + */ + + public final static int + // start enum + ERROR = -1, // well-known as the only code < EOF + EOF = 0, // end of file token - (not EOF_CHAR) + EOL = 1, // end of line + // Beginning here are interpreter bytecodes. Their values + // must not exceed 127. + POPV = 2, + ENTERWITH = 3, + LEAVEWITH = 4, + RETURN = 5, + GOTO = 6, + IFEQ = 7, + IFNE = 8, + DUP = 9, + SETNAME = 10, + BITOR = 11, + BITXOR = 12, + BITAND = 13, + EQ = 14, + NE = 15, + LT = 16, + LE = 17, + GT = 18, + GE = 19, + LSH = 20, + RSH = 21, + URSH = 22, + ADD = 23, + SUB = 24, + MUL = 25, + DIV = 26, + MOD = 27, + BITNOT = 28, + NEG = 29, + NEW = 30, + DELPROP = 31, + TYPEOF = 32, + NAMEINC = 33, + PROPINC = 34, + ELEMINC = 35, + NAMEDEC = 36, + PROPDEC = 37, + ELEMDEC = 38, + GETPROP = 39, + SETPROP = 40, + GETELEM = 41, + SETELEM = 42, + CALL = 43, + NAME = 44, + NUMBER = 45, + STRING = 46, + ZERO = 47, + ONE = 48, + NULL = 49, + THIS = 50, + FALSE = 51, + TRUE = 52, + SHEQ = 53, // shallow equality (===) + SHNE = 54, // shallow inequality (!==) + CLOSURE = 55, + REGEXP = 56, + POP = 57, + POS = 58, + VARINC = 59, + VARDEC = 60, + BINDNAME = 61, + THROW = 62, + IN = 63, + INSTANCEOF = 64, + GOSUB = 65, + RETSUB = 66, + CALLSPECIAL = 67, + GETTHIS = 68, + NEWTEMP = 69, + USETEMP = 70, + GETBASE = 71, + GETVAR = 72, + SETVAR = 73, + UNDEFINED = 74, + TRY = 75, + ENDTRY = 76, + NEWSCOPE = 77, + TYPEOFNAME = 78, + ENUMINIT = 79, + ENUMNEXT = 80, + GETPROTO = 81, + GETPARENT = 82, + SETPROTO = 83, + SETPARENT = 84, + SCOPE = 85, + GETSCOPEPARENT = 86, + THISFN = 87, + JTHROW = 88, + // End of interpreter bytecodes + SEMI = 89, // semicolon + LB = 90, // left and right brackets + RB = 91, + LC = 92, // left and right curlies (braces) + RC = 93, + LP = 94, // left and right parentheses + GWT = 95, + COMMA = 96, // comma operator + ASSIGN = 97, // assignment ops (= += -= etc.) + HOOK = 98, // conditional (?:) + COLON = 99, + OR = 100, // logical or (||) + AND = 101, // logical and (&&) + EQOP = 102, // equality ops (== !=) + RELOP = 103, // relational ops (< <= > >=) + SHOP = 104, // shift ops (<< >> >>>) + UNARYOP = 105, // unary prefix operator + INC = 106, // increment/decrement (++ --) + DEC = 107, + DOT = 108, // member operator (.) + PRIMARY = 109, // true, false, null, this + FUNCTION = 110, // function keyword + EXPORT = 111, // export keyword + IMPORT = 112, // import keyword + IF = 113, // if keyword + ELSE = 114, // else keyword + SWITCH = 115, // switch keyword + CASE = 116, // case keyword + DEFAULT = 117, // default keyword + WHILE = 118, // while keyword + DO = 119, // do keyword + FOR = 120, // for keyword + BREAK = 121, // break keyword + CONTINUE = 122, // continue keyword + VAR = 123, // var keyword + WITH = 124, // with keyword + CATCH = 125, // catch keyword + FINALLY = 126, // finally keyword + RESERVED = 127, // reserved keywords + + /** Added by Mike - these are JSOPs in the jsref, but I + * don't have them yet in the java implementation... + * so they go here. Also whatever I needed. + + * Most of these go in the 'op' field when returning + * more general token types, eg. 'DIV' as the op of 'ASSIGN'. + */ + NOP = 128, // NOP + NOT = 129, // etc. + PRE = 130, // for INC, DEC nodes. + POST = 131, + + /** + * For JSOPs associated with keywords... + * eg. op = THIS; token = PRIMARY + */ + + VOID = 132, + + /* types used for the parse tree - these never get returned + * by the scanner. + */ + BLOCK = 133, // statement block + ARRAYLIT = 134, // array literal + OBJLIT = 135, // object literal + LABEL = 136, // label + TARGET = 137, + LOOP = 138, + ENUMDONE = 139, + EXPRSTMT = 140, + PARENT = 141, + CONVERT = 142, + JSR = 143, + NEWLOCAL = 144, + USELOCAL = 145, + DEBUGGER = 146, + SCRIPT = 147, // top-level node for entire script + + LAST_TOKEN = 147, + + // This value is only used as a return value for getTokenHelper, + // which is only called from getToken and exists to avoid an excessive + // recursion problem if a number of lines in a row are comments. + RETRY_TOKEN = 65535; + + // end enum + + + public static String tokenToName(int token) { + if (Context.printTrees || Context.printICode) { + switch (token) { + case ERROR: return "error"; + case EOF: return "eof"; + case EOL: return "eol"; + case POPV: return "popv"; + case ENTERWITH: return "enterwith"; + case LEAVEWITH: return "leavewith"; + case RETURN: return "return"; + case GOTO: return "goto"; + case IFEQ: return "ifeq"; + case IFNE: return "ifne"; + case DUP: return "dup"; + case SETNAME: return "setname"; + case BITOR: return "bitor"; + case BITXOR: return "bitxor"; + case BITAND: return "bitand"; + case EQ: return "eq"; + case NE: return "ne"; + case LT: return "lt"; + case LE: return "le"; + case GT: return "gt"; + case GE: return "ge"; + case LSH: return "lsh"; + case RSH: return "rsh"; + case URSH: return "ursh"; + case ADD: return "add"; + case SUB: return "sub"; + case MUL: return "mul"; + case DIV: return "div"; + case MOD: return "mod"; + case BITNOT: return "bitnot"; + case NEG: return "neg"; + case NEW: return "new"; + case DELPROP: return "delprop"; + case TYPEOF: return "typeof"; + case NAMEINC: return "nameinc"; + case PROPINC: return "propinc"; + case ELEMINC: return "eleminc"; + case NAMEDEC: return "namedec"; + case PROPDEC: return "propdec"; + case ELEMDEC: return "elemdec"; + case GETPROP: return "getprop"; + case SETPROP: return "setprop"; + case GETELEM: return "getelem"; + case SETELEM: return "setelem"; + case CALL: return "call"; + case NAME: return "name"; + case NUMBER: return "number"; + case STRING: return "string"; + case ZERO: return "zero"; + case ONE: return "one"; + case NULL: return "null"; + case THIS: return "this"; + case FALSE: return "false"; + case TRUE: return "true"; + case SHEQ: return "sheq"; + case SHNE: return "shne"; + case CLOSURE: return "closure"; + case REGEXP: return "object"; + case POP: return "pop"; + case POS: return "pos"; + case VARINC: return "varinc"; + case VARDEC: return "vardec"; + case BINDNAME: return "bindname"; + case THROW: return "throw"; + case IN: return "in"; + case INSTANCEOF: return "instanceof"; + case GOSUB: return "gosub"; + case RETSUB: return "retsub"; + case CALLSPECIAL: return "callspecial"; + case GETTHIS: return "getthis"; + case NEWTEMP: return "newtemp"; + case USETEMP: return "usetemp"; + case GETBASE: return "getbase"; + case GETVAR: return "getvar"; + case SETVAR: return "setvar"; + case UNDEFINED: return "undefined"; + case TRY: return "try"; + case ENDTRY: return "endtry"; + case NEWSCOPE: return "newscope"; + case TYPEOFNAME: return "typeofname"; + case ENUMINIT: return "enuminit"; + case ENUMNEXT: return "enumnext"; + case GETPROTO: return "getproto"; + case GETPARENT: return "getparent"; + case SETPROTO: return "setproto"; + case SETPARENT: return "setparent"; + case SCOPE: return "scope"; + case GETSCOPEPARENT: return "getscopeparent"; + case THISFN: return "thisfn"; + case JTHROW: return "jthrow"; + case SEMI: return "semi"; + case LB: return "lb"; + case RB: return "rb"; + case LC: return "lc"; + case RC: return "rc"; + case LP: return "lp"; + case GWT: return "gwt"; + case COMMA: return "comma"; + case ASSIGN: return "assign"; + case HOOK: return "hook"; + case COLON: return "colon"; + case OR: return "or"; + case AND: return "and"; + case EQOP: return "eqop"; + case RELOP: return "relop"; + case SHOP: return "shop"; + case UNARYOP: return "unaryop"; + case INC: return "inc"; + case DEC: return "dec"; + case DOT: return "dot"; + case PRIMARY: return "primary"; + case FUNCTION: return "function"; + case EXPORT: return "export"; + case IMPORT: return "import"; + case IF: return "if"; + case ELSE: return "else"; + case SWITCH: return "switch"; + case CASE: return "case"; + case DEFAULT: return "default"; + case WHILE: return "while"; + case DO: return "do"; + case FOR: return "for"; + case BREAK: return "break"; + case CONTINUE: return "continue"; + case VAR: return "var"; + case WITH: return "with"; + case CATCH: return "catch"; + case FINALLY: return "finally"; + case RESERVED: return "reserved"; + case NOP: return "nop"; + case NOT: return "not"; + case PRE: return "pre"; + case POST: return "post"; + case VOID: return "void"; + case BLOCK: return "block"; + case ARRAYLIT: return "arraylit"; + case OBJLIT: return "objlit"; + case LABEL: return "label"; + case TARGET: return "target"; + case LOOP: return "loop"; + case ENUMDONE: return "enumdone"; + case EXPRSTMT: return "exprstmt"; + case PARENT: return "parent"; + case CONVERT: return "convert"; + case JSR: return "jsr"; + case NEWLOCAL: return "newlocal"; + case USELOCAL: return "uselocal"; + case SCRIPT: return "script"; + } + return ""; + } + return ""; + } + + /* This function uses the cached op, string and number fields in + * TokenStream; if getToken has been called since the passed token + * was scanned, the op or string printed may be incorrect. + */ + public String tokenToString(int token) { + if (Context.printTrees) { + String name = tokenToName(token); + + switch (token) { + case UNARYOP: + case ASSIGN: + case PRIMARY: + case EQOP: + case SHOP: + case RELOP: + return name + " " + tokenToName(this.op); + + case STRING: + case REGEXP: + case NAME: + return name + " `" + this.string + "'"; + + case NUMBER: + return "NUMBER " + this.number; + } + + return name; + } + return ""; + } + + private static int getKeywordId(String name) { +// #string_id_map# +// The following assumes that EOF == 0 + final int + Id_break = BREAK, + Id_case = CASE, + Id_continue = CONTINUE, + Id_default = DEFAULT, + Id_delete = DELPROP, + Id_do = DO, + Id_else = ELSE, + Id_export = EXPORT, + Id_false = PRIMARY | (FALSE << 8), + Id_for = FOR, + Id_function = FUNCTION, + Id_if = IF, + Id_in = RELOP | (IN << 8), + Id_new = NEW, + Id_null = PRIMARY | (NULL << 8), + Id_return = RETURN, + Id_switch = SWITCH, + Id_this = PRIMARY | (THIS << 8), + Id_true = PRIMARY | (TRUE << 8), + Id_typeof = UNARYOP | (TYPEOF << 8), + Id_var = VAR, + Id_void = UNARYOP | (VOID << 8), + Id_while = WHILE, + Id_with = WITH, + + // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c + Id_abstract = RESERVED, + Id_boolean = RESERVED, + Id_byte = RESERVED, + Id_catch = CATCH, + Id_char = RESERVED, + Id_class = RESERVED, + Id_const = RESERVED, + Id_debugger = DEBUGGER, + Id_double = RESERVED, + Id_enum = RESERVED, + Id_extends = RESERVED, + Id_final = RESERVED, + Id_finally = FINALLY, + Id_float = RESERVED, + Id_goto = RESERVED, + Id_implements = RESERVED, + Id_import = IMPORT, + Id_instanceof = RELOP | (INSTANCEOF << 8), + Id_int = RESERVED, + Id_interface = RESERVED, + Id_long = RESERVED, + Id_native = RESERVED, + Id_package = RESERVED, + Id_private = RESERVED, + Id_protected = RESERVED, + Id_public = RESERVED, + Id_short = RESERVED, + Id_static = RESERVED, + Id_super = RESERVED, + Id_synchronized = RESERVED, + Id_throw = THROW, + Id_throws = RESERVED, + Id_transient = RESERVED, + Id_try = TRY, + Id_volatile = RESERVED; + + int id; + String s = name; +// #generated# Last update: 2001-06-01 17:45:01 CEST + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 2: c=s.charAt(1); + if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} } + else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} } + else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} } + break L; + case 3: switch (s.charAt(0)) { + case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L; + case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L; + case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') {id=Id_new; break L0;} break L; + case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L; + case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L; + } break L; + case 4: switch (s.charAt(0)) { + case 'b': X="byte";id=Id_byte; break L; + case 'c': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') {id=Id_case; break L0;} } + else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') {id=Id_char; break L0;} } + break L; + case 'e': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') {id=Id_else; break L0;} } + else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') {id=Id_enum; break L0;} } + break L; + case 'g': X="goto";id=Id_goto; break L; + case 'l': X="long";id=Id_long; break L; + case 'n': X="null";id=Id_null; break L; + case 't': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') {id=Id_true; break L0;} } + else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') {id=Id_this; break L0;} } + break L; + case 'v': X="void";id=Id_void; break L; + case 'w': X="with";id=Id_with; break L; + } break L; + case 5: switch (s.charAt(2)) { + case 'a': X="class";id=Id_class; break L; + case 'e': X="break";id=Id_break; break L; + case 'i': X="while";id=Id_while; break L; + case 'l': X="false";id=Id_false; break L; + case 'n': c=s.charAt(0); + if (c=='c') { X="const";id=Id_const; } + else if (c=='f') { X="final";id=Id_final; } + break L; + case 'o': c=s.charAt(0); + if (c=='f') { X="float";id=Id_float; } + else if (c=='s') { X="short";id=Id_short; } + break L; + case 'p': X="super";id=Id_super; break L; + case 'r': X="throw";id=Id_throw; break L; + case 't': X="catch";id=Id_catch; break L; + } break L; + case 6: switch (s.charAt(1)) { + case 'a': X="native";id=Id_native; break L; + case 'e': c=s.charAt(0); + if (c=='d') { X="delete";id=Id_delete; } + else if (c=='r') { X="return";id=Id_return; } + break L; + case 'h': X="throws";id=Id_throws; break L; + case 'm': X="import";id=Id_import; break L; + case 'o': X="double";id=Id_double; break L; + case 't': X="static";id=Id_static; break L; + case 'u': X="public";id=Id_public; break L; + case 'w': X="switch";id=Id_switch; break L; + case 'x': X="export";id=Id_export; break L; + case 'y': X="typeof";id=Id_typeof; break L; + } break L; + case 7: switch (s.charAt(1)) { + case 'a': X="package";id=Id_package; break L; + case 'e': X="default";id=Id_default; break L; + case 'i': X="finally";id=Id_finally; break L; + case 'o': X="boolean";id=Id_boolean; break L; + case 'r': X="private";id=Id_private; break L; + case 'x': X="extends";id=Id_extends; break L; + } break L; + case 8: switch (s.charAt(0)) { + case 'a': X="abstract";id=Id_abstract; break L; + case 'c': X="continue";id=Id_continue; break L; + case 'd': X="debugger";id=Id_debugger; break L; + case 'f': X="function";id=Id_function; break L; + case 'v': X="volatile";id=Id_volatile; break L; + } break L; + case 9: c=s.charAt(0); + if (c=='i') { X="interface";id=Id_interface; } + else if (c=='p') { X="protected";id=Id_protected; } + else if (c=='t') { X="transient";id=Id_transient; } + break L; + case 10: c=s.charAt(1); + if (c=='m') { X="implements";id=Id_implements; } + else if (c=='n') { X="instanceof";id=Id_instanceof; } + break L; + case 12: X="synchronized";id=Id_synchronized; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# +// #/string_id_map# + + return id; + } + + private int stringToKeyword(String name) { + int id = getKeywordId(name); + if (id == 0) { return EOF; } + this.op = id >> 8; + return id & 0xff; + } + + public TokenStream(Reader in, + String sourceName, int lineno) + { + this.in = new LineBuffer(in, lineno); + this.pushbackToken = EOF; + this.sourceName = sourceName; + flags = 0; + } + + /* return and pop the token from the stream if it matches... + * otherwise return null + */ + public boolean matchToken(int toMatch) throws IOException { + int token = getToken(); + if (token == toMatch) + return true; + + // didn't match, push back token + tokenno--; + this.pushbackToken = token; + return false; + } + + public void clearPushback() { + this.pushbackToken = EOF; + } + + public void ungetToken(int tt) { + if (this.pushbackToken != EOF && tt != ERROR) { + String message = Context.getMessage2("msg.token.replaces.pushback", + tokenToString(tt), tokenToString(this.pushbackToken)); + throw new RuntimeException(message); + } + this.pushbackToken = tt; + tokenno--; + } + + public int peekToken() throws IOException { + int result = getToken(); + + this.pushbackToken = result; + tokenno--; + return result; + } + + public int peekTokenSameLine() throws IOException { + int result; + + flags |= TSF_NEWLINES; // SCAN_NEWLINES from jsscan.h + result = peekToken(); + flags &= ~TSF_NEWLINES; // HIDE_NEWLINES from jsscan.h + if (this.pushbackToken == EOL) + this.pushbackToken = EOF; + return result; + } + + public static boolean isJSKeyword(String s) { + return getKeywordId(s) != 0; + } + + public static boolean isJSIdentifier(String s) { + int length = s.length(); + + if (length == 0 || !Character.isJavaIdentifierStart(s.charAt(0))) + return false; + + for (int i=1; i= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z')); + } + + static boolean isDigit(int c) { + return (c >= '0' && c <= '9'); + } + + static int xDigitToInt(int c) { + if ('0' <= c && c <= '9') { return c - '0'; } + if ('a' <= c && c <= 'f') { return c - ('a' - 10); } + if ('A' <= c && c <= 'F') { return c - ('A' - 10); } + return -1; + } + + /* As defined in ECMA. jsscan.c uses C isspace() (which allows + * \v, I think.) note that code in in.read() implicitly accepts + * '\r' == \u000D as well. + */ + public static boolean isJSSpace(int c) { + return (c == '\u0020' || c == '\u0009' + || c == '\u000C' || c == '\u000B' + || c == '\u00A0' + || Character.getType((char)c) == Character.SPACE_SEPARATOR); + } + + public static boolean isJSLineTerminator(int c) { + return (c == '\n' || c == '\r' + || c == 0x2028 || c == 0x2029); + } + + private void skipLine() throws IOException { + // skip to end of line + int c; + while ((c = in.read()) != EOF_CHAR && c != '\n') { } + in.unread(); + } + + public int getToken() throws IOException { + int c; + do { + c = getTokenHelper(); + } while (c == RETRY_TOKEN); + return c; + } + + private int getTokenHelper() throws IOException { + int c; + tokenno++; + + // Check for pushed-back token + if (this.pushbackToken != EOF) { + int result = this.pushbackToken; + this.pushbackToken = EOF; + return result; + } + + // Eat whitespace, possibly sensitive to newlines. + do { + c = in.read(); + if (c == '\n') { + flags &= ~TSF_DIRTYLINE; + if ((flags & TSF_NEWLINES) != 0) + break; + } + } while (isJSSpace(c) || c == '\n'); + + if (c == EOF_CHAR) + return EOF; + if (c != '-' && c != '\n') + flags |= TSF_DIRTYLINE; + + // identifier/keyword/instanceof? + // watch out for starting with a + boolean identifierStart; + boolean isUnicodeEscapeStart = false; + if (c == '\\') { + c = in.read(); + if (c == 'u') { + identifierStart = true; + isUnicodeEscapeStart = true; + stringBufferTop = 0; + } else { + identifierStart = false; + c = '\\'; + in.unread(); + } + } else { + identifierStart = Character.isJavaIdentifierStart((char)c); + if (identifierStart) { + stringBufferTop = 0; + addToString(c); + } + + // bruce: special handling of JSNI signatures + // - it would be nice to handle Unicode escapes in the future + // + if (c == '@') { + stringBufferTop = 0; + addToString(c); + return jsniMatchReference(); + } + } + + if (identifierStart) { + boolean containsEscape = isUnicodeEscapeStart; + for (;;) { + if (isUnicodeEscapeStart) { + // strictly speaking we should probably push-back + // all the bad characters if the uXXXX + // sequence is malformed. But since there isn't a + // correct context(is there?) for a bad Unicode + // escape sequence in an identifier, we can report + // an error here. + int escapeVal = 0; + for (int i = 0; i != 4; ++i) { + c = in.read(); + escapeVal = (escapeVal << 4) | xDigitToInt(c); + // Next check takes care about c < 0 and bad escape + if (escapeVal < 0) { break; } + } + if (escapeVal < 0) { + reportSyntaxError("msg.invalid.escape", null); + return ERROR; + } + addToString(escapeVal); + isUnicodeEscapeStart = false; + } else { + c = in.read(); + if (c == '\\') { + c = in.read(); + if (c == 'u') { + isUnicodeEscapeStart = true; + containsEscape = true; + } else { + reportSyntaxError("msg.illegal.character", null); + return ERROR; + } + } else { + if (!Character.isJavaIdentifierPart((char)c)) { + break; + } + addToString(c); + } + } + } + in.unread(); + + String str = getStringFromBuffer(); + if (!containsEscape) { + // OPT we shouldn't have to make a string (object!) to + // check if it's a keyword. + + // Return the corresponding token if it's a keyword + int result = stringToKeyword(str); + if (result != EOF) { + if (result != RESERVED) { + return result; + } + else if (!RESERVED_KEYWORD_AS_IDENTIFIER) + { + return result; + } + else { + // If implementation permits to use future reserved + // keywords in violation with the EcmaScript standard, + // treat it as name but issue warning + Object[] errArgs = { str }; + reportSyntaxWarning("msg.reserved.keyword", errArgs); + } + } + } + this.string = str; + return NAME; + } + + // is it a number? + if (isDigit(c) || (c == '.' && isDigit(in.peek()))) { + + stringBufferTop = 0; + int base = 10; + + if (c == '0') { + c = in.read(); + if (c == 'x' || c == 'X') { + base = 16; + c = in.read(); + } else if (isDigit(c)) { + base = 8; + } else { + addToString('0'); + } + } + + if (base == 16) { + while (0 <= xDigitToInt(c)) { + addToString(c); + c = in.read(); + } + } else { + while ('0' <= c && c <= '9') { + /* + * We permit 08 and 09 as decimal numbers, which + * makes our behavior a superset of the ECMA + * numeric grammar. We might not always be so + * permissive, so we warn about it. + */ + if (base == 8 && c >= '8') { + Object[] errArgs = { c == '8' ? "8" : "9" }; + reportSyntaxWarning("msg.bad.octal.literal", errArgs); + base = 10; + } + addToString(c); + c = in.read(); + } + } + + boolean isInteger = true; + + if (base == 10 && (c == '.' || c == 'e' || c == 'E')) { + isInteger = false; + if (c == '.') { + do { + addToString(c); + c = in.read(); + } while (isDigit(c)); + } + if (c == 'e' || c == 'E') { + addToString(c); + c = in.read(); + if (c == '+' || c == '-') { + addToString(c); + c = in.read(); + } + if (!isDigit(c)) { + reportSyntaxError("msg.missing.exponent", null); + return ERROR; + } + do { + addToString(c); + c = in.read(); + } while (isDigit(c)); + } + } + in.unread(); + String numString = getStringFromBuffer(); + + double dval; + if (base == 10 && !isInteger) { + try { + // Use Java conversion to number from string... + dval = (Double.valueOf(numString)).doubleValue(); + } + catch (NumberFormatException ex) { + Object[] errArgs = { ex.getMessage() }; + reportSyntaxError("msg.caught.nfe", errArgs); + return ERROR; + } + } else { + dval = ScriptRuntime.stringToNumber(numString, 0, base); + } + + this.number = dval; + return NUMBER; + } + + // is it a string? + if (c == '"' || c == '\'') { + // We attempt to accumulate a string the fast way, by + // building it directly out of the reader. But if there + // are any escaped characters in the string, we revert to + // building it out of a StringBuffer. + + int quoteChar = c; + int val = 0; + stringBufferTop = 0; + + c = in.read(); + strLoop: while (c != quoteChar) { + if (c == '\n' || c == EOF_CHAR) { + in.unread(); + reportSyntaxError("msg.unterminated.string.lit", null); + return ERROR; + } + + if (c == '\\') { + // We've hit an escaped character + + c = in.read(); + switch (c) { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + + // \v a late addition to the ECMA spec, + // it is not in Java, so use 0xb + case 'v': c = 0xb; break; + + case 'u': { + /* + * Get 4 hex digits; if the u escape is not + * followed by 4 hex digits, use 'u' + the literal + * character sequence that follows. + */ + int escapeStart = stringBufferTop; + addToString('u'); + int escapeVal = 0; + for (int i = 0; i != 4; ++i) { + c = in.read(); + escapeVal = (escapeVal << 4) | xDigitToInt(c); + if (escapeVal < 0) { + continue strLoop; + } + addToString(c); + } + // prepare for replace of stored 'u' sequence + // by escape value + stringBufferTop = escapeStart; + c = escapeVal; + } break; + + case 'x': { + /* Get 2 hex digits, defaulting to 'x' + literal + * sequence, as above. + */ + c = in.read(); + int escapeVal = xDigitToInt(c); + if (escapeVal < 0) { + addToString('x'); + continue strLoop; + } else { + int c1 = c; + c = in.read(); + escapeVal = (escapeVal << 4) | xDigitToInt(c); + if (escapeVal < 0) { + addToString('x'); + addToString(c1); + continue strLoop; + } else { + // got 2 hex digits + c = escapeVal; + } + } + } break; + + case '\n': + // Remove line terminator + c = in.read(); + continue strLoop; + + default: if ('0' <= c && c < '8') { + val = c - '0'; + c = in.read(); + if ('0' <= c && c < '8') { + val = 8 * val + c - '0'; + c = in.read(); + if ('0' <= c && c < '8' && val <= 037) { + // c is 3rd char of octal sequence only if + // the resulting val <= 0377 + val = 8 * val + c - '0'; + c = in.read(); + } + } + in.unread(); + c = val; + } + } + } + addToString(c); + c = in.read(); + } + + this.string = getStringFromBuffer(); + return STRING; + } + + switch (c) + { + case '\n': return EOL; + case ';': return SEMI; + case '[': return LB; + case ']': return RB; + case '{': return LC; + case '}': return RC; + case '(': return LP; + case ')': return GWT; + case ',': return COMMA; + case '?': return HOOK; + case ':': return COLON; + case '.': return DOT; + + case '|': + if (in.match('|')) { + return OR; + } else if (in.match('=')) { + this.op = BITOR; + return ASSIGN; + } else { + return BITOR; + } + + case '^': + if (in.match('=')) { + this.op = BITXOR; + return ASSIGN; + } else { + return BITXOR; + } + + case '&': + if (in.match('&')) { + return AND; + } else if (in.match('=')) { + this.op = BITAND; + return ASSIGN; + } else { + return BITAND; + } + + case '=': + if (in.match('=')) { + if (in.match('=')) + this.op = SHEQ; + else + this.op = EQ; + return EQOP; + } else { + this.op = NOP; + return ASSIGN; + } + + case '!': + if (in.match('=')) { + if (in.match('=')) + this.op = SHNE; + else + this.op = NE; + return EQOP; + } else { + this.op = NOT; + return UNARYOP; + } + + case '<': + /* NB:treat HTML begin-comment as comment-till-eol */ + if (in.match('!')) { + if (in.match('-')) { + if (in.match('-')) { + skipLine(); + return RETRY_TOKEN; // in place of 'goto retry' + } + in.unread(); + } + in.unread(); + } + if (in.match('<')) { + if (in.match('=')) { + this.op = LSH; + return ASSIGN; + } else { + this.op = LSH; + return SHOP; + } + } else { + if (in.match('=')) { + this.op = LE; + return RELOP; + } else { + this.op = LT; + return RELOP; + } + } + + case '>': + if (in.match('>')) { + if (in.match('>')) { + if (in.match('=')) { + this.op = URSH; + return ASSIGN; + } else { + this.op = URSH; + return SHOP; + } + } else { + if (in.match('=')) { + this.op = RSH; + return ASSIGN; + } else { + this.op = RSH; + return SHOP; + } + } + } else { + if (in.match('=')) { + this.op = GE; + return RELOP; + } else { + this.op = GT; + return RELOP; + } + } + + case '*': + if (in.match('=')) { + this.op = MUL; + return ASSIGN; + } else { + return MUL; + } + + case '/': + // is it a // comment? + if (in.match('/')) { + skipLine(); + return RETRY_TOKEN; + } + if (in.match('*')) { + while ((c = in.read()) != -1 && + !(c == '*' && in.match('/'))) { + ; // empty loop body + } + if (c == EOF_CHAR) { + reportSyntaxError("msg.unterminated.comment", null); + return ERROR; + } + return RETRY_TOKEN; // `goto retry' + } + + // is it a regexp? + if ((flags & TSF_REGEXP) != 0) { + stringBufferTop = 0; + while ((c = in.read()) != '/') { + if (c == '\n' || c == EOF_CHAR) { + in.unread(); + reportSyntaxError("msg.unterminated.re.lit", null); + return ERROR; + } + if (c == '\\') { + addToString(c); + c = in.read(); + } + + addToString(c); + } + int reEnd = stringBufferTop; + + while (true) { + if (in.match('g')) + addToString('g'); + else if (in.match('i')) + addToString('i'); + else if (in.match('m')) + addToString('m'); + else + break; + } + + if (isAlpha(in.peek())) { + reportSyntaxError("msg.invalid.re.flag", null); + return ERROR; + } + + this.string = new String(stringBuffer, 0, reEnd); + this.regExpFlags = new String(stringBuffer, reEnd, + stringBufferTop - reEnd); + return REGEXP; + } + + + if (in.match('=')) { + this.op = DIV; + return ASSIGN; + } else { + return DIV; + } + + case '%': + this.op = MOD; + if (in.match('=')) { + return ASSIGN; + } else { + return MOD; + } + + case '~': + this.op = BITNOT; + return UNARYOP; + + case '+': + if (in.match('=')) { + this.op = ADD; + return ASSIGN; + } else if (in.match('+')) { + return INC; + } else { + return ADD; + } + + case '-': + if (in.match('=')) { + this.op = SUB; + c = ASSIGN; + } else if (in.match('-')) { + if (0 == (flags & TSF_DIRTYLINE)) { + // treat HTML end-comment after possible whitespace + // after line start as comment-utill-eol + if (in.match('>')) { + skipLine(); + return RETRY_TOKEN; + } + } + c = DEC; + } else { + c = SUB; + } + flags |= TSF_DIRTYLINE; + return c; + + default: + reportSyntaxError("msg.illegal.character", null); + return ERROR; + } + } + + private void skipWhitespace() throws IOException { + int tmp; + do { + tmp = in.read(); + } while (isJSSpace(tmp) || tmp == '\n'); + // Reposition back to first non whitespace char. + in.unread(); + } + + private int jsniMatchReference() throws IOException { + + // First, read the type name whose member is being accessed. + if (!jsniMatchQualifiedTypeName('.', ':')) { + return ERROR; + } + + // Now we must the second colon. + // + int c = in.read(); + if (c != ':') { + in.unread(); + reportSyntaxError("msg.jsni.expected.char", new String[] { ":" }); + return ERROR; + } + addToString(c); + + // Skip whitespace starting after ::. + skipWhitespace(); + + // Finish by reading the field or method signature. + if (!jsniMatchMethodSignatureOrFieldName()) { + return ERROR; + } + + this.string = new String(stringBuffer, 0, stringBufferTop); + return NAME; + } + + private boolean jsniMatchParamListSignature() throws IOException { + // Assume the opening '(' has already been read. + // Read param type signatures until we see a closing ')'. + + skipWhitespace(); + + // First check for the special case of * as the parameter list, indicating + // a wildcard + if (in.peek() == '*') { + addToString(in.read()); + if (in.peek() != ')') { + reportSyntaxError("msg.jsni.expected.char", new String[] { ")" }); + } + addToString(in.read()); + return true; + } + + // Otherwise, loop through reading one param type at a time + do { + // Skip whitespace between parameters. + skipWhitespace(); + + int c = in.read(); + + if (c == ')') { + // Finished successfully. + // + addToString(c); + return true; + } + + in.unread(); + } while (jsniMatchParamTypeSignature()); + + // If we made it here, we can assume that there was an invalid type + // signature that was already reported and that the offending char + // was already unread. + // + return false; + } + + private boolean jsniMatchParamTypeSignature() throws IOException { + int c = in.read(); + switch (c) { + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + // Primitive type id. + addToString(c); + return true; + case 'L': + // Class/Interface type prefix. + addToString(c); + return jsniMatchQualifiedTypeName('/', ';'); + case '[': + // Array type prefix. + addToString(c); + return jsniMatchParamArrayTypeSignature(); + default: + in.unread(); + reportSyntaxError("msg.jsni.expected.param.type", null); + return false; + } + } + + private boolean jsniMatchParamArrayTypeSignature() throws IOException { + // Assume the leading '[' has already been read. + // What follows must be another param type signature. + // + return jsniMatchParamTypeSignature(); + } + + private boolean jsniMatchMethodSignatureOrFieldName() throws IOException { + int c = in.read(); + + + // We must see an ident start here. + // + if (!Character.isJavaIdentifierStart((char)c)) { + in.unread(); + reportSyntaxError("msg.jsni.expected.identifier", null); + return false; + } + + addToString(c); + + for (;;) { + c = in.read(); + if (Character.isJavaIdentifierPart((char)c)) { + addToString(c); + } + else if (c == '(') { + // This means we're starting a JSNI method signature. + // + addToString(c); + if (jsniMatchParamListSignature()) { + // Finished a method signature with success. + // Assume the callee unread the last char. + // + return true; + } + else { + // Assume the callee reported the error and unread the last char. + // + return false; + } + } + else { + // We don't know this char, so it finishes the token. + // + in.unread(); + return true; + } + } + } + + /** + * This method is called to match the fully-qualified type name that + * should appear after the '@' in a JSNI reference. + * @param sepChar the character that will separate the Java idents + * (either a '.' or '/') + * @param endChar the character that indicates the end of the + */ + private boolean jsniMatchQualifiedTypeName(char sepChar, char endChar) + throws IOException { + int c = in.read(); + + // Whether nested or not, we must see an ident start here. + // + if (!Character.isJavaIdentifierStart((char)c)) { + in.unread(); + reportSyntaxError("msg.jsni.expected.identifier", null); + return false; + } + + // Now actually add the first ident char. + // + addToString(c); + + // And append any other ident chars. + // + for (;;) { + c = in.read(); + if (Character.isJavaIdentifierPart((char)c)) { + addToString(c); + } + else { + break; + } + } + + // Arrray-type reference + while (c == '[') { + if (']' == in.peek()) { + addToString('['); + addToString(in.read()); + c = in.read(); + } else { + break; + } + } + + // We have a non-ident char to classify. + // + if (c == sepChar) { + addToString(c); + if (jsniMatchQualifiedTypeName(sepChar, endChar)) { + // We consumed up to the endChar, so we finished with total success. + // + return true; + } else { + // Assume that the nested call reported the syntax error and + // unread the last character. + // + return false; + } + } else if (c == endChar) { + // Matched everything up to the specified end char. + // + addToString(c); + return true; + } else { + // This is an unknown char that finishes the token. + // + in.unread(); + return true; + } + } + + private String getStringFromBuffer() { + return new String(stringBuffer, 0, stringBufferTop); + } + + private void addToString(int c) { + if (stringBufferTop == stringBuffer.length) { + char[] tmp = new char[stringBuffer.length * 2]; + System.arraycopy(stringBuffer, 0, tmp, 0, stringBufferTop); + stringBuffer = tmp; + } + stringBuffer[stringBufferTop++] = (char)c; + } + + public void reportSyntaxError(String messageProperty, Object[] args) { + String message = Context.getMessage(messageProperty, args); + + Context.reportError(message, getSourceName(), + getLineno(), getLine(), getOffset()); + } + + private void reportSyntaxWarning(String messageProperty, Object[] args) { + String message = Context.getMessage(messageProperty, args); + Context.reportWarning(message, getSourceName(), + getLineno(), getLine(), getOffset()); + } + + public String getSourceName() { return sourceName; } + public int getLineno() { return in.getLineno(); } + public int getOp() { return op; } + public String getString() { return string; } + public double getNumber() { return number; } + public String getLine() { return in.getLine(); } + public int getOffset() { return in.getOffset(); } + public int getTokenno() { return tokenno; } + public boolean eof() { return in.eof(); } + + // instance variables + private LineBuffer in; + + + /* for TSF_REGEXP, etc. + * should this be manipulated by gettor/settor functions? + * should it be passed to getToken(); + */ + int flags; + String regExpFlags; + + private String sourceName; + private int pushbackToken; + private int tokenno; + + private int op; + + // Set this to an inital non-null value so that the Parser has + // something to retrieve even if an error has occured and no + // string is found. Fosters one class of error, but saves lots of + // code. + private String string = ""; + private double number; + + private char[] stringBuffer = new char[128]; + private int stringBufferTop; +} diff --git a/js/js.parser/src/com/google/gwt/dev/js/rhino/UintMap.java b/js/js.parser/src/com/google/gwt/dev/js/rhino/UintMap.java new file mode 100644 index 00000000000..365088e0f3f --- /dev/null +++ b/js/js.parser/src/com/google/gwt/dev/js/rhino/UintMap.java @@ -0,0 +1,657 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Map to associate non-negative integers to objects or integers. + * The map does not synchronize any of its operation, so either use + * it from a single thread or do own synchronization or perform all mutation + * operations on one thread before passing the map to others + * + * @author Igor Bukanov + * + */ + +class UintMap implements Serializable { + +// Map implementation via hashtable, +// follows "The Art of Computer Programming" by Donald E. Knuth + + public UintMap() { + this(4); + } + + public UintMap(int initialCapacity) { + if (initialCapacity < 0) Context.codeBug(); + // Table grow when number of stored keys >= 3/4 of max capacity + int minimalCapacity = initialCapacity * 4 / 3; + int i; + for (i = 2; (1 << i) < minimalCapacity; ++i) { } + power = i; + if (check && power < 2) Context.codeBug(); + } + + public boolean isEmpty() { + return keyCount == 0; + } + + public int size() { + return keyCount; + } + + public boolean has(int key) { + if (key < 0) Context.codeBug(); + return 0 <= findIndex(key); + } + + /** + * Get object value assigned with key. + * @return key object value or null if key is absent + */ + public Object getObject(int key) { + if (key < 0) Context.codeBug(); + if (values != null) { + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + } + return null; + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key is absent + */ + public int getInt(int key, int defaultValue) { + if (key < 0) Context.codeBug(); + int index = findIndex(key); + if (0 <= index) { + if (ivaluesShift != 0) { + return keys[ivaluesShift + index]; + } + return 0; + } + return defaultValue; + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key does not exist or does + * not have int value + * @throws RuntimeException if key does not exist + */ + public int getExistingInt(int key) { + if (key < 0) Context.codeBug(); + int index = findIndex(key); + if (0 <= index) { + if (ivaluesShift != 0) { + return keys[ivaluesShift + index]; + } + return 0; + } + // Key must exist + Context.codeBug(); + return 0; + } + + /** + * Set object value of the key. + * If key does not exist, also set its int value to 0. + */ + public void put(int key, Object value) { + if (key < 0) Context.codeBug(); + int index = ensureIndex(key, false); + if (values == null) { + values = new Object[1 << power]; + } + values[index] = value; + } + + /** + * Set int value of the key. + * If key does not exist, also set its object value to null. + */ + public void put(int key, int value) { + if (key < 0) Context.codeBug(); + int index = ensureIndex(key, true); + if (ivaluesShift == 0) { + int N = 1 << power; + // keys.length can be N * 2 after clear which set ivaluesShift to 0 + if (keys.length != N * 2) { + int[] tmp = new int[N * 2]; + System.arraycopy(keys, 0, tmp, 0, N); + keys = tmp; + } + ivaluesShift = N; + } + keys[ivaluesShift + index] = value; + } + + public void remove(int key) { + if (key < 0) Context.codeBug(); + int index = findIndex(key); + if (0 <= index) { + keys[index] = DELETED; + --keyCount; + // Allow to GC value and make sure that new key with the deleted + // slot shall get proper default values + if (values != null) { values[index] = null; } + if (ivaluesShift != 0) { keys[ivaluesShift + index] = 0; } + } + } + + public void clear() { + int N = 1 << power; + if (keys != null) { + for (int i = 0; i != N; ++i) { + keys[i] = EMPTY; + } + if (values != null) { + for (int i = 0; i != N; ++i) { + values[i] = null; + } + } + } + ivaluesShift = 0; + keyCount = 0; + occupiedCount = 0; + } + + /** Return array of present keys */ + public int[] getKeys() { + int[] keys = this.keys; + int n = keyCount; + int[] result = new int[n]; + for (int i = 0; n != 0; ++i) { + int entry = keys[i]; + if (entry != EMPTY && entry != DELETED) { + result[--n] = entry; + } + } + return result; + } + + private static int tableLookupStep(int fraction, int mask, int power) { + int shift = 32 - 2 * power; + if (shift >= 0) { + return ((fraction >>> shift) & mask) | 1; + } + else { + return (fraction & (mask >>> -shift)) | 1; + } + } + + private int findIndex(int key) { + int[] keys = this.keys; + if (keys != null) { + int fraction = key * A; + int index = fraction >>> (32 - power); + int entry = keys[index]; + if (entry == key) { return index; } + if (entry != EMPTY) { + // Search in table after first failed attempt + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + do { + if (check) { + if (n >= occupiedCount) Context.codeBug(); + ++n; + } + index = (index + step) & mask; + entry = keys[index]; + if (entry == key) { return index; } + } while (entry != EMPTY); + } + } + return -1; + } + +// Insert key that is not present to table without deleted entries +// and enough free space + private int insertNewKey(int key) { + if (check && occupiedCount != keyCount) Context.codeBug(); + if (check && keyCount == 1 << power) Context.codeBug(); + int[] keys = this.keys; + int fraction = key * A; + int index = fraction >>> (32 - power); + if (keys[index] != EMPTY) { + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int firstIndex = index; + do { + if (check && keys[index] == DELETED) Context.codeBug(); + index = (index + step) & mask; + if (check && firstIndex == index) Context.codeBug(); + } while (keys[index] != EMPTY); + } + keys[index] = key; + ++occupiedCount; + ++keyCount; + return index; + } + + private void rehashTable(boolean ensureIntSpace) { + if (keys != null) { + // Check if removing deleted entries would free enough space + if (keyCount * 2 >= occupiedCount) { + // Need to grow: less then half of deleted entries + ++power; + } + } + int N = 1 << power; + int[] old = keys; + int oldShift = ivaluesShift; + if (oldShift == 0 && !ensureIntSpace) { + keys = new int[N]; + } + else { + ivaluesShift = N; keys = new int[N * 2]; + } + for (int i = 0; i != N; ++i) { keys[i] = EMPTY; } + + Object[] oldValues = values; + if (oldValues != null) { values = new Object[N]; } + + int oldCount = keyCount; + occupiedCount = 0; + if (oldCount != 0) { + keyCount = 0; + for (int i = 0, remaining = oldCount; remaining != 0; ++i) { + int key = old[i]; + if (key != EMPTY && key != DELETED) { + int index = insertNewKey(key); + if (oldValues != null) { + values[index] = oldValues[i]; + } + if (oldShift != 0) { + keys[ivaluesShift + index] = old[oldShift + i]; + } + --remaining; + } + } + } + } + +// Ensure key index creating one if necessary + private int ensureIndex(int key, boolean intType) { + int index = -1; + int firstDeleted = -1; + int[] keys = this.keys; + if (keys != null) { + int fraction = key * A; + index = fraction >>> (32 - power); + int entry = keys[index]; + if (entry == key) { return index; } + if (entry != EMPTY) { + if (entry == DELETED) { firstDeleted = index; } + // Search in table after first failed attempt + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + do { + if (check) { + if (n >= occupiedCount) Context.codeBug(); + ++n; + } + index = (index + step) & mask; + entry = keys[index]; + if (entry == key) { return index; } + if (entry == DELETED && firstDeleted < 0) { + firstDeleted = index; + } + } while (entry != EMPTY); + } + } + // Inserting of new key + if (check && keys != null && keys[index] != EMPTY) + Context.codeBug(); + if (firstDeleted >= 0) { + index = firstDeleted; + } + else { + // Need to consume empty entry: check occupation level + if (keys == null || occupiedCount * 4 >= (1 << power) * 3) { + // Too few unused entries: rehash + rehashTable(intType); + return insertNewKey(key); + } + ++occupiedCount; + } + keys[index] = key; + ++keyCount; + return index; + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + + int count = keyCount; + if (count != 0) { + boolean hasIntValues = (ivaluesShift != 0); + boolean hasObjectValues = (values != null); + out.writeBoolean(hasIntValues); + out.writeBoolean(hasObjectValues); + + for (int i = 0; count != 0; ++i) { + int key = keys[i]; + if (key != EMPTY && key != DELETED) { + --count; + out.writeInt(key); + if (hasIntValues) { + out.writeInt(keys[ivaluesShift + i]); + } + if (hasObjectValues) { + out.writeObject(values[i]); + } + } + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + int writtenKeyCount = keyCount; + if (writtenKeyCount != 0) { + keyCount = 0; + boolean hasIntValues = in.readBoolean(); + boolean hasObjectValues = in.readBoolean(); + + int N = 1 << power; + if (hasIntValues) { + keys = new int[2 * N]; + ivaluesShift = N; + }else { + keys = new int[N]; + } + for (int i = 0; i != N; ++i) { + keys[i] = EMPTY; + } + if (hasObjectValues) { + values = new Object[N]; + } + for (int i = 0; i != writtenKeyCount; ++i) { + int key = in.readInt(); + int index = insertNewKey(key); + if (hasIntValues) { + int ivalue = in.readInt(); + keys[ivaluesShift + index] = ivalue; + } + if (hasObjectValues) { + values[index] = in.readObject(); + } + } + } + } + + static final long serialVersionUID = -6916326879143724506L; + + +// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32) +// See Knuth etc. + private static final int A = 0x9e3779b9; + + private static final int EMPTY = -1; + private static final int DELETED = -2; + +// Structure of kyes and values arrays (N == 1 << power): +// keys[0 <= i < N]: key value or EMPTY or DELETED mark +// values[0 <= i < N]: value of key at keys[i] +// keys[N <= i < 2N]: int values of keys at keys[i - N] + + private transient int[] keys; + private transient Object[] values; + + private int power; + private int keyCount; + private transient int occupiedCount; // == keyCount + deleted_count + + // If ivaluesShift != 0, keys[ivaluesShift + index] contains integer + // values associated with keys + private transient int ivaluesShift; + +// If true, enables consitency checks + private static final boolean check = false; + +/* TEST START + + public static void main(String[] args) { + if (!check) { + System.err.println("Set check to true and re-run"); + throw new RuntimeException("Set check to true and re-run"); + } + + UintMap map; + map = new UintMap(); + testHash(map, 2); + map = new UintMap(); + testHash(map, 10 * 1000); + map = new UintMap(30 * 1000); + testHash(map, 10 * 100); + map.clear(); + testHash(map, 4); + map = new UintMap(0); + testHash(map, 10 * 100); + } + + private static void testHash(UintMap map, int N) { + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, i); + check(i == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, i); + check(i == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, new Integer(i)); + check(-1 == map.getInt(i, -1)); + Integer obj = (Integer)map.getObject(i); + check(obj != null && i == obj.intValue()); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + int[] keys = map.getKeys(); + check(keys.length == N); + for (int i = 0; i != N; ++i) { + int key = keys[i]; + check(map.has(key)); + check(!map.isIntType(key)); + check(map.isObjectType(key)); + Integer obj = (Integer) map.getObject(key); + check(obj != null && key == obj.intValue()); + } + + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + check(-1 == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i * i, i); + check(i == map.getInt(i * i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + check(i == map.getInt(i * i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i * i, new Integer(i)); + check(-1 == map.getInt(i * i, -1)); + map.remove(i * i); + check(!map.has(i * i)); + map.put(i * i, i); + check(map.isIntType(i * i)); + check(null == map.getObject(i * i)); + map.remove(i * i); + check(!map.isObjectType(i * i)); + check(!map.isIntType(i * i)); + } + + int old_size = map.size(); + for (int i = 0; i != N; ++i) { + map.remove(i * i); + check(map.size() == old_size); + } + + System.out.print("."); System.out.flush(); + map.clear(); + check(map.size() == 0); + for (int i = 0; i != N; ++i) { + map.put(i * i, i); + map.put(i * i + 1, new Double(i+0.5)); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(0); + checkSameMaps(map, (UintMap)writeAndRead(map)); + map = new UintMap(1); + checkSameMaps(map, (UintMap)writeAndRead(map)); + map = new UintMap(1000); + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(N / 10); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, i); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(N / 10); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, i); + } + for (int i = 0; i != N / 2; ++i) { + map.remove(2*i+1); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, new Double(i + 10)); + } + for (int i = 0; i != N / 2; ++i) { + map.remove(2*i+1); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.println(); System.out.flush(); + + } + + private static void checkSameMaps(UintMap map1, UintMap map2) { + check(map1.size() == map2.size()); + int[] keys = map1.getKeys(); + check(keys.length == map1.size()); + for (int i = 0; i != keys.length; ++i) { + int key = keys[i]; + check(map2.has(key)); + check(map1.isObjectType(key) == map2.isObjectType(key)); + check(map1.isIntType(key) == map2.isIntType(key)); + Object o1 = map1.getObject(key); + Object o2 = map2.getObject(key); + if (map1.isObjectType(key)) { + check(o1.equals(o2)); + }else { + check(map1.getObject(key) == null); + check(map2.getObject(key) == null); + } + if (map1.isIntType(key)) { + check(map1.getExistingInt(key) == map2.getExistingInt(key)); + }else { + check(map1.getInt(key, -10) == -10); + check(map1.getInt(key, -11) == -11); + check(map2.getInt(key, -10) == -10); + check(map2.getInt(key, -11) == -11); + } + } + } + + private static void check(boolean condition) { + if (!condition) Context.codeBug(); + } + + private static Object writeAndRead(Object obj) { + try { + java.io.ByteArrayOutputStream + bos = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream + out = new java.io.ObjectOutputStream(bos); + out.writeObject(obj); + out.close(); + byte[] data = bos.toByteArray(); + java.io.ByteArrayInputStream + bis = new java.io.ByteArrayInputStream(data); + java.io.ObjectInputStream + in = new java.io.ObjectInputStream(bis); + Object result = in.readObject(); + in.close(); + return result; + }catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException("Unexpected"); + } + } + +// TEST END */ +} diff --git a/js/js.rhino-parser/src/com/google/gwt/dev/js/rhino/ObjArray.java b/js/js.rhino-parser/src/com/google/gwt/dev/js/rhino/ObjArray.java new file mode 100644 index 00000000000..85156730145 --- /dev/null +++ b/js/js.rhino-parser/src/com/google/gwt/dev/js/rhino/ObjArray.java @@ -0,0 +1,356 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// Modified by Google + +package com.google.gwt.dev.js.rhino; + +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** +Implementation of resizable array with focus on minimizing memory usage by storing few initial array elements in object fields. Can also be used as a stack. +*/ + +public class ObjArray implements Serializable { + + public ObjArray() { } + + public ObjArray(int capacityHint) { + if (capacityHint < 0) throw new IllegalArgumentException(); + if (capacityHint > FIELDS_STORE_SIZE) { + data = new Object[capacityHint - FIELDS_STORE_SIZE]; + } + } + + public final boolean isEmpty() { + return size == 0; + } + + public final int size() { + return size; + } + + public final void setSize(int newSize) { + if (newSize < 0) throw new IllegalArgumentException(); + int N = size; + if (newSize < N) { + for (int i = newSize; i != N; ++i) { + setImpl(i, null); + } + }else if (newSize > N) { + if (newSize > FIELDS_STORE_SIZE) { + ensureCapacity(newSize); + } + } + size = newSize; + } + + public final Object get(int index) { + if (!(0 <= index && index < size)) throw invalidIndex(index, size); + return getImpl(index); + } + + public final void set(int index, Object value) { + if (!(0 <= index && index < size)) throw invalidIndex(index, size); + setImpl(index, value); + } + + private Object getImpl(int index) { + switch (index) { + case 0: return f0; + case 1: return f1; + case 2: return f2; + case 3: return f3; + case 4: return f4; + case 5: return f5; + } + return data[index - FIELDS_STORE_SIZE]; + } + + private void setImpl(int index, Object value) { + switch (index) { + case 0: f0 = value; break; + case 1: f1 = value; break; + case 2: f2 = value; break; + case 3: f3 = value; break; + case 4: f4 = value; break; + case 5: f5 = value; break; + default: data[index - FIELDS_STORE_SIZE] = value; + } + + } + + public int indexOf(Object obj) { + int N = size; + for (int i = 0; i != N; ++i) { + Object current = getImpl(i); + if (current == obj || (current != null && current.equals(obj))) { + return i; + } + } + return -1; + } + + public int lastIndexOf(Object obj) { + for (int i = size; i != 0;) { + --i; + Object current = getImpl(i); + if (current == obj || (current != null && current.equals(obj))) { + return i; + } + } + return -1; + } + + public final Object peek() { + int N = size; + if (N == 0) throw invalidEmptyStackAccess(); + return getImpl(N - 1); + } + + public final Object pop() { + int N = size; + --N; + Object top; + switch (N) { + case -1: throw invalidEmptyStackAccess(); + case 0: top = f0; f0 = null; break; + case 1: top = f1; f1 = null; break; + case 2: top = f2; f2 = null; break; + case 3: top = f3; f3 = null; break; + case 4: top = f4; f4 = null; break; + case 5: top = f5; f5 = null; break; + default: + top = data[N - FIELDS_STORE_SIZE]; + data[N - FIELDS_STORE_SIZE] = null; + } + size = N; + return top; + } + + public final void push(Object value) { + add(value); + } + + public final void add(Object value) { + int N = size; + if (N >= FIELDS_STORE_SIZE) { + ensureCapacity(N + 1); + } + size = N + 1; + setImpl(N, value); + } + + public final void add(int index, Object value) { + Object tmp; + int N = size; + if (!(0 <= index && index <= N)) throw invalidIndex(index, N + 1); + switch (index) { + case 0: + if (N == 0) { f0 = value; break; } + tmp = f0; f0 = value; value = tmp; + case 1: + if (N == 1) { f1 = value; break; } + tmp = f1; f1 = value; value = tmp; + case 2: + if (N == 2) { f2 = value; break; } + tmp = f2; f2 = value; value = tmp; + case 3: + if (N == 3) { f3 = value; break; } + tmp = f3; f3 = value; value = tmp; + case 4: + if (N == 4) { f4 = value; break; } + tmp = f4; f4 = value; value = tmp; + case 5: + if (N == 5) { f5 = value; break; } + tmp = f5; f5 = value; value = tmp; + + index = FIELDS_STORE_SIZE; + default: + ensureCapacity(N + 1); + if (index != N) { + System.arraycopy(data, index - FIELDS_STORE_SIZE, + data, index - FIELDS_STORE_SIZE + 1, + N - index); + } + data[index - FIELDS_STORE_SIZE] = value; + } + size = N + 1; + } + + public final void remove(int index) { + int N = size; + if (!(0 <= index && index < N)) throw invalidIndex(index, N); + --N; + switch (index) { + case 0: + if (N == 0) { f0 = null; break; } + f0 = f1; + case 1: + if (N == 1) { f1 = null; break; } + f1 = f2; + case 2: + if (N == 2) { f2 = null; break; } + f2 = f3; + case 3: + if (N == 3) { f3 = null; break; } + f3 = f4; + case 4: + if (N == 4) { f4 = null; break; } + f4 = f5; + case 5: + if (N == 5) { f5 = null; break; } + f5 = data[0]; + + index = FIELDS_STORE_SIZE; + default: + if (index != N) { + System.arraycopy(data, index - FIELDS_STORE_SIZE + 1, + data, index - FIELDS_STORE_SIZE, + N - index); + } + data[N - FIELDS_STORE_SIZE] = null; + } + size = N; + } + + public final void clear() { + int N = size; + for (int i = 0; i != N; ++i) { + setImpl(i, null); + } + size = 0; + } + + public final Object[] toArray() { + Object[] array = new Object[size]; + toArray(array, 0); + return array; + } + + public final void toArray(Object[] array) { + toArray(array, 0); + } + + public final void toArray(Object[] array, int offset) { + int N = size; + switch (N) { + default: + System.arraycopy(data, 0, array, offset + FIELDS_STORE_SIZE, + N - FIELDS_STORE_SIZE); + case 6: array[offset + 5] = f5; + case 5: array[offset + 4] = f4; + case 4: array[offset + 3] = f3; + case 3: array[offset + 2] = f2; + case 2: array[offset + 1] = f1; + case 1: array[offset + 0] = f0; + case 0: break; + } + } + + private void ensureCapacity(int minimalCapacity) { + int required = minimalCapacity - FIELDS_STORE_SIZE; + if (required <= 0) throw new IllegalArgumentException(); + if (data == null) { + int alloc = FIELDS_STORE_SIZE * 2; + if (alloc < required) { + alloc = required; + } + data = new Object[alloc]; + } else { + int alloc = data.length; + if (alloc < required) { + if (alloc <= FIELDS_STORE_SIZE) { + alloc = FIELDS_STORE_SIZE * 2; + } else { + alloc *= 2; + } + if (alloc < required) { + alloc = required; + } + Object[] tmp = new Object[alloc]; + if (size > FIELDS_STORE_SIZE) { + System.arraycopy(data, 0, tmp, 0, + size - FIELDS_STORE_SIZE); + } + data = tmp; + } + } + } + + private static RuntimeException invalidIndex(int index, int upperBound) { + // \u2209 is "NOT ELEMENT OF" + String msg = index+" \u2209 [0, "+upperBound+')'; + return new IndexOutOfBoundsException(msg); + } + + private static RuntimeException invalidEmptyStackAccess() { + throw new RuntimeException("Empty stack"); + } + + private void writeObject(ObjectOutputStream os) throws IOException { + os.defaultWriteObject(); + int N = size; + for (int i = 0; i != N; ++i) { + Object obj = getImpl(i); + os.writeObject(obj); + } + } + + private void readObject(ObjectInputStream is) + throws IOException, ClassNotFoundException + { + is.defaultReadObject(); // It reads size + int N = size; + if (N > FIELDS_STORE_SIZE) { + data = new Object[N - FIELDS_STORE_SIZE]; + } + for (int i = 0; i != N; ++i) { + Object obj = is.readObject(); + setImpl(i, obj); + } + } + + static final long serialVersionUID = 7448768847663119705L; + +// Number of data elements + private int size; + + private static final int FIELDS_STORE_SIZE = 6; + private transient Object f0, f1, f2, f3, f4, f5; + private transient Object[] data; +} diff --git a/js/js.translator/js.translator.iml b/js/js.translator/js.translator.iml index 7f4ef570412..517bbe9302c 100644 --- a/js/js.translator/js.translator.iml +++ b/js/js.translator/js.translator.iml @@ -15,5 +15,4 @@ - - + \ No newline at end of file