From 2c7492501427a4876792cc12492d2185a605d7c4 Mon Sep 17 00:00:00 2001 From: brian Date: Tue, 13 Jul 2004 04:28:31 +0000 Subject: [PATCH] local scope vars indexed by number darcs-hash:20040713042831-24bed-3c9fc5e4a850d016160df3578433df0c340586c2.gz --- src/org/ibex/js/ByteCodes.java | 13 +- src/org/ibex/js/Interpreter.java | 137 ++++++++++---------- src/org/ibex/js/JS.java | 79 ++++++------ src/org/ibex/js/JSArray.java | 2 +- src/org/ibex/js/JSFunction.java | 23 +--- src/org/ibex/js/JSRegexp.java | 2 +- src/org/ibex/js/JSScope.java | 48 ++++++- src/org/ibex/js/Lexer.java | 1 + src/org/ibex/js/Parser.java | 259 +++++++++++++++++++++++++------------- src/org/ibex/js/Test.java | 4 +- src/org/ibex/js/Tokens.java | 3 +- 11 files changed, 346 insertions(+), 225 deletions(-) diff --git a/src/org/ibex/js/ByteCodes.java b/src/org/ibex/js/ByteCodes.java index e8170f1..84c5926 100644 --- a/src/org/ibex/js/ByteCodes.java +++ b/src/org/ibex/js/ByteCodes.java @@ -24,10 +24,11 @@ interface ByteCodes { /** 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) */ @@ -85,10 +86,14 @@ interface ByteCodes { /** 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" }; } diff --git a/src/org/ibex/js/Interpreter.java b/src/org/ibex/js/Interpreter.java index 83ff539..04f6784 100644 --- a/src/org/ibex/js/Interpreter.java +++ b/src/org/ibex/js/Interpreter.java @@ -6,8 +6,6 @@ import java.util.*; /** 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()); } @@ -25,7 +23,7 @@ class Interpreter implements ByteCodes, Tokens { 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); @@ -46,6 +44,7 @@ class Interpreter implements ByteCodes, Tokens { /** 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); @@ -89,16 +88,22 @@ class Interpreter implements ByteCodes, Tokens { 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; @@ -173,7 +178,7 @@ class Interpreter implements ByteCodes, Tokens { 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) { @@ -209,7 +214,44 @@ class Interpreter implements ByteCodes, Tokens { } 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(); @@ -217,24 +259,9 @@ class Interpreter implements ByteCodes, Tokens { 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); @@ -244,15 +271,13 @@ class Interpreter implements ByteCodes, Tokens { 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: @@ -267,26 +292,12 @@ class Interpreter implements ByteCodes, Tokens { 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); @@ -296,14 +307,13 @@ class Interpreter implements ByteCodes, Tokens { 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: { @@ -337,7 +347,7 @@ class Interpreter implements ByteCodes, Tokens { 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 { @@ -382,14 +392,6 @@ class Interpreter implements ByteCodes, Tokens { 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; @@ -528,9 +530,9 @@ class Interpreter implements ByteCodes, Tokens { 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; } @@ -603,16 +605,17 @@ class Interpreter implements ByteCodes, Tokens { 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); } diff --git a/src/org/ibex/js/JS.java b/src/org/ibex/js/JS.java index 2d5c9aa..d602da3 100644 --- a/src/org/ibex/js/JS.java +++ b/src/org/ibex/js/JS.java @@ -8,43 +8,35 @@ import java.util.*; /** 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; @@ -64,29 +56,29 @@ public abstract class JS { } } - 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 { @@ -110,6 +102,7 @@ public abstract class JS { return super.get(key); } } + public static class EmptyEnumeration extends Enumeration { public EmptyEnumeration(Enumeration parent) { super(parent); } protected boolean _hasMoreElements() { return false; } @@ -217,9 +210,6 @@ public abstract class JS { 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); @@ -274,39 +264,43 @@ public abstract class JS { 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; @@ -314,7 +308,8 @@ public abstract class JS { } /** 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; } diff --git a/src/org/ibex/js/JSArray.java b/src/org/ibex/js/JSArray.java index 5afa82a..a9483d0 100644 --- a/src/org/ibex/js/JSArray.java +++ b/src/org/ibex/js/JSArray.java @@ -5,7 +5,7 @@ import org.ibex.util.*; 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(); diff --git a/src/org/ibex/js/JSFunction.java b/src/org/ibex/js/JSFunction.java index 71da162..d50e566 100644 --- a/src/org/ibex/js/JSFunction.java +++ b/src/org/ibex/js/JSFunction.java @@ -5,7 +5,8 @@ import java.io.*; 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 /////////////////////////////////////////////// @@ -32,21 +33,6 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task { 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 @@ -106,7 +92,7 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task { 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(" "); @@ -119,6 +105,9 @@ class JSFunction extends JS implements ByteCodes, Tokens, Task { 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"); } diff --git a/src/org/ibex/js/JSRegexp.java b/src/org/ibex/js/JSRegexp.java index d352ce6..75503ac 100644 --- a/src/org/ibex/js/JSRegexp.java +++ b/src/org/ibex/js/JSRegexp.java @@ -4,7 +4,7 @@ package org.ibex.js; 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; diff --git a/src/org/ibex/js/JSScope.java b/src/org/ibex/js/JSScope.java index 9fbd3e2..9b9a3b1 100644 --- a/src/org/ibex/js/JSScope.java +++ b/src/org/ibex/js/JSScope.java @@ -1,11 +1,51 @@ // 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() { }; @@ -142,6 +182,6 @@ public class JSScope extends JS.O { } return NaN; } - } + }*/ } diff --git a/src/org/ibex/js/Lexer.java b/src/org/ibex/js/Lexer.java index 3012415..42ec2c7 100644 --- a/src/org/ibex/js/Lexer.java +++ b/src/org/ibex/js/Lexer.java @@ -137,6 +137,7 @@ class Lexer implements Tokens { case "implements": return RESERVED; case "instanceof": return RESERVED; case "synchronized": return RESERVED; + case "cascade": return CASCADE; //#end return -1; } diff --git a/src/org/ibex/js/Parser.java b/src/org/ibex/js/Parser.java index dce9a24..6293107 100644 --- a/src/org/ibex/js/Parser.java +++ b/src/org/ibex/js/Parser.java @@ -69,7 +69,7 @@ class Parser extends Lexer implements ByteCodes { // 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 { @@ -78,7 +78,6 @@ class Parser extends Lexer implements ByteCodes { System.out.println(block); } - // Statics //////////////////////////////////////////////////////////// static byte[] precedence = new byte[MAX_TOKEN + 1]; @@ -133,8 +132,68 @@ class Parser extends Lexer implements ByteCodes { 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 code */ private void consume(int code) throws IOException { @@ -177,12 +236,10 @@ class Parser extends Lexer implements ByteCodes { // (.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; } @@ -227,18 +284,23 @@ class Parser extends Lexer implements ByteCodes { 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: { @@ -266,11 +328,25 @@ class Parser extends Lexer implements ByteCodes { 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; @@ -278,27 +354,20 @@ class Parser extends Lexer implements ByteCodes { 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); @@ -307,13 +376,13 @@ class Parser extends Lexer implements ByteCodes { 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); @@ -364,12 +433,12 @@ class Parser extends Lexer implements ByteCodes { * 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]))) @@ -393,52 +462,71 @@ class Parser extends Lexer implements ByteCodes { */ 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); @@ -516,13 +604,13 @@ class Parser extends Lexer implements ByteCodes { 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: { @@ -611,22 +699,19 @@ class Parser extends Lexer implements ByteCodes { 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; } @@ -780,17 +865,12 @@ class Parser extends Lexer implements ByteCodes { } 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)); @@ -863,20 +943,27 @@ class Parser extends Lexer implements ByteCodes { 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)); @@ -884,7 +971,7 @@ class Parser extends Lexer implements ByteCodes { 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 @@ -914,7 +1001,7 @@ class Parser extends Lexer implements ByteCodes { 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; } @@ -938,9 +1025,9 @@ class Parser extends Lexer implements ByteCodes { case LC: { // blocks are statements too pushBackToken(); - b.add(parserLine, NEWSCOPE); + scopePush(b); parseBlock(b, label); - b.add(parserLine, OLDSCOPE); + scopePop(b); break; } diff --git a/src/org/ibex/js/Test.java b/src/org/ibex/js/Test.java index c432a6a..1bf62e1 100644 --- a/src/org/ibex/js/Test.java +++ b/src/org/ibex/js/Test.java @@ -10,9 +10,9 @@ public class Test extends JS { 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(); diff --git a/src/org/ibex/js/Tokens.java b/src/org/ibex/js/Tokens.java index 8970e14..126a10b 100644 --- a/src/org/ibex/js/Tokens.java +++ b/src/org/ibex/js/Tokens.java @@ -96,6 +96,7 @@ interface Tokens { 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; @@ -112,7 +113,7 @@ interface Tokens { "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" }; } -- 1.7.10.4