289 lines
10 KiB
Java
289 lines
10 KiB
Java
/* -*- 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.
|
|
*
|
|
* 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 long serialVersionUID = 1645892441041347273L;
|
|
|
|
private static final Object GENERATOR_TAG = "Generator";
|
|
|
|
static NativeGenerator init(ScriptableObject scope, boolean sealed) {
|
|
// Generator
|
|
// Can't use "NativeGenerator().exportAsJSClass" since we don't want
|
|
// to define "Generator" as a constructor in the top-level scope.
|
|
|
|
NativeGenerator prototype = new NativeGenerator();
|
|
if (scope != null) {
|
|
prototype.setParentScope(scope);
|
|
prototype.setPrototype(getObjectPrototype(scope));
|
|
}
|
|
prototype.activatePrototypeMap(MAX_PROTOTYPE_ID);
|
|
if (sealed) {
|
|
prototype.sealObject();
|
|
}
|
|
|
|
// Need to access Generator prototype when constructing
|
|
// Generator instances, but don't have a generator constructor
|
|
// to use to find the prototype. Use the "associateValue"
|
|
// approach instead.
|
|
if (scope != null) {
|
|
scope.associateValue(GENERATOR_TAG, prototype);
|
|
}
|
|
|
|
return prototype;
|
|
}
|
|
|
|
/**
|
|
* Only for constructing the prototype object.
|
|
*/
|
|
private NativeGenerator() { }
|
|
|
|
public NativeGenerator(Scriptable scope, NativeFunction function,
|
|
Object savedState)
|
|
{
|
|
this.function = function;
|
|
this.savedState = savedState;
|
|
// Set parent and prototype properties. Since we don't have a
|
|
// "Generator" constructor in the top scope, we stash the
|
|
// prototype in the top scope's associated value.
|
|
Scriptable top = ScriptableObject.getTopLevelScope(scope);
|
|
this.setParentScope(top);
|
|
NativeGenerator prototype = (NativeGenerator)
|
|
ScriptableObject.getTopScopeValue(top, GENERATOR_TAG);
|
|
this.setPrototype(prototype);
|
|
}
|
|
|
|
public static final int GENERATOR_SEND = 0,
|
|
GENERATOR_THROW = 1,
|
|
GENERATOR_CLOSE = 2;
|
|
|
|
@Override
|
|
public String getClassName() {
|
|
return "Generator";
|
|
}
|
|
|
|
/**
|
|
* Close the generator if it is still open.
|
|
*/
|
|
@Override
|
|
public void finalize() throws Throwable {
|
|
if (savedState != null) {
|
|
// This is a little tricky since we are most likely running in
|
|
// a different thread. We need to get a Context to run this, and
|
|
// we must call "doTopCall" since this will likely be the outermost
|
|
// JavaScript frame on this thread.
|
|
Context cx = Context.getCurrentContext();
|
|
ContextFactory factory = cx != null ? cx.getFactory()
|
|
: ContextFactory.getGlobal();
|
|
factory.call(new CloseGeneratorAction(this));
|
|
}
|
|
}
|
|
|
|
private static class CloseGeneratorAction implements ContextAction {
|
|
private NativeGenerator generator;
|
|
|
|
CloseGeneratorAction(NativeGenerator generator) {
|
|
this.generator = generator;
|
|
}
|
|
|
|
public Object run(Context cx) {
|
|
Scriptable scope = ScriptableObject.getTopLevelScope(generator);
|
|
Callable closeGenerator = new Callable() {
|
|
public Object call(Context cx, Scriptable scope,
|
|
Scriptable thisObj, Object[] args) {
|
|
return ((NativeGenerator)thisObj).resume(cx, scope,
|
|
GENERATOR_CLOSE, new GeneratorClosedException());
|
|
}
|
|
};
|
|
return ScriptRuntime.doTopCall(closeGenerator, cx, scope,
|
|
generator, null);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void initPrototypeId(int id) {
|
|
String s;
|
|
int arity;
|
|
switch (id) {
|
|
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;
|
|
case Id___iterator__: arity=1; s="__iterator__"; break;
|
|
default: throw new IllegalArgumentException(String.valueOf(id));
|
|
}
|
|
initPrototypeMethod(GENERATOR_TAG, id, s, arity);
|
|
}
|
|
|
|
@Override
|
|
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_close:
|
|
// need to run any pending finally clauses
|
|
return generator.resume(cx, scope, GENERATOR_CLOSE,
|
|
new GeneratorClosedException());
|
|
|
|
case Id_next:
|
|
// arguments to next() are ignored
|
|
generator.firstTime = false;
|
|
return generator.resume(cx, scope, GENERATOR_SEND,
|
|
Undefined.instance);
|
|
|
|
case Id_send: {
|
|
Object arg = args.length > 0 ? args[0] : Undefined.instance;
|
|
if (generator.firstTime && !arg.equals(Undefined.instance)) {
|
|
throw ScriptRuntime.typeError0("msg.send.newborn");
|
|
}
|
|
return generator.resume(cx, scope, GENERATOR_SEND, arg);
|
|
}
|
|
|
|
case Id_throw:
|
|
return generator.resume(cx, scope, GENERATOR_THROW,
|
|
args.length > 0 ? args[0] : Undefined.instance);
|
|
|
|
case Id___iterator__:
|
|
return thisObj;
|
|
|
|
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;
|
|
if (operation == GENERATOR_THROW) {
|
|
thrown = value;
|
|
} else {
|
|
thrown = NativeIterator.getStopIterationObject(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 (GeneratorClosedException e) {
|
|
// On closing a generator in the compile path, the generator
|
|
// throws a special exception. This ensures execution of all pending
|
|
// finalizers and will not get caught by user code.
|
|
return Undefined.instance;
|
|
} 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#
|
|
|
|
@Override
|
|
protected int findPrototypeId(String s) {
|
|
int id;
|
|
// #generated# Last update: 2007-06-14 13:13:03 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==12) { X="__iterator__";id=Id___iterator__; }
|
|
if (X!=null && X!=s && !X.equals(s)) id = 0;
|
|
break L0;
|
|
}
|
|
// #/generated#
|
|
return id;
|
|
}
|
|
|
|
private static final int
|
|
Id_close = 1,
|
|
Id_next = 2,
|
|
Id_send = 3,
|
|
Id_throw = 4,
|
|
Id___iterator__ = 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;
|
|
|
|
public static class GeneratorClosedException extends RuntimeException {
|
|
private static final long serialVersionUID = 2561315658662379681L;
|
|
}
|
|
}
|