Initial checkin for Rhino 1.7. Add JavaScript 1.7 version number.

Initial implementation of 1.7 generators for interpretive mode. See bug 379377.


git-svn-id: svn://10.0.0.236/trunk@227537 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
nboyd%atg.com 2007-06-05 18:23:49 +00:00
parent 496b8fa553
commit d71e9843a3
20 changed files with 799 additions and 163 deletions

View File

@ -36,9 +36,9 @@
name: rhino
Name: Rhino
version: 1_6R6pre
version: 1_7R1pre
# See Context#getImplementationVersion() for format of this!
implementation.version: Rhino 1.6 release 6 Pre ${implementation.date}
implementation.version: Rhino 1.7 release 1 Pre ${implementation.date}
build.dir: build
rhino.jar: js.jar

View File

@ -133,6 +133,11 @@ public class Context
*/
public static final int VERSION_1_6 = 160;
/**
* JavaScript 1.7
*/
public static final int VERSION_1_7 = 170;
/**
* Controls behaviour of <tt>Date.prototype.getYear()</tt>.
* If <tt>hasFeature(FEATURE_NON_ECMA_GET_YEAR)</tt> returns true,
@ -758,6 +763,7 @@ public class Context
case VERSION_1_4:
case VERSION_1_5:
case VERSION_1_6:
case VERSION_1_7:
return true;
}
return false;

View File

@ -747,6 +747,10 @@ public class Decompiler
case Token.CONST:
result.append("const ");
break;
case Token.YIELD:
result.append("yield ");
break;
case Token.NOT:
result.append('!');

View File

@ -57,6 +57,10 @@ public class FunctionNode extends ScriptOrFnNode {
public boolean getIgnoreDynamicScope() {
return itsIgnoreDynamicScope;
}
public boolean isGenerator() {
return itsIsGenerator;
}
/**
* There are three types of functions that can be defined. The first
@ -82,7 +86,8 @@ public class FunctionNode extends ScriptOrFnNode {
}
String functionName;
boolean itsNeedsActivation;
int itsFunctionType;
boolean itsNeedsActivation;
boolean itsIgnoreDynamicScope;
boolean itsIsGenerator;
}

View File

@ -950,6 +950,19 @@ final class IRFactory
return new Node(nodeType, child);
}
Node createYield(Node child, int lineno)
{
if (!parser.insideFunction()) {
parser.reportError("msg.bad.yield");
}
setRequiresActivation();
setIsGenerator();
if (child != null)
return new Node(Token.YIELD, child, lineno);
else
return new Node(Token.YIELD, lineno);
}
Node createCallOrNew(int nodeType, Node child)
{
int type = Node.NON_SPECIALCALL;
@ -1417,6 +1430,13 @@ final class IRFactory
}
}
private void setIsGenerator()
{
if (parser.insideFunction()) {
((FunctionNode)parser.currentScriptOrFn).itsIsGenerator = true;
}
}
private Parser parser;
private static final int LOOP_DO_WHILE = 0;

View File

@ -187,6 +187,12 @@ final class InterpretedFunction extends NativeFunction implements Script
return idata;
}
Object resumeGenerator(Context cx, Scriptable scope, int operation,
Object state, Object value)
{
return Interpreter.resumeGenerator(cx, scope, operation, state, value);
}
protected int getLanguageVersion()
{
return idata.languageVersion;

View File

@ -160,20 +160,24 @@ public class Interpreter
Icode_TAIL_CALL = -55,
// Clear local to allow GC its context
// Clear local to allow GC its context
Icode_LOCAL_CLEAR = -56,
// Literal get/set
// Literal get/set
Icode_LITERAL_GETTER = -57,
Icode_LITERAL_SETTER = -58,
// const
// const
Icode_SETCONST = -59,
Icode_SETCONSTVAR = -60,
Icode_SETCONSTVAR1 = -61,
// Generator opcodes (along with Token.YIELD)
Icode_GENERATOR = -62,
Icode_GENERATOR_END = -63,
// Last icode
MIN_ICODE = -61;
MIN_ICODE = -63;
// data for parsing
@ -335,7 +339,18 @@ public class Interpreter
Kit.codeBug();
}
}
}
private static CallFrame captureFrameForGenerator(CallFrame frame) {
frame.frozen = true;
CallFrame result = frame.cloneFrozen();
frame.frozen = false;
// now isolate this frame from its previous context
result.parentFrame = null;
result.frameIndex = 0;
return result;
}
static {
@ -429,6 +444,8 @@ public class Interpreter
case Icode_SETCONST: return "SETCONST";
case Icode_SETCONSTVAR: return "SETCONSTVAR";
case Icode_SETCONSTVAR1: return "SETCONSTVAR1";
case Icode_GENERATOR: return "GENERATOR";
case Icode_GENERATOR_END: return "GENERATOR_END";
}
// icode without name
@ -517,6 +534,10 @@ public class Interpreter
itsData.useDynamicScope = true;
}
}
if (theFunction.isGenerator()) {
addIcode(Icode_GENERATOR);
addUint16(theFunction.getBaseLineno() & 0xFFFF);
}
generateICodeFromTree(theFunction.getLastChild());
}
@ -863,7 +884,11 @@ public class Interpreter
case Token.RETURN:
updateLineNumber(node);
if (child != null) {
if (node.getIntProp(Node.GENERATOR_END_PROP, 0) != 0) {
// We're in a generator, so change RETURN to GENERATOR_END
addIcode(Icode_GENERATOR_END);
addUint16(itsLineNumber & 0xFFFF);
} else if (child != null) {
visitExpression(child, ECF_TAIL);
addToken(Token.RETURN);
stackChange(-1);
@ -884,6 +909,9 @@ public class Interpreter
stackChange(-1);
break;
case Icode_GENERATOR:
break;
default:
throw badTree(node);
}
@ -1322,6 +1350,17 @@ public class Interpreter
addToken(type);
break;
case Token.YIELD:
if (child != null) {
visitExpression(child, 0);
} else {
addIcode(Icode_UNDEF);
stackChange(1);
}
addToken(Token.YIELD);
addUint16(node.getLineno() & 0xFFFF);
break;
default:
throw badTree(node);
}
@ -1605,7 +1644,7 @@ public class Interpreter
byte[] array = itsData.itsICode;
int top = itsICodeTop;
if (top == array.length) {
array = increaseICodeCapasity(1);
array = increaseICodeCapacity(1);
}
array[top] = (byte)value;
itsICodeTop = top + 1;
@ -1617,7 +1656,7 @@ public class Interpreter
byte[] array = itsData.itsICode;
int top = itsICodeTop;
if (top + 2 > array.length) {
array = increaseICodeCapasity(2);
array = increaseICodeCapacity(2);
}
array[top] = (byte)(value >>> 8);
array[top + 1] = (byte)value;
@ -1629,7 +1668,7 @@ public class Interpreter
byte[] array = itsData.itsICode;
int top = itsICodeTop;
if (top + 4 > array.length) {
array = increaseICodeCapasity(4);
array = increaseICodeCapacity(4);
}
array[top] = (byte)(i >>> 24);
array[top + 1] = (byte)(i >>> 16);
@ -1658,7 +1697,7 @@ public class Interpreter
byte[] array = itsData.itsICode;
int top = itsICodeTop;
if (top + 3 > array.length) {
array = increaseICodeCapasity(3);
array = increaseICodeCapacity(3);
}
array[top] = (byte)gotoOp;
// Offset would written later
@ -1774,7 +1813,7 @@ public class Interpreter
itsExceptionTableTop = top + EXCEPTION_SLOT_SIZE;
}
private byte[] increaseICodeCapasity(int extraSize)
private byte[] increaseICodeCapacity(int extraSize)
{
int capacity = itsData.itsICode.length;
int top = itsICodeTop;
@ -1958,7 +1997,11 @@ public class Interpreter
case Token.NEW :
out.println(tname+' '+indexReg);
break;
case Token.THROW : {
case Token.THROW :
case Token.YIELD :
case Icode_GENERATOR :
case Icode_GENERATOR_END :
{
int line = getIndex(iCode, pc);
out.println(tname + " : " + line);
pc += 2;
@ -2006,6 +2049,24 @@ public class Interpreter
pc += 4;
break;
}
case Icode_REG_IND_C0:
indexReg = 0;
break;
case Icode_REG_IND_C1:
indexReg = 1;
break;
case Icode_REG_IND_C2:
indexReg = 2;
break;
case Icode_REG_IND_C3:
indexReg = 3;
break;
case Icode_REG_IND_C4:
indexReg = 4;
break;
case Icode_REG_IND_C5:
indexReg = 5;
break;
case Icode_REG_IND1: {
indexReg = 0xFF & iCode[pc];
out.println(tname+" "+indexReg);
@ -2062,6 +2123,9 @@ public class Interpreter
{
switch (bytecode) {
case Token.THROW :
case Token.YIELD:
case Icode_GENERATOR:
case Icode_GENERATOR_END:
// source line
return 1 + 2;
@ -2326,6 +2390,40 @@ public class Interpreter
return interpretLoop(cx, frame, null);
}
static class GeneratorState {
GeneratorState(int operation, Object value) {
this.operation = operation;
this.value = value;
}
int operation;
Object value;
RuntimeException returnedException;
}
public static Object resumeGenerator(Context cx,
Scriptable scope,
int operation,
Object savedState,
Object value)
{
CallFrame frame = (CallFrame) savedState;
GeneratorState generatorState = new GeneratorState(operation, value);
if (operation == NativeGenerator.GENERATOR_CLOSE) {
try {
return interpretLoop(cx, frame, generatorState);
} catch (RuntimeException e) {
// Only propagate exceptions other than closingException
if (e != value)
throw e;
}
return Undefined.instance;
}
Object result = interpretLoop(cx, frame, generatorState);
if (generatorState.returnedException != null)
throw generatorState.returnedException;
return result;
}
public static Object restartContinuation(Continuation c, Context cx,
Scriptable scope, Object[] args)
{
@ -2388,9 +2486,15 @@ public class Interpreter
// catch bugs with using indeReg to access array eleemnts before
// initializing indexReg.
GeneratorState generatorState = null;
if (throwable != null) {
// Assert assumptions
if (!(throwable instanceof ContinuationJump)) {
if (throwable instanceof GeneratorState) {
generatorState = (GeneratorState) throwable;
// reestablish this call frame
enterFrame(cx, frame, ScriptRuntime.emptyArgs, true);
throwable = null;
} else if (!(throwable instanceof ContinuationJump)) {
// It should be continuation
Kit.codeBug();
}
@ -2493,7 +2597,7 @@ public class Interpreter
}
} else {
if (frame.frozen) Kit.codeBug();
if (generatorState == null && frame.frozen) Kit.codeBug();
}
// Use local variables for constant values in frame
@ -2508,7 +2612,7 @@ public class Interpreter
// Use local for stackTop as well. Since execption handlers
// can only exist at statement level where stack is empty,
// it is necessary to save/restore stackTop only accross
// it is necessary to save/restore stackTop only across
// function calls and normal returns.
int stackTop = frame.savedStackTop;
@ -2523,8 +2627,76 @@ public class Interpreter
int op = iCode[frame.pc++];
jumplessRun: {
// Back indent to ease imlementation reading
// Back indent to ease implementation reading
switch (op) {
case Icode_GENERATOR: {
if (!frame.frozen) {
// First time encountering this opcode: create new generator
// object and return
frame.pc--; // we want to come back here when we resume
CallFrame generatorFrame = captureFrameForGenerator(frame);
generatorFrame.frozen = true;
NativeGenerator generator
= new NativeGenerator(generatorFrame.fnOrScript, generatorFrame);
ScriptRuntime.setObjectProtoAndParent(generator,
ScriptRuntime.getTopCallScope(cx));
frame.result = generator;
break Loop;
} else {
// We are now resuming execution. Fall through to YIELD case.
}
}
// fall through...
case Token.YIELD: {
if (!frame.frozen) {
if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
// Error: no yields when generator is closing
throw ScriptRuntime.typeError0("msg.yield.closing");
}
// return to our caller (which should be a method of NativeGenerator)
frame.frozen = true;
frame.result = stack[stackTop];
frame.resultDbl = sDbl[stackTop];
frame.savedStackTop = stackTop;
frame.pc--; // we want to come back here when we resume
ScriptRuntime.exitActivationFunction(cx);
return (frame.result != DBL_MRK)
? frame.result
: ScriptRuntime.wrapNumber(frame.resultDbl);
} else {
// we are resuming execution
frame.frozen = false;
int sourceLine = getIndex(iCode, frame.pc);
frame.pc += 2; // skip line number data
if (generatorState.operation == NativeGenerator.GENERATOR_THROW) {
// processing a call to <generator>.throw(exception): must
// act as if exception was thrown from resumption point
throwable = new JavaScriptException(generatorState.value,
frame.idata.itsSourceFile,
sourceLine);
break withoutExceptions;
}
if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
throwable = generatorState.value;
break withoutExceptions;
}
if (generatorState.operation != NativeGenerator.GENERATOR_SEND)
throw Kit.codeBug();
if (op == Token.YIELD)
stack[stackTop] = generatorState.value;
continue Loop;
}
}
case Icode_GENERATOR_END: {
// throw StopIteration
frame.frozen = true;
Scriptable top = ScriptableObject.getTopLevelScope(frame.scope);
Object e = top.get(NativeGenerator.STOP_ITERATION, frame.scope);
int sourceLine = getIndex(iCode, frame.pc);
generatorState.returnedException =
new JavaScriptException(e, frame.idata.itsSourceFile, sourceLine);
break Loop;
}
case Token.THROW: {
Object value = stack[stackTop];
if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]);
@ -3124,7 +3296,8 @@ switch (op) {
// optimization will create a "hole" in the context stack.
// The correct thing to do may be to disable tail call
// optimization if the code is being debugged.
exitFrame(cx, frame, null); }
exitFrame(cx, frame, null);
}
initFrame(cx, calleeScope, funThisObj, stack, sDbl,
stackTop + 2, indexReg, ifun, callParentFrame,
calleeFrame);
@ -3704,7 +3877,12 @@ switch (op) {
int exState;
ContinuationJump cjump = null;
if (throwable instanceof JavaScriptException) {
if (generatorState != null &&
generatorState.operation == NativeGenerator.GENERATOR_CLOSE &&
throwable == generatorState.value)
{
exState = EX_FINALLY_STATE;
} else if (throwable instanceof JavaScriptException) {
exState = EX_CATCH_STATE;
} else if (throwable instanceof EcmaError) {
// an offical ECMA error object,
@ -3762,7 +3940,7 @@ switch (op) {
continue StateLoop;
}
}
// No allowed execption handlers in this frame, unwind
// No allowed exception handlers in this frame, unwind
// to parent and try to look there
exitFrame(cx, frame, throwable);
@ -3988,7 +4166,8 @@ switch (op) {
return frame.debuggerFrame != null || frame.idata.itsNeedsActivation;
}
private static void enterFrame(Context cx, CallFrame frame, Object[] args, boolean continuationRestart)
private static void enterFrame(Context cx, CallFrame frame, Object[] args,
boolean continuationRestart)
{
boolean usesActivation = frame.idata.itsNeedsActivation;
boolean isDebugged = frame.debuggerFrame != null;
@ -3996,7 +4175,7 @@ switch (op) {
Scriptable scope = frame.scope;
if(scope == null) {
Kit.codeBug();
} else if(continuationRestart) {
} else if (continuationRestart) {
// Walk the parent chain of frame.scope until a NativeCall is
// found. Normally, frame.scope is a NativeCall when called
// from initFrame() for a debugged or activatable function.
@ -4010,7 +4189,9 @@ switch (op) {
break;
} else {
scope = scope.getParentScope();
if(scope == null || (frame.parentFrame != null && frame.parentFrame.scope == scope)) {
if (scope == null || (frame.parentFrame != null &&
frame.parentFrame.scope == scope))
{
// If we get here, we didn't find a NativeCall in
// the call chain before reaching parent frame's
// scope. This should not be possible.

View File

@ -116,6 +116,16 @@ public abstract class NativeFunction extends BaseFunction
return null;
}
/*
* Resumes execution of a JS 1.7 generator.
*/
/*abstract*/ Object resumeGenerator(Context cx, Scriptable scope,
int operation, Object state, Object value)
{
// TODO(js1.7gen): make abstract once bytecode generation is done
throw new EvaluatorException("Not yet implemented");
}
protected abstract int getLanguageVersion();
/**

View File

@ -0,0 +1,227 @@
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla 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/MPL/
*
* 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 the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Norris Boyd
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (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
* MPL, indicate your decision by deleting the provisions above and replacing
* 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 MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.javascript;
/**
* This class implements generator objects. See
* http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Generators
*
* @author Norris Boyd
*/
public final class NativeGenerator extends IdScriptableObject {
private static final Object GENERATOR_TAG = new Object();
static void init(Scriptable scope, boolean sealed) {
// Generator
new NativeGenerator().exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed);
// StopIteration
NativeObject obj = new StopIteration();
obj.setPrototype(getObjectPrototype(scope));
obj.setParentScope(scope);
if (sealed) { obj.sealObject(); }
ScriptableObject.defineProperty(scope, STOP_ITERATION, obj,
ScriptableObject.DONTENUM);
}
/**
* Only for constructing the prototype object.
*/
private NativeGenerator() { }
NativeGenerator(NativeFunction function, Object savedState) {
this.function = function;
this.savedState = savedState;
}
public static final String STOP_ITERATION = "StopIteration";
static class StopIteration extends NativeObject {
public String getClassName() { return STOP_ITERATION; }
}
public static final int GENERATOR_SEND = 0,
GENERATOR_THROW = 1,
GENERATOR_CLOSE = 2;
public String getClassName() {
return "Generator";
}
protected void initPrototypeId(int id) {
String s;
int arity;
switch (id) {
case Id_constructor: arity=1; s="constructor"; break;
case Id_close: arity=1; s="close"; break;
case Id_next: arity=1; s="next"; break;
case Id_send: arity=0; s="send"; break;
case Id_throw: arity=0; s="throw"; break;
default: throw new IllegalArgumentException(String.valueOf(id));
}
initPrototypeMethod(GENERATOR_TAG, id, s, arity);
}
public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope,
Scriptable thisObj, Object[] args)
{
if (!f.hasTag(GENERATOR_TAG)) {
return super.execIdCall(f, cx, scope, thisObj, args);
}
int id = f.methodId();
if (!(thisObj instanceof NativeGenerator))
throw incompatibleCallError(f);
NativeGenerator generator = (NativeGenerator) thisObj;
switch (id) {
case Id_constructor:
// TODO(js1.7gen): Shouldn't have a constructor. Currently need
// one to get Generator.prototype
return null;
case Id_close:
// need to run any pending finally clauses
return generator.resume(cx, scope, GENERATOR_CLOSE,
new RuntimeException());
case Id_next:
// arguments to next() are ignored
generator.firstTime = false;
return generator.resume(cx, scope, GENERATOR_SEND,
Undefined.instance);
case Id_send:
if (generator.firstTime) {
throw ScriptRuntime.typeError0("msg.send.newborn");
}
return generator.resume(cx, scope, GENERATOR_SEND,
args.length > 0 ? args[0] : Undefined.instance);
case Id_throw:
return generator.resume(cx, scope, GENERATOR_THROW,
args.length > 0 ? args[0] : Undefined.instance);
default:
throw new IllegalArgumentException(String.valueOf(id));
}
}
private Object resume(Context cx, Scriptable scope, int operation,
Object value)
{
if (savedState == null) {
if (operation == GENERATOR_CLOSE)
return Undefined.instance;
Object thrown = operation == GENERATOR_THROW
? value
: ScriptableObject.getTopLevelScope(scope).get(STOP_ITERATION,
scope);
throw new JavaScriptException(thrown, lineSource, lineNumber);
}
try {
synchronized (this) {
// generator execution is necessarily single-threaded and
// non-reentrant.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=349263
if (locked)
throw ScriptRuntime.typeError0("msg.already.exec.gen");
locked = true;
}
return function.resumeGenerator(cx, scope, operation, savedState,
value);
} catch (RhinoException e) {
lineNumber = e.lineNumber();
lineSource = e.lineSource();
savedState = null;
throw e;
} finally {
synchronized (this) {
locked = false;
}
if (operation == GENERATOR_CLOSE)
savedState = null;
}
}
// #string_id_map#
protected int findPrototypeId(String s) {
int id;
// #generated# Last update: 2007-05-09 08:23:27 EDT
L0: { id = 0; String X = null; int c;
int s_length = s.length();
if (s_length==4) {
c=s.charAt(0);
if (c=='n') { X="next";id=Id_next; }
else if (c=='s') { X="send";id=Id_send; }
}
else if (s_length==5) {
c=s.charAt(0);
if (c=='c') { X="close";id=Id_close; }
else if (c=='t') { X="throw";id=Id_throw; }
}
else if (s_length==11) { X="constructor";id=Id_constructor; }
if (X!=null && X!=s && !X.equals(s)) id = 0;
break L0;
}
// #/generated#
return id;
}
private static final int
Id_constructor = 1,
Id_close = 2,
Id_next = 3,
Id_send = 4,
Id_throw = 5,
MAX_PROTOTYPE_ID = 5;
// #/string_id_map#
private NativeFunction function;
private Object savedState;
private String lineSource;
private int lineNumber;
private boolean firstTime = true;
private boolean locked;
}

View File

@ -82,7 +82,8 @@ public class Node
NAME_PROP = 17, // property name
CONTROL_BLOCK_PROP = 18, // flags a control block that can drop off
PARENTHESIZED_PROP = 19, // expression is parenthesized
LAST_PROP = 19;
GENERATOR_END_PROP = 20,
LAST_PROP = 20;
// values of ISNUMBER_PROP to specify
// which of the children are Number types
@ -460,6 +461,7 @@ public class Node
case NAME_PROP: return "name_prop";
case CONTROL_BLOCK_PROP: return "control_block_prop";
case PARENTHESIZED_PROP: return "parenthesized_prop";
case GENERATOR_END_PROP: return "generator_end";
default: Kit.codeBug();
}

View File

@ -135,6 +135,19 @@ public class NodeTransformer
case Token.RETURN:
{
boolean isGenerator = tree.getType() == Token.FUNCTION
&& ((FunctionNode)tree).isGenerator();
if (isGenerator) {
node.putIntProp(Node.GENERATOR_END_PROP, 1);
/*
// Replace returns inside generators with throw STOP_ITERATION
node = replaceCurrent(parent, previous, node,
new Node(Token.THROW,
Node.newString(Token.NAME,
NativeGenerator.STOP_ITERATION),
node.getLineno()));
*/
}
/* If we didn't support try/finally, it wouldn't be
* necessary to put LEAVEWITH nodes here... but as
* we do need a series of JSR FINALLY nodes before
@ -169,7 +182,7 @@ public class NodeTransformer
Node returnNode = node;
Node returnExpr = returnNode.getFirstChild();
node = replaceCurrent(parent, previous, node, unwindBlock);
if (returnExpr == null) {
if (returnExpr == null || isGenerator) {
unwindBlock.addChildToBack(returnNode);
} else {
Node store = new Node(Token.EXPR_RESULT, returnExpr);

View File

@ -1069,44 +1069,9 @@ public class Parser
break;
}
case Token.RETURN: {
if (!insideFunction()) {
reportError("msg.bad.return");
}
consumeToken();
decompiler.addToken(Token.RETURN);
int lineno = ts.getLineno();
Node retExpr;
/* This is ugly, but we don't want to require a semicolon. */
tt = peekTokenOrEOL();
switch (tt) {
case Token.SEMI:
case Token.RC:
case Token.EOF:
case Token.EOL:
case Token.ERROR:
retExpr = null;
break;
default:
retExpr = expr(false);
hasReturnValue = true;
}
pn = nf.createReturn(retExpr, lineno);
// see if we need a strict mode warning
if (retExpr == null) {
if (functionEndFlags == Node.END_RETURNS_VALUE)
addStrictWarning("msg.return.inconsistent", "");
functionEndFlags |= Node.END_RETURNS;
} else {
if (functionEndFlags == Node.END_RETURNS)
addStrictWarning("msg.return.inconsistent", "");
functionEndFlags |= Node.END_RETURNS_VALUE;
}
case Token.RETURN:
case Token.YIELD: {
pn = returnOrYield(tt, false);
break;
}
@ -1241,6 +1206,58 @@ public class Parser
return pn;
}
private Node returnOrYield(int tt, boolean exprContext)
throws IOException, ParserException
{
if (!insideFunction()) {
reportError(tt == Token.RETURN ? "msg.bad.return"
: "msg.bad.yield");
}
consumeToken();
decompiler.addToken(tt);
int lineno = ts.getLineno();
Node e;
/* This is ugly, but we don't want to require a semicolon. */
switch (peekTokenOrEOL()) {
case Token.SEMI:
case Token.RC:
case Token.EOF:
case Token.EOL:
case Token.ERROR:
case Token.RB:
case Token.RP:
case Token.YIELD:
e = null;
break;
default:
e = expr(false);
break;
}
if (tt == Token.RETURN) {
// see if we need a strict mode warning
// TODO(js1.7gen): check for mixture of yield and value returns
if (e == null) {
if (functionEndFlags == Node.END_RETURNS_VALUE)
addStrictWarning("msg.return.inconsistent", "");
functionEndFlags |= Node.END_RETURNS;
} else {
hasReturnValue = true;
if (functionEndFlags == Node.END_RETURNS)
addStrictWarning("msg.return.inconsistent", "");
functionEndFlags |= Node.END_RETURNS_VALUE;
}
return nf.createReturn(e, lineno);
} else {
Node n = nf.createYield(e, lineno);
if (exprContext)
return n;
return new Node(Token.EXPR_VOID, n, lineno);
}
}
/**
* Parse a 'var' or 'const' statement, or a 'var' init list in a for
@ -1321,6 +1338,9 @@ public class Parser
decompiler.addToken(Token.COMMA);
if (!pn.hasSideEffects())
addStrictWarning("msg.no.side.effects", "");
if (peekToken() == Token.YIELD) {
reportError("msg.yield.parenthesized");
}
pn = nf.createBinary(Token.COMMA, pn, assignExpr(inForInit));
}
return pn;
@ -1329,9 +1349,14 @@ public class Parser
private Node assignExpr(boolean inForInit)
throws IOException, ParserException
{
int tt = peekToken();
if (tt == Token.YIELD) {
consumeToken();
return returnOrYield(tt, true);
}
Node pn = condExpr(inForInit);
int tt = peekToken();
tt = peekToken();
if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) {
consumeToken();
decompiler.addToken(tt);
@ -1687,6 +1712,9 @@ public class Parser
if (!first)
decompiler.addToken(Token.COMMA);
first = false;
if (peekToken() == Token.YIELD) {
reportError("msg.yield.parenthesized");
}
nf.addChildToBack(listNode, assignExpr(false));
} while (matchToken(Token.COMMA));
@ -1769,6 +1797,13 @@ public class Parser
tt = nextToken();
switch (tt) {
// needed for generator.throw();
case Token.THROW:
decompiler.addName("throw");
pn = propertyName(pn, "throw", memberTypeFlags);
break;
// handles: name, ns::name, ns::*, ns::[expr]
case Token.NAME:
s = ts.getString();

View File

@ -202,6 +202,10 @@ public class ScriptRuntime {
NativeWith.init(scope, sealed);
NativeCall.init(scope, sealed);
NativeScript.init(scope, sealed);
// TODO(js1.7gen): jsshell marks generators as JSCLASS_IS_ANONYMOUS,
// meaning that "Generator" is not defined in the global scope
NativeGenerator.init(scope, sealed);
boolean withXml = cx.hasFeature(Context.FEATURE_E4X) && cx.getE4xImplementationFactory() != null;

View File

@ -144,113 +144,115 @@ public class Token
DEL_REF = 67, // delete reference
REF_CALL = 68, // f(args) = something or f(args)++
REF_SPECIAL = 69, // reference for special properties like __proto
YIELD = 70, // JS 1.7 yield pseudo keyword
// For XML support:
DEFAULTNAMESPACE = 70, // default xml namespace =
ESCXMLATTR = 71,
ESCXMLTEXT = 72,
REF_MEMBER = 73, // Reference for x.@y, x..y etc.
REF_NS_MEMBER = 74, // Reference for x.ns::y, x..ns::y etc.
REF_NAME = 75, // Reference for @y, @[y] etc.
REF_NS_NAME = 76; // Reference for ns::y, @ns::y@[y] etc.
DEFAULTNAMESPACE = 71, // default xml namespace =
ESCXMLATTR = 72,
ESCXMLTEXT = 73,
REF_MEMBER = 74, // Reference for x.@y, x..y etc.
REF_NS_MEMBER = 75, // Reference for x.ns::y, x..ns::y etc.
REF_NAME = 76, // Reference for @y, @[y] etc.
REF_NS_NAME = 77; // Reference for ns::y, @ns::y@[y] etc.
// End of interpreter bytecodes
public final static int
LAST_BYTECODE_TOKEN = REF_NS_NAME,
TRY = 77,
SEMI = 78, // semicolon
LB = 79, // left and right brackets
RB = 80,
LC = 81, // left and right curlies (braces)
RC = 82,
LP = 83, // left and right parentheses
RP = 84,
COMMA = 85, // comma operator
TRY = 78,
SEMI = 79, // semicolon
LB = 80, // left and right brackets
RB = 81,
LC = 82, // left and right curlies (braces)
RC = 83,
LP = 84, // left and right parentheses
RP = 85,
COMMA = 86, // comma operator
ASSIGN = 86, // simple assignment (=)
ASSIGN_BITOR = 87, // |=
ASSIGN_BITXOR = 88, // ^=
ASSIGN_BITAND = 89, // |=
ASSIGN_LSH = 90, // <<=
ASSIGN_RSH = 91, // >>=
ASSIGN_URSH = 92, // >>>=
ASSIGN_ADD = 93, // +=
ASSIGN_SUB = 94, // -=
ASSIGN_MUL = 95, // *=
ASSIGN_DIV = 96, // /=
ASSIGN_MOD = 97; // %=
ASSIGN = 87, // simple assignment (=)
ASSIGN_BITOR = 88, // |=
ASSIGN_BITXOR = 89, // ^=
ASSIGN_BITAND = 90, // |=
ASSIGN_LSH = 91, // <<=
ASSIGN_RSH = 92, // >>=
ASSIGN_URSH = 93, // >>>=
ASSIGN_ADD = 94, // +=
ASSIGN_SUB = 95, // -=
ASSIGN_MUL = 96, // *=
ASSIGN_DIV = 97, // /=
ASSIGN_MOD = 98; // %=
public final static int
FIRST_ASSIGN = ASSIGN,
LAST_ASSIGN = ASSIGN_MOD,
HOOK = 98, // conditional (?:)
COLON = 99,
OR = 100, // logical or (||)
AND = 101, // logical and (&&)
INC = 102, // increment/decrement (++ --)
DEC = 103,
DOT = 104, // member operator (.)
FUNCTION = 105, // function keyword
EXPORT = 106, // export keyword
IMPORT = 107, // import keyword
IF = 108, // if keyword
ELSE = 109, // else keyword
SWITCH = 110, // switch keyword
CASE = 111, // case keyword
DEFAULT = 112, // default keyword
WHILE = 113, // while keyword
DO = 114, // do keyword
FOR = 115, // for keyword
BREAK = 116, // break keyword
CONTINUE = 117, // continue keyword
VAR = 118, // var keyword
WITH = 119, // with keyword
CATCH = 120, // catch keyword
FINALLY = 121, // finally keyword
VOID = 122, // void keyword
RESERVED = 123, // reserved keywords
HOOK = 99, // conditional (?:)
COLON = 100,
OR = 101, // logical or (||)
AND = 102, // logical and (&&)
INC = 103, // increment/decrement (++ --)
DEC = 104,
DOT = 105, // member operator (.)
FUNCTION = 106, // function keyword
EXPORT = 107, // export keyword
IMPORT = 108, // import keyword
IF = 109, // if keyword
ELSE = 110, // else keyword
SWITCH = 111, // switch keyword
CASE = 112, // case keyword
DEFAULT = 113, // default keyword
WHILE = 114, // while keyword
DO = 115, // do keyword
FOR = 116, // for keyword
BREAK = 117, // break keyword
CONTINUE = 118, // continue keyword
VAR = 119, // var keyword
WITH = 120, // with keyword
CATCH = 121, // catch keyword
FINALLY = 122, // finally keyword
VOID = 123, // void keyword
RESERVED = 124, // reserved keywords
EMPTY = 124,
EMPTY = 125,
/* types used for the parse tree - these never get returned
* by the scanner.
*/
BLOCK = 125, // statement block
LABEL = 126, // label
TARGET = 127,
LOOP = 128,
EXPR_VOID = 129, // expression statement in functions
EXPR_RESULT = 130, // expression statement in scripts
JSR = 131,
SCRIPT = 132, // top-level node for entire script
TYPEOFNAME = 133, // for typeof(simple-name)
USE_STACK = 134,
SETPROP_OP = 135, // x.y op= something
SETELEM_OP = 136, // x[y] op= something
LOCAL_BLOCK = 137,
SET_REF_OP = 138, // *reference op= something
BLOCK = 126, // statement block
LABEL = 127, // label
TARGET = 128,
LOOP = 129,
EXPR_VOID = 130, // expression statement in functions
EXPR_RESULT = 131, // expression statement in scripts
JSR = 132,
SCRIPT = 133, // top-level node for entire script
TYPEOFNAME = 134, // for typeof(simple-name)
USE_STACK = 135,
SETPROP_OP = 136, // x.y op= something
SETELEM_OP = 137, // x[y] op= something
LOCAL_BLOCK = 138,
SET_REF_OP = 139, // *reference op= something
// For XML support:
DOTDOT = 139, // member operator (..)
COLONCOLON = 140, // namespace::name
XML = 141, // XML type
DOTQUERY = 142, // .() -- e.g., x.emps.emp.(name == "terry")
XMLATTR = 143, // @
XMLEND = 144,
DOTDOT = 140, // member operator (..)
COLONCOLON = 141, // namespace::name
XML = 142, // XML type
DOTQUERY = 143, // .() -- e.g., x.emps.emp.(name == "terry")
XMLATTR = 144, // @
XMLEND = 145,
// Optimizer-only-tokens
TO_OBJECT = 145,
TO_DOUBLE = 146,
TO_OBJECT = 146,
TO_DOUBLE = 147,
GET = 147, // JS 1.5 get pseudo keyword
SET = 148, // JS 1.5 set pseudo keyword
CONST = 149,
SETCONST = 150,
SETCONSTVAR = 151,
LAST_TOKEN = 152;
GET = 148, // JS 1.5 get pseudo keyword
SET = 149, // JS 1.5 set pseudo keyword
LET = 150, // JS 1.7 let pseudo keyword
CONST = 151,
SETCONST = 152,
SETCONSTVAR = 153,
LAST_TOKEN = 154;
public static String name(int token)
{
@ -407,6 +409,8 @@ public class Token
case TO_DOUBLE: return "TO_DOUBLE";
case GET: return "GET";
case SET: return "SET";
case LET: return "LET";
case YIELD: return "YIELD";
case CONST: return "CONST";
case SETCONST: return "SETCONST";
}

View File

@ -133,6 +133,7 @@ class TokenStream
Id_function = Token.FUNCTION,
Id_if = Token.IF,
Id_in = Token.IN,
Id_let = Token.LET,
Id_new = Token.NEW,
Id_null = Token.NULL,
Id_return = Token.RETURN,
@ -144,6 +145,7 @@ class TokenStream
Id_void = Token.VOID,
Id_while = Token.WHILE,
Id_with = Token.WITH,
Id_yield = Token.YIELD,
// the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c
Id_abstract = Token.RESERVED,
@ -184,7 +186,7 @@ class TokenStream
int id;
String s = name;
// #generated# Last update: 2001-06-01 17:45:01 CEST
// #generated# Last update: 2007-04-18 13:53:30 PDT
L0: { id = 0; String X = null; int c;
L: switch (s.length()) {
case 2: c=s.charAt(1);
@ -195,6 +197,7 @@ class TokenStream
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 'l': if (s.charAt(2)=='t' && s.charAt(1)=='e') {id=Id_let; 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;
@ -221,7 +224,10 @@ class TokenStream
} 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 'e': c=s.charAt(0);
if (c=='b') { X="break";id=Id_break; }
else if (c=='y') { X="yield";id=Id_yield; }
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);
@ -394,6 +400,13 @@ class TokenStream
// Return the corresponding token if it's a keyword
int result = stringToKeyword(str);
if (result != Token.EOF) {
if ((result == Token.LET || result == Token.YIELD) &&
parser.compilerEnv.getLanguageVersion()
< Context.VERSION_1_7)
{
// LET and YIELD are tokens only in 1.7 and later
result = Token.NAME;
}
if (result != Token.RESERVED) {
return result;
} else if (!parser.compilerEnv.

View File

@ -372,6 +372,10 @@ public class Codegen extends Interpreter
cfw.stopMethod((short)(firstLocal + 1));
}
// TODO(js1.7gen): make name a parameter, generalize to
// work for "call" and for "resumeGenerator".
// The "call" method should call "resumeGenerator" with a "start" operation
// (need to add "args" parameter to "resumeGenerator").
private void generateCallMethod(ClassFileWriter cfw)
{
cfw.startMethod("call",
@ -1271,6 +1275,14 @@ class BodyCodegen
*/
private void generatePrologue()
{
/*
* TODO(js1.7gen):
* For resumeGenerator, generate prologue that looks at the value
* of the operation argument, does some setup, and jumps to the
* appropriate resumption point.
* Can assume inDirectCallFunction = false and hasVarsInRegs = false
* for generators.
*/
if (inDirectCallFunction) {
int directParameterCount = scriptOrFn.getParamCount();
// 0 is reserved for function Object 'this'
@ -2355,6 +2367,60 @@ class BodyCodegen
+")Ljava/lang/Object;");
break;
case Token.YIELD:
/*
* TODO(js1.7gen): Generate code to save the execution state
* of the JVM. This may not be too bad: since generators
* require an activation, all variables are in the activation
* object. Look through the prologue code at the java
* registers that are initialized. Look to see if any of those
* values are altered during execution; if not, you can
* just rely on shared prologue code for the generator.
* Hardest part will be the stack. You'll need to create a
* compile-time data structure that tracks the type of every
* element on the stack. Then when you generate code for the
* yield, you'll generate code to take the contents of the
* stack and save it to an in-memory object (Object[] or pair
* of Object[] double[], I'm not sure which). You'll also
* need the inverse operation of taking an object like that
* and pushing all its saved values onto the stack. It may
* make sense to alter ClassFileWriter to maintain stack
* information, or maintain a parallel data structure in
* Codegen.
*
* Each yield point will need to have a unique integer number
* determined at compile time. When a program encounters a
* yield, the yield number will be saved in the state object.
* The "resumeGenerator" prologue code will need to have a
* code to take the integer and then jump to the appropriate
* yield. You'll need to run the validator with yields in
* various points in control flow to make sure that the
* validator doesn't object to these jumps into the middle of
* various control structures. I think it should be okay since
* the register save code will ensure that you never jump
* into a place with different stack height or uninitialized
* variables.
*
* The biggest unknown area for me is exception handling.
* There are cases of yields in finally clauses that suspend
* a function with an in-flight exception. It may have to be
* that Rhino doesn't generate any finally clauses but instead
* catches all exceptions, processes finally clauses (including
* yields) and rethrows. Then in-flight exceptions will just
* be variables that can be captured. The other question is
* whether the validator will object to jumps in and out of
* try and catch blocks.
*
* Probably don't need analog to Icode_GENERATOR: all setup
* code can be handled in "resumeGenerator" prologue.
*
* Probably do need analog to Icode_GENERATOR_END. Perhaps
* during code generation for returns, can check to see if
* in generator and if so generate code for the analog
* to Icode_GENERATOR_END.
*/
break;
default:
throw new RuntimeException("Unexpected node type "+type);
}

View File

@ -126,6 +126,12 @@ msg.bad.decr =\
msg.bad.incr =\
Invalid increment operand.
msg.bad.yield =\
yield must be in a function.
msg.yield.parenthesized =\
yield expression must be parenthesized.
# NativeGlobal
msg.cant.call.indirect =\
Function "{0}" must be called directly, and not by way of a \
@ -448,7 +454,7 @@ msg.assn.create.strict =\
Assignment to undeclared variable {0}
msg.ref.undefined.prop =\
Referenced to undefined property "{0}"
Referenced to undefined property "{0}"
msg.prop.not.found =\
Property {0} not found.
@ -691,3 +697,14 @@ msg.bad.uri =\
# Number
msg.bad.precision =\
Precision {0} out of range.
# NativeGenerator
msg.send.newborn =\
Attempt to send value to newborn generator
msg.already.exec.gen =\
Already executing generator
# Interpreter
msg.yield.closing =\
Yield from closing generator

View File

@ -167,8 +167,29 @@ js1_7/geniter/regress-347739.js
js1_7/geniter/regress-349012-01.js
js1_7/geniter/regress-349331.js
# Generator functionality not yet implemented:
# "Iterator" constructor
js1_7/geniter/builtin-Iterator-function.js
# array comprehensions
js1_7/geniter/regress-345736.js
# let statements
js1_7/geniter/regress-347593.js
# yield and xml-filtering predicate
js1_7/geniter/regress-352605.js
# destructuring assignment
js1_7/geniter/regress-366941.js
# for (i in <generator>)
js1_7/geniter/regress-350621.js
# JS 1.7 not yet implemented
js1_7
js1_7/block
js1_7/decompilation
js1_7/expressions
js1_7/extensions
js1_7/iterable
js1_7/lexical
js1_7/regexp
js1_7/regress
# JS 1.8 not yet implemented
js1_8

View File

@ -13,11 +13,14 @@ js1_5/Regress/regress-80981.js
js1_5/Regress/regress-89443.js
js1_5/extensions/regress-226507.js
# Needs investigation
#js1_5/Exceptions/regress-257751.js
#js1_5/Regress/regress-111557.js
#js1_5/Regress/regress-155081-2.js
#js1_5/Regress/regress-155081.js
#js1_5/Regress/regress-167328.js
#js1_5/extensions/regress-50447.js
#js1_5/extensions/regress-311161.js
# program too large/complex; could have better error message
js1_5/Regress/regress-111557.js
js1_5/Regress/regress-155081-2.js
js1_5/Regress/regress-155081.js
js1_5/extensions/regress-311161.js
# Missing line number information on error
js1_5/Regress/regress-167328.js
js1_5/extensions/regress-50447.js
js1_5/Exceptions/regress-257751.js

View File

@ -57,7 +57,7 @@ msg.shell.usage =\
Valid options are:\n\
\ -?, -help Displays help messages.\n\
\ -w Enable warnings.\n\
\ -version 100|110|120|130|140|150|160\n\
\ -version 100|110|120|130|140|150|160|170\n\
\ Set a specific language version.\n\
\ -opt [-1|0-9] Set optimization level.\n\
\ -f script-filename Execute script file.\n\
@ -66,7 +66,6 @@ msg.shell.usage =\
\ -strict Enable strict mode warnings.\n\
\ -fatal-warnings Treat warnings as errors.
msg.help =\
\n\
Command Description \n\