/** if given a non-null argument declare its argument in the current scope and push
it to the stack, else, declares the element on the top of the stack and leaves it
there */
- public static final byte DECLARE = -6;
+ //public static final byte DECLARE = -6;
/** push a reference to the current scope onto the stack */
- public static final byte TOPSCOPE = -7;
+ // FIXME: Document this
+ public static final byte GLOBALSCOPE = -7;
/** if given a null literal pop two elements off the stack; push stack[-1].get(stack[top])
else pop one element off the stack, push stack[top].get(literal) */
/** finish a finally block and carry out whatever instruction initiated the finally block */
public static final byte MAKE_GRAMMAR = -25;
+ // FIXME: Document these and NEWSCOPE/OLDSCOPE/TOPSCOPE changes
+ public static final byte SCOPEGET = -26;
+ public static final byte SCOPEPUT = -27;
+
public static final String[] bytecodeToString = new String[] {
- "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "TOPSCOPE",
+ "", "", "LITERAL", "ARRAY", "OBJECT", "NEWFUNCTION", "DECLARE", "GLOBALSCOPE",
"GET", "GET_PRESERVE", "PUT", "JT", "JF", "JMP", "POP", "CALL", "PUSHKEYS",
"SWAP", "NEWSCOPE", "OLDSCOPE", "DUP", "LABEL", "LOOP", "CALLMETHOD",
- "FINALLY_DONE", "MAKE_GRAMMAR"
+ "FINALLY_DONE", "MAKE_GRAMMAR", "SCOPEGET", "SCOPEPUT"
};
}
/** Encapsulates a single JS interpreter (ie call stack) */
class Interpreter implements ByteCodes, Tokens {
- private static final JS CASCADE = JSString.intern("cascade");
-
// Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
static Interpreter current() { return (Interpreter)threadToInterpreter.get(Thread.currentThread()); }
Interpreter(JSFunction f, boolean pauseable, JSArgs args) {
this.f = f;
this.pausecount = pauseable ? 0 : -1;
- this.scope = new JSScope(f.parentScope);
+ this.scope = f.parentScope;
try {
stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null
stack.push(args);
/** this is the only synchronization point we need in order to be threadsafe */
synchronized JS resume() throws JSExn {
if(f == null) throw new RuntimeException("function already finished");
+ if(scope == null) throw new RuntimeException("scope is null");
Thread t = Thread.currentThread();
Interpreter old = (Interpreter)threadToInterpreter.get(t);
threadToInterpreter.put(t, this);
case LITERAL: stack.push((JS)arg); break;
case OBJECT: stack.push(new JS.O()); break;
case ARRAY: stack.push(new JSArray(JS.toInt((JS)arg))); break;
- case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break;
- case TOPSCOPE: stack.push(scope); break;
+ //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break;
case JT: if (JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
case JF: if (!JS.toBoolean(stack.pop())) pc += JS.toInt((JS)arg) - 1; break;
case JMP: pc += JS.toInt((JS)arg) - 1; break;
case POP: stack.pop(); break;
case SWAP: stack.swap(); break;
case DUP: stack.push(stack.peek()); break;
- case NEWSCOPE: scope = new JSScope(scope); break;
- case OLDSCOPE: scope = scope.getParentScope(); break;
+ case NEWSCOPE: {
+ int n = JS.toInt((JS)arg);
+ scope = new JSScope(scope,(n>>>16)&0xffff,(n>>>0)&0xffff);
+ break;
+ }
+ case OLDSCOPE: scope = scope.parent; break;
+ case GLOBALSCOPE: stack.push(scope.getGlobal()); break;
+ case SCOPEGET: stack.push(scope.get((JS)arg)); break;
+ case SCOPEPUT: scope.put((JS)arg,stack.peek()); break;
case ASSERT: if (!JS.toBoolean(stack.pop())) throw je("ibex.assertion.failed"); break;
case BITNOT: stack.push(JS.N(~JS.toLong(stack.pop()))); break;
case BANG: stack.push(JS.B(!JS.toBoolean(stack.pop()))); break;
continue OUTER;
} else if (o instanceof CallMarker) {
boolean didTrapPut = false;
- if (o instanceof TrapMarker) { // handles return component of a read trap
+ if (o instanceof TrapMarker) { // handles return component of a write trap
TrapMarker tm = (TrapMarker) o;
boolean cascade = tm.t.isWriteTrap() && !tm.cascadeHappened && !JS.toBoolean(retval);
if(cascade) {
}
throw new Error("error: RETURN invoked but couldn't find a CallMarker!");
}
-
+
+ case CASCADE: {
+ boolean write = JS.toBoolean((JS)arg);
+ JS val = write ? stack.pop() : null;
+ CallMarker o = stack.findCall();
+ if(!(o instanceof TrapMarker)) throw new JSExn("tried to CASCADE while not in a trap");
+ TrapMarker tm = (TrapMarker) o;
+ JS key = tm.t.key;
+ JS target = tm.t.target;
+ if(tm.t.isWriteTrap() != write) throw new JSExn("tried to do a " + (write?"write":"read") + " cascade in a " + (write?"read":"write") + " trap");
+ Trap t = write ? tm.t.nextWriteTrap() : tm.t.nextReadTrap();
+ // FIXME: Doesn't handle multiple levels of clone's (probably can just make this a while loop)
+ if(t == null && target instanceof JS.Clone) {
+ target = ((JS.Clone)target).clonee;
+ t = target.getTrap(key);
+ if(t != null) t = write ? t.writeTrap() : t.readTrap();
+ }
+ if(write) {
+ tm.cascadeHappened = true;
+ stack.push(val);
+ }
+ if(t != null) {
+ setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnPut));
+ pc--; // we increment later
+ } else {
+ if(write) {
+ if (tm.pauseOnPut) { pc++; return val; }
+ target.put(key,val);
+ } else {
+ JS ret = target.get(key);
+ if (ret == JS.METHOD) ret = new Stub(target, key);
+ stack.push(ret);
+ }
+ if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
+ }
+ break;
+ }
+
case PUT: {
JS val = stack.pop();
JS key = stack.pop();
if (target == null) throw je("tried to put " + JS.debugToString(val) + " to the " + JS.debugToString(key) + " property on the null value");
if (key == null) throw je("tried to assign \"" + JS.debugToString(val) + "\" to the null key");
- Trap t = null;
- TrapMarker tm = null;
- if(target instanceof JSScope && key.jsequals(CASCADE)) {
- CallMarker o = stack.findCall();
- if(o instanceof TrapMarker) {
- tm = (TrapMarker) o;
- target = tm.t.target;
- key = tm.t.key;
- tm.cascadeHappened = true;
- t = tm.t;
- if(t.isReadTrap()) throw new JSExn("can't do a write cascade in a read trap");
- t = t.nextWriteTrap();
- }
- }
- if(tm == null) { // not cascading
- t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : target.getTrap(key);
- if(t != null) t = t.writeTrap();
- }
+ Trap t = target.getTrap(key);
+ if(t != null) t = t.writeTrap();
+
if(t == null && target instanceof JS.Clone) {
target = ((JS.Clone)target).clonee;
t = target.getTrap(key);
stack.push(val);
if(t != null) {
- setupTrap(t,val,new TrapMarker(this,t,val, tm != null && tm.pauseOnPut));
+ setupTrap(t,val,new TrapMarker(this,t,val));
pc--; // we increment later
- break;
} else {
- if (tm != null && tm.pauseOnPut) { pc++; return val; }
target.put(key,val);
if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
- break;
}
+ break;
}
case GET:
stack.push(key);
}
JS ret = null;
- if (key == null) throw je("tried to get the null key from " + target);
- if (target == null) throw je("tried to get property \"" + key + "\" from the null object");
+ if (key == null) throw je("tried to get the null key from " + JS.debugToString(target));
+ if (target == null) throw je("tried to get property \"" + JS.debugToString(key) + "\" from the null object");
+
+ Trap t = target.getTrap(key);
+ if(t != null) t = t.readTrap();
- Trap t = null;
- TrapMarker tm = null;
- if(target instanceof JSScope && key.jsequals(CASCADE)) {
- CallMarker o = stack.findCall();
- if(o instanceof TrapMarker) {
- tm = (TrapMarker) o;
- target = tm.t.target;
- key = tm.t.key;
- t = tm.t;
- if(t.isWriteTrap()) throw new JSExn("can't do a read cascade in a write trap");
- t = t.nextReadTrap();
- }
- }
- if(tm == null) { // not cascading
- t = target instanceof JSScope ? t = ((JSScope)target).top().getTrap(key) : ((JS)target).getTrap(key);
- if(t != null) t = t.readTrap();
- }
if(t == null && target instanceof JS.Clone) {
target = ((JS.Clone)target).clonee;
t = target.getTrap(key);
if(t != null) {
setupTrap(t,null,new TrapMarker(this,t,null));
pc--; // we increment later
- break;
} else {
ret = target.get(key);
if (pausecount > initialPauseCount) { pc++; return null; } // we were paused
if (ret == JS.METHOD) ret = new Stub(target, key);
stack.push(ret);
- break;
}
+ break;
}
case CALL: case CALLMETHOD: {
stack.push(new CallMarker(this));
stack.push(new JSArgs(a0,a1,a2,rest,numArgs,object));
f = (JSFunction)object;
- scope = new JSScope(f.parentScope);
+ scope = f.parentScope;
pc = -1;
break;
} else {
JS js = stack.peek();
// A trap addition/removal
if(!(val instanceof JSFunction)) throw new JSExn("tried to add/remove a non-function trap");
- if(js instanceof JSScope) {
- JSScope s = (JSScope) js;
- while(s.getParentScope() != null) {
- if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
- s = s.getParentScope();
- }
- js = s;
- }
if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
else js.delTrap(key, (JSFunction)val);
break;
void setupTrap(Trap t, JS val, CallMarker cm) throws JSExn {
stack.push(cm);
- stack.push(t.isWriteTrap() ? new JSArgs(val,t.f) : new JSArgs(t.f));
+ stack.push(new TrapArgs(t,val));
f = t.f;
- scope = new TrapScope(t.f.parentScope,t);
+ scope = f.parentScope;
pc = 0;
}
public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
}
- static class TrapScope extends JSScope {
+ static class TrapArgs extends JS {
private Trap t;
- public TrapScope(JSScope parent, Trap t) {
- super(parent); this.t = t;
- }
+ private JS val;
+ public TrapArgs(Trap t, JS val) { this.t = t; this.val = val; }
public JS get(JS key) throws JSExn {
+ if(JS.isInt(key) && JS.toInt(key) == 0) return val;
//#jswitch(key)
case "trapee": return t.target;
case "callee": return t.f;
case "trapname": return t.key;
+ case "length": return t.isWriteTrap() ? ONE : ZERO;
//#end
return super.get(key);
}
/** The minimum set of functionality required for objects which are manipulated by JavaScript */
public abstract class JS {
public static final JS METHOD = new JS() { };
- public final JS unclone() { return _unclone(); }
- public final JS jsclone() throws JSExn { return new Clone(this); }
public JS.Enumeration keys() throws JSExn { throw new JSExn("you can't enumerate the keys of this object (class=" + getClass().getName() +")"); }
public JS get(JS key) throws JSExn { return null; }
public void put(JS key, JS val) throws JSExn { throw new JSExn("" + key + " is read only (class=" + getClass().getName() +")"); }
- public InputStream getInputStream() throws IOException {
- throw new IOException("this object doesn't have a stream associated with it " + getClass().getName() + ")");
- }
-
- public final boolean hasTrap(JS key) { return getTrap(key) != null; }
-
public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
throw new JSExn("method not found (" + JS.debugToString(method) + ")");
}
-
public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
throw new JSExn("you cannot call this object (class=" + this.getClass().getName() +")");
}
+ public InputStream getInputStream() throws IOException {
+ throw new IOException("this object doesn't have a stream associated with it " + getClass().getName() + ")");
+ }
+ public final JS unclone() { return _unclone(); }
+ public final JS jsclone() throws JSExn { return new Clone(this); }
+ public final boolean hasTrap(JS key) { return getTrap(key) != null; }
public final boolean equals(Object o) { return this == o || ((o instanceof JS) && jsequals((JS)o)); }
+ // Discourage people from using toString()
+ public final String toString() { return "JS Object [class=" + getClass().getName() + "]"; }
- public final String toString() {
- // Discourage people from using toString()
- String s = "JS Object [class=" + getClass().getName() + "]";
- String ext = extendedToString();
- return ext == null ? s : s + " " + ext;
- }
-
- // These are only used internally by org.ibex.js
+ // Package private methods
Trap getTrap(JS key) { return null; }
void putTrap(JS key, Trap value) throws JSExn { throw new JSExn("traps cannot be placed on this object (class=" + this.getClass().getName() +")"); }
String coerceToString() throws JSExn { throw new JSExn("can't coerce to a string (class=" + getClass().getName() +")"); }
boolean jsequals(JS o) { return this == o; }
- String extendedToString() { return null; }
-
+ JS _unclone() { return this; }
+
public static class O extends JS implements Cloneable {
private Hash entries;
}
}
- JS _unclone() { return this; }
-
public interface Cloneable { }
-
- public static class Clone extends JS.O {
+
+ public static class Clone extends O {
protected final JS clonee;
- JS _unclone() { return clonee.unclone(); }
- public JS getClonee() { return clonee; }
public Clone(JS clonee) throws JSExn {
if(!(clonee instanceof Cloneable)) throw new JSExn("" + clonee.getClass().getName() + " isn't cloneable");
this.clonee = clonee;
}
- public boolean jsequals(JS o) { return unclone().jsequals(o.unclone()); }
+ JS _unclone() { return clonee.unclone(); }
+ boolean jsequals(JS o) { return clonee.jsequals(o); }
+
public Enumeration keys() throws JSExn { return clonee.keys(); }
- public JS get(JS key) throws JSExn { return clonee.getAndTriggerTraps(key); }
- public void put(JS key, JS val) throws JSExn { clonee.putAndTriggerTraps(key, val); }
- public JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
- return clonee.callMethod(method, a0, a1, a2, rest, nargs);
+ public final JS get(JS key) throws JSExn { return clonee.get(key); }
+ public final void put(JS key, JS val) throws JSExn { clonee.put(key,val); }
+ public final JS callMethod(JS method, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
+ return clonee.callMethod(method,a0,a1,a2,rest,nargs);
}
public JS call(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
return clonee.call(a0, a1, a2, rest, nargs);
}
public InputStream getInputStream() throws IOException { return clonee.getInputStream(); }
+ // FIXME: This shouldn't be necessary (its for Ibex.Blessing)
+ public JS getClonee() { return clonee; }
}
public static abstract class Enumeration extends JS {
return super.get(key);
}
}
+
public static class EmptyEnumeration extends Enumeration {
public EmptyEnumeration(Enumeration parent) { super(parent); }
protected boolean _hasMoreElements() { return false; }
return false;
}
- public static JS newArray() { return new JSArray(); }
- public static JS newRegexp(JS pat, JS flags) throws JSExn { return new JSRegexp(pat,flags); }
-
// Instance Methods ////////////////////////////////////////////////////////////////////
public final static JS NaN = new JSNumber.D(Double.NaN);
private static Enumeration EMPTY_ENUMERATION = new EmptyEnumeration(null);
public static JS fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
- return JSFunction._fromReader(sourceName, firstLine, sourceCode);
+ return Parser.fromReader(sourceName, firstLine, sourceCode);
}
- // HACK: caller can't know if the argument is a JSFunction or not...
- public static JS cloneWithNewParentScope(JS j, JSScope s) {
- return ((JSFunction)j)._cloneWithNewParentScope(s);
+ public static JS cloneWithNewGlobalScope(JS js, JS s) {
+ if(js instanceof JSFunction)
+ return ((JSFunction)js)._cloneWithNewParentScope(new JSScope.Top(s));
+ else
+ return js;
}
// Trap support //////////////////////////////////////////////////////////////////////////////
/** performs a put, triggering traps if present; traps are run in an unpauseable interpreter */
- public void putAndTriggerTraps(JS key, JS value) throws JSExn {
+ public final void putAndTriggerTraps(JS key, JS value) throws JSExn {
Trap t = getTrap(key);
+ if(JS.isString(key) && JS.toString(key).equals("action")) System.err.println("got put to action! + " + t);
if(t == null || (t = t.writeTrap()) == null) put(key,value);
else new Interpreter(t,value,false).resume();
}
/** performs a get, triggering traps if present; traps are run in an unpauseable interpreter */
- public JS getAndTriggerTraps(JS key) throws JSExn {
+ public final JS getAndTriggerTraps(JS key) throws JSExn {
Trap t = getTrap(key);
if (t == null || (t = t.readTrap()) == null) return get(key);
else return new Interpreter(t,null,false).resume();
}
- public JS justTriggerTraps(JS key, JS value) throws JSExn {
+ public final JS justTriggerTraps(JS key, JS value) throws JSExn {
Trap t = getTrap(key);
if(t == null || (t = t.writeTrap()) == null) return value;
else return new Interpreter(t,value,true).resume();
}
/** adds a trap, avoiding duplicates */
- final void addTrap(JS key, JSFunction f) throws JSExn {
+ // FIXME: This shouldn't be public, it is needed for a hack in Template.java
+ public void addTrap(JS key, JSFunction f) throws JSExn {
if (f.numFormalArgs > 1) throw new JSExn("traps must take either one argument (write) or no arguments (read)");
boolean isRead = f.numFormalArgs == 0;
for(Trap t = getTrap(key); t != null; t = t.next) if (t.f == f) return;
}
/** deletes a trap, if present */
- final void delTrap(JS key, JSFunction f) throws JSExn {
+ // FIXME: This shouldn't be public, it is needed for a hack in Template.java
+ public void delTrap(JS key, JSFunction f) throws JSExn {
Trap t = (Trap)getTrap(key);
if (t == null) return;
if (t.f == f) { putTrap(t.target, t.next); return; }
import java.util.*;
/** A JavaScript JSArray */
-class JSArray extends JS.O {
+public class JSArray extends JS.O {
private static final Object NULL = new Object();
private final BalancedTree bt = new BalancedTree();
import org.ibex.util.*;
/** A JavaScript function, compiled into bytecode */
-class JSFunction extends JS implements ByteCodes, Tokens, Task {
+// FIXME: This shouldn't be public, needed for public add/delTrap (which is needed for the Template.java hack)
+public class JSFunction extends JS implements ByteCodes, Tokens, Task {
// Fields and Accessors ///////////////////////////////////////////////
i.resume();
}
- /** parse and compile a function */
- public static JSFunction _fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
- JSFunction ret = new JSFunction(sourceName, firstLine, null);
- if (sourceCode == null) return ret;
- Parser p = new Parser(sourceCode, sourceName, firstLine);
- while(true) {
- int s = ret.size;
- p.parseStatement(ret, null);
- if (s == ret.size) break;
- }
- ret.add(-1, LITERAL, null);
- ret.add(-1, RETURN);
- return ret;
- }
-
public JSFunction _cloneWithNewParentScope(JSScope s) {
JSFunction ret = new JSFunction(sourceName, firstLine, s);
// Reuse the same op, arg, line, and size variables for the new "instance" of the function
sb.append("\n" + sourceName + ": " + firstLine + "\n");
for (int i=0; i < size; i++) {
sb.append(prefix);
- sb.append(i).append(" (").append(line[i]).append(") :");
+ sb.append(i).append(" (").append(line[i]).append("): ");
if (op[i] < 0) sb.append(bytecodeToString[-op[i]]);
else sb.append(codeToString[op[i]]);
sb.append(" ");
sb.append(" finally: ").append(jmps[1] < 0 ? "No finally block" : ""+(i+jmps[1]));
} else if(op[i] == NEWFUNCTION) {
sb.append(((JSFunction) arg[i]).dump(prefix + " "));
+ } else if(op[i] == NEWSCOPE) {
+ int n = ((JSNumber)arg[i]).toInt();
+ sb.append(" base: " + (n>>>16) + " size: " + (n&0xffff));
}
sb.append("\n");
}
import gnu.regexp.*;
/** A JavaScript regular expression object */
-class JSRegexp extends JS {
+public class JSRegexp extends JS {
private boolean global;
private RE re;
private int lastIndex;
// Copyright 2004 Adam Megacz, see the COPYING file for licensing [GPL]
package org.ibex.js;
-// FIXME: should allow parentScope to be a JS, not a JSScope
/** Implementation of a JavaScript Scope */
-public class JSScope extends JS.O {
+class JSScope {
- private JSScope parentScope;
+ private final int base;
+ private final JS[] vars;
+ final JSScope parent;
+
+ public static class Top extends JSScope {
+ private final JS global;
+ public Top(JS global) { super(null,0,0); this.global = global; }
+ JS get(int i) throws JSExn { throw new JSExn("scope index out of range"); }
+ void put(int i, JS o) throws JSExn { throw new JSExn("scope index out of range"); }
+ JS getGlobal() { return global; }
+ };
+
+ // NOTE: We can't just set base to parent.base + parent.vars.length
+ // sometimes we only access part of a parent's scope
+ public JSScope(JSScope parent, int base, int size) {
+ this.parent = parent;
+ this.base = base;
+ this.vars = new JS[size];
+ }
+
+ final JS get(JS i) throws JSExn {
+ if(i==null) throw new NullPointerException();
+ try {
+ return get(JS.toInt(i));
+ } catch(ArrayIndexOutOfBoundsException e) {
+ throw new JSExn("scope index out of range");
+ }
+ }
+ final void put(JS i, JS o) throws JSExn {
+ if(i==null) throw new NullPointerException();
+ try {
+ put(JS.toInt(i),o);
+ } catch(ArrayIndexOutOfBoundsException e) {
+ throw new JSExn("scope index out of range");
+ }
+ }
+ JS get(int i) throws JSExn { return i < base ? parent.get(i) : vars[i-base]; }
+ void put(int i, JS o) throws JSExn { if(i < base) parent.put(i,o); else vars[i-base] = o; }
+
+ JS getGlobal() { return parent.getGlobal(); }
+
+ /*private JSScope parentScope;
private static final JS NULL_PLACEHOLDER = new JS() { };
}
return NaN;
}
- }
+ }*/
}
case "implements": return RESERVED;
case "instanceof": return RESERVED;
case "synchronized": return RESERVED;
+ case "cascade": return CASCADE;
//#end
return -1;
}
// Constructors //////////////////////////////////////////////////////
- public Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
+ private Parser(Reader r, String sourceName, int line) throws IOException { super(r, sourceName, line); }
/** for debugging */
public static void main(String[] s) throws IOException {
System.out.println(block);
}
-
// Statics ////////////////////////////////////////////////////////////
static byte[] precedence = new byte[MAX_TOKEN + 1];
precedence[DOT] = precedence[LB] = precedence[LP] = precedence[INC] = precedence[DEC] = 15;
}
-
+ // Local variable management
+ Vec scopeStack = new Vec();
+ static class ScopeInfo {
+ int base;
+ int end;
+ int newScopeInsn;
+ Hash mapping = new Hash();
+ }
+ Hash globalCache = new Hash();
+ JS scopeKey(String name) {
+ if(globalCache.get(name) != null) return null;
+ for(int i=scopeStack.size()-1;i>=0;i--) {
+ JS key = (JS)((ScopeInfo) scopeStack.elementAt(i)).mapping.get(name);
+ if(key != null) return key;
+ }
+ globalCache.put(name,Boolean.TRUE);
+ return null;
+ }
+ void scopeDeclare(String name) throws IOException {
+ ScopeInfo si = (ScopeInfo) scopeStack.lastElement();
+ if(si.mapping.get(name) != null) throw pe("" + name + " already declared in this scope");
+ si.mapping.put(name,JS.N(si.end++));
+ globalCache.put(name,null);
+ }
+ void scopePush(JSFunction b) {
+ ScopeInfo prev = (ScopeInfo) scopeStack.lastElement();
+ ScopeInfo si = new ScopeInfo();
+ si.base = prev.end;
+ si.end = si.base;
+ si.newScopeInsn = b.size;
+ scopeStack.push(si);
+ b.add(parserLine, NEWSCOPE);
+ }
+ void scopePop(JSFunction b) {
+ ScopeInfo si = (ScopeInfo) scopeStack.pop();
+ b.add(parserLine, OLDSCOPE);
+ b.set(si.newScopeInsn,JS.N((si.base<<16)|((si.end-si.base)<<0)));
+ }
+
+
// Parsing Logic /////////////////////////////////////////////////////////
+
+ /** parse and compile a function */
+ public static JSFunction fromReader(String sourceName, int firstLine, Reader sourceCode) throws IOException {
+ JSFunction ret = new JSFunction(sourceName, firstLine, null);
+ if (sourceCode == null) return ret;
+ Parser p = new Parser(sourceCode, sourceName, firstLine);
+ p.scopeStack.setSize(0);
+ p.scopeStack.push(new ScopeInfo());
+ p.scopePush(ret);
+ while(true) {
+ //int s = ret.size;
+ if(p.peekToken() == -1) break; // FIXME: Check this logic one more time
+ p.parseStatement(ret, null);
+ //if (s == ret.size) break;
+ }
+ p.scopePop(ret);
+ if(p.scopeStack.size() != 1) throw new Error("scopeStack height mismatch");
+ ret.add(-1, LITERAL, null);
+ ret.add(-1, RETURN);
+ return ret;
+ }
/** gets a token and throws an exception if it is not <tt>code</tt> */
private void consume(int code) throws IOException {
// (.foo) syntax
case DOT: {
consume(NAME);
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, LITERAL, JSString.intern(""));
- b.add(parserLine, GET);
- b.add(parserLine, LITERAL, JSString.intern(string));
- b.add(parserLine, GET);
- continueExpr(b, minPrecedence);
+ b.add(parserLine, GLOBALSCOPE);
+ b.add(parserLine, GET, JS.S("",true));
+ b.add(parserLine, LITERAL, JS.S(string,true));
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
case INC: case DEC: { // prefix (not postfix)
startExpr(b, precedence[tok]);
int prev = b.size - 1;
+ boolean sg = b.get(prev) == SCOPEGET;
if (b.get(prev) == GET && b.getArg(prev) != null)
b.set(prev, LITERAL, b.getArg(prev));
else if(b.get(prev) == GET)
b.pop();
- else
+ else if(!sg)
throw pe("prefixed increment/decrement can only be performed on a valid assignment target");
- b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+ if(!sg) b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
b.add(parserLine, LITERAL, JS.N(1));
b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
- b.add(parserLine, PUT, null);
- b.add(parserLine, SWAP, null);
- b.add(parserLine, POP, null);
+ if(sg) {
+ b.add(parserLine, SCOPEPUT, b.getArg(prev));
+ } else {
+ b.add(parserLine, PUT, null);
+ b.add(parserLine, SWAP, null);
+ b.add(parserLine, POP, null);
+ }
break;
}
case BANG: case BITNOT: case TYPEOF: {
break;
}
case NAME: {
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, LITERAL, JSString.intern(string));
- continueExprAfterAssignable(b,minPrecedence);
+ JS varKey = scopeKey(string);
+ if(varKey == null) {
+ b.add(parserLine, GLOBALSCOPE);
+ b.add(parserLine, LITERAL, JSString.intern(string));
+ }
+ continueExprAfterAssignable(b,minPrecedence,varKey);
+ break;
+ }
+ case CASCADE: {
+ if(peekToken() == ASSIGN) {
+ consume(ASSIGN);
+ startExpr(b, precedence[ASSIGN]);
+ b.add(parserLine, CASCADE, JS.T);
+ } else {
+ b.add(parserLine, CASCADE, JS.F);
+ }
break;
}
+
case FUNCTION: {
consume(LP);
int numArgs = 0;
b.add(parserLine, NEWFUNCTION, b2);
// function prelude; arguments array is already on the stack
- b2.add(parserLine, TOPSCOPE);
- b2.add(parserLine, SWAP);
- b2.add(parserLine, DECLARE, JSString.intern("arguments")); // declare arguments (equivalent to 'var arguments;')
- b2.add(parserLine, SWAP); // set this.arguments and leave the value on the stack
- b2.add(parserLine, PUT);
+ scopePush(b2);
+ scopeDeclare("arguments");
+ b2.add(parserLine, SCOPEPUT,scopeKey("arguments"));
while(peekToken() != RP) { // run through the list of argument names
numArgs++;
if (peekToken() == NAME) {
consume(NAME); // a named argument
- JS varName = JSString.intern(string);
b2.add(parserLine, DUP); // dup the args array
b2.add(parserLine, GET, JS.N(numArgs - 1)); // retrieve it from the arguments array
- b2.add(parserLine, TOPSCOPE);
- b2.add(parserLine, SWAP);
- b2.add(parserLine, DECLARE, varName); // declare the name
- b2.add(parserLine, SWAP);
- b2.add(parserLine, PUT);
- b2.add(parserLine, POP); // pop the value
- b2.add(parserLine, POP); // pop the scope
+ scopeDeclare(string);
+ b2.add(parserLine, SCOPEPUT, scopeKey(string));
+ b2.add(parserLine, POP);
}
if (peekToken() == RP) break;
consume(COMMA);
b2.numFormalArgs = numArgs;
b2.add(parserLine, POP); // pop off the arguments array
- b2.add(parserLine, POP); // pop off TOPSCOPE
if(peekToken() != LC)
throw pe("JSFunctions must have a block surrounded by curly brackets");
parseBlock(b2, null); // the function body
-
+
+ scopePop(b2);
b2.add(parserLine, LITERAL, null); // in case we "fall out the bottom", return NULL
b2.add(parserLine, RETURN);
* expression that modifies the assignable. This method always
* decreases the stack depth by exactly one element.
*/
- private void continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+ private void continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
int saveParserLine = parserLine;
- _continueExprAfterAssignable(b,minPrecedence);
+ _continueExprAfterAssignable(b,minPrecedence,varKey);
parserLine = saveParserLine;
}
- private void _continueExprAfterAssignable(JSFunction b,int minPrecedence) throws IOException {
+ private void _continueExprAfterAssignable(JSFunction b,int minPrecedence, JS varKey) throws IOException {
if (b == null) throw new Error("got null b; this should never happen");
int tok = getToken();
if (minPrecedence != -1 && (precedence[tok] < minPrecedence || (precedence[tok] == minPrecedence && !isRightAssociative[tok])))
*/
case ASSIGN_BITOR: case ASSIGN_BITXOR: case ASSIGN_BITAND: case ASSIGN_LSH: case ASSIGN_RSH: case ASSIGN_URSH:
case ASSIGN_MUL: case ASSIGN_DIV: case ASSIGN_MOD: case ASSIGN_ADD: case ASSIGN_SUB: case ADD_TRAP: case DEL_TRAP: {
- if (tok != ADD_TRAP && tok != DEL_TRAP) b.add(parserLine, GET_PRESERVE);
+ if (tok != ADD_TRAP && tok != DEL_TRAP)
+ b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
startExpr(b, precedence[tok]);
if (tok != ADD_TRAP && tok != DEL_TRAP) {
// tok-1 is always s/^ASSIGN_// (0 is BITOR, 1 is ASSIGN_BITOR, etc)
b.add(parserLine, tok - 1, tok-1==ADD ? JS.N(2) : null);
- b.add(parserLine, PUT);
- b.add(parserLine, SWAP);
- b.add(parserLine, POP);
+ if(varKey == null) {
+ b.add(parserLine, PUT);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
} else {
+ if(varKey != null) throw pe("cannot place traps on local variables");
b.add(parserLine, tok);
}
break;
}
case INC: case DEC: { // postfix
- b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
- b.add(parserLine, LITERAL, JS.N(1));
- b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
- b.add(parserLine, PUT, null);
- b.add(parserLine, SWAP, null);
- b.add(parserLine, POP, null);
- b.add(parserLine, LITERAL, JS.N(1));
- b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix
+ if(varKey == null) {
+ b.add(parserLine, GET_PRESERVE, Boolean.TRUE);
+ b.add(parserLine, LITERAL, JS.N(1));
+ b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+ b.add(parserLine, PUT, null);
+ b.add(parserLine, SWAP, null);
+ b.add(parserLine, POP, null);
+ b.add(parserLine, LITERAL, JS.N(1));
+ b.add(parserLine, tok == INC ? SUB : ADD, JS.N(2)); // undo what we just did, since this is postfix
+ } else {
+ b.add(parserLine, SCOPEGET, varKey);
+ b.add(parserLine, DUP);
+ b.add(parserLine, LITERAL, JS.ONE);
+ b.add(parserLine, tok == INC ? ADD : SUB, JS.N(2));
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
break;
}
case ASSIGN: {
startExpr(b, precedence[tok]);
- b.add(parserLine, PUT);
- b.add(parserLine, SWAP);
- b.add(parserLine, POP);
+ if(varKey == null) {
+ b.add(parserLine, PUT);
+ b.add(parserLine, SWAP);
+ b.add(parserLine, POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
break;
}
case LP: {
-
// Method calls are implemented by doing a GET_PRESERVE
// first. If the object supports method calls, it will
// return JS.METHOD
- b.add(parserLine, GET_PRESERVE);
+ b.add(parserLine, varKey == null ? GET_PRESERVE : SCOPEGET, varKey);
int n = parseArgs(b);
- b.add(parserLine, CALLMETHOD, JS.N(n));
+ b.add(parserLine, varKey == null ? CALLMETHOD : CALL, JS.N(n));
break;
}
default: {
pushBackToken();
- if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
+ if(varKey != null)
+ b.add(parserLine, SCOPEGET, varKey);
+ else if(b.get(b.size-1) == LITERAL && b.getArg(b.size-1) != null)
b.set(b.size-1,GET,b.getArg(b.size-1));
else
b.add(parserLine, GET);
consume(NAME);
}
b.add(parserLine, LITERAL, JSString.intern(string));
- continueExprAfterAssignable(b,minPrecedence);
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
case LB: { // subscripting (not array constructor)
startExpr(b, -1);
consume(RB);
- continueExprAfterAssignable(b,minPrecedence);
+ continueExprAfterAssignable(b,minPrecedence,null);
break;
}
case HOOK: {
break;
}
case VAR: {
- b.add(parserLine, TOPSCOPE); // push the current scope
while(true) {
consume(NAME);
- b.add(parserLine, DECLARE, JSString.intern(string)); // declare it
+ String var = string;
+ scopeDeclare(var);
if (peekToken() == ASSIGN) { // if there is an '=' after the variable name
consume(ASSIGN);
startExpr(b, NO_COMMA);
- b.add(parserLine, PUT); // assign it
+ b.add(parserLine, SCOPEPUT, scopeKey(var)); // assign it
b.add(parserLine, POP); // clean the stack
- } else {
- b.add(parserLine, POP); // pop the string pushed by declare
- }
+ }
if (peekToken() != COMMA) break;
consume(COMMA);
}
- b.add(parserLine, POP); // pop off the topscope
if ((mostRecentlyReadToken != RC || peekToken() == SEMI) && peekToken() != -1 && mostRecentlyReadToken != SEMI) consume(SEMI);
break;
}
}
consume(RP);
// the exception is on top of the stack; put it to the chosen name
- b.add(parserLine, NEWSCOPE);
- b.add(parserLine, TOPSCOPE);
- b.add(parserLine, SWAP);
- b.add(parserLine, LITERAL,JSString.intern(exceptionVar));
- b.add(parserLine, DECLARE);
- b.add(parserLine, SWAP);
- b.add(parserLine, PUT);
- b.add(parserLine, POP);
+ scopePush(b);
+ scopeDeclare(exceptionVar);
+ b.add(parserLine, SCOPEPUT, scopeKey(exceptionVar));
b.add(parserLine, POP);
parseBlock(b, null);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
b.add(parserLine, JMP);
catchEnds.addElement(new Integer(b.size-1));
b.add(parserLine,DUP);
b.add(parserLine,GET,JS.S("nextElement"));
- b.add(parserLine, NEWSCOPE);
+ scopePush(b);
- b.add(parserLine,TOPSCOPE);
- b.add(parserLine,SWAP);
- b.add(parserLine, hadVar ? DECLARE : LITERAL, JSString.intern(varName));
- b.add(parserLine,SWAP);
- b.add(parserLine,PUT);
- b.add(parserLine,POP);
- b.add(parserLine,POP);
- b.add(parserLine,SWAP);
+ if(hadVar) scopeDeclare(varName);
+ JS varKey = scopeKey(varName);
+
+ if(varKey == null) {
+ b.add(parserLine,GLOBALSCOPE);
+ b.add(parserLine,SWAP);
+ b.add(parserLine, LITERAL, JSString.intern(varName));
+ b.add(parserLine,SWAP);
+ b.add(parserLine,PUT);
+ b.add(parserLine,POP);
+ } else {
+ b.add(parserLine, SCOPEPUT, varKey);
+ }
+ b.add(parserLine,POP); // pop the put'ed value
+ b.add(parserLine,SWAP); // put CallMarker back into place
parseStatement(b, null);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
b.add(parserLine, CONTINUE);
// jump here on break
b.set(size, JS.N(b.size - size));
b.add(parserLine, POP);
} else {
if (hadVar) pushBackToken(VAR, null); // yeah, this actually matters
- b.add(parserLine, NEWSCOPE); // grab a fresh scope
+ scopePush(b); // grab a fresh scope
parseStatement(b, null); // initializer
JSFunction e2 = // we need to put the incrementor before the test
b.add(parserLine, CONTINUE); // if we fall out the bottom, CONTINUE
b.set(size2 - 1, JS.N(b.size - size2 + 1)); // end of the loop
- b.add(parserLine, OLDSCOPE); // get our scope back
+ scopePop(b); // get our scope back
}
break;
}
case LC: { // blocks are statements too
pushBackToken();
- b.add(parserLine, NEWSCOPE);
+ scopePush(b);
parseBlock(b, label);
- b.add(parserLine, OLDSCOPE);
+ scopePop(b);
break;
}
if(args.length == 0) { System.err.println("Usage Test filename"); System.exit(1); }
JS f = JS.fromReader(args[0],1,new FileReader(args[0]));
System.out.println(((JSFunction)f).dump());
- JSScope s = new JSScope(new JSScope.Global());
+ JS s = new JS.O();
s.put(JS.S("sys"),new Test());
- f = JS.cloneWithNewParentScope(f,s);
+ f = JS.cloneWithNewGlobalScope(f,s);
//JS ret = f.call(null,null,null,null,0);
Interpreter i = new Interpreter((JSFunction)f, true, new Interpreter.JSArgs(f));
JS ret = i.resume();
public static final int GRAMMAR = 78; // the grammar-definition operator (::=)
public static final int ADD_TRAP = 79; // the add-trap operator (++=)
public static final int DEL_TRAP = 80; // the del-trap operator (--=)
+ public static final int CASCADE = 81; // cascade expression - arg==true for write cascade
public static final int MAX_TOKEN = DEL_TRAP;
"HOOK", "COLON", "INC", "DEC", "DOT", "FUNCTION", "IF",
"ELSE", "SWITCH", "CASE", "DEFAULT", "WHILE", "DO", "FOR",
"VAR", "WITH", "CATCH", "FINALLY", "RESERVED", "GRAMMAR",
- "ADD_TRAP", "DEL_TRAP"
+ "ADD_TRAP", "DEL_TRAP", "CASCADE"
};
}