Revision: 4763 http://sourceforge.net/p/vexi/code/4763 Author: mkpg2 Date: 2015-02-16 01:01:31 +0000 (Mon, 16 Feb 2015) Log Message: ----------- Minor refactor. - removed duplicated code for handling nested interpreters - split out jpp code from Intepreter.java (incovenient for development).
Modified Paths: -------------- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Scope.jpp Added Paths: ----------- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Interpreter.java branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgs.jpp branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgsTrap.jpp branches/vexi3/org.vexi-library.js/src/poke/java/org/ibex/js/JSUX.java Removed Paths: ------------- branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp Copied: branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Interpreter.java (from rev 4762, branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp) =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Interpreter.java (rev 0) +++ branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Interpreter.java 2015-02-16 01:01:31 UTC (rev 4763) @@ -0,0 +1,1036 @@ +// Copyright 2000-2008 the Contributors, as shown in the revision logs. +// Licensed under the Apache Software License 2.0 ("the License"). +// You may not use this file except in compliance with the License. + + +package org.ibex.js; + +import org.ibex.js.parse.*; + +/*@PAGE(concept=Special Variables) + * + * <h3>General Variables</h3> + * <p>The following special variables are accessible in general template script.</p> + * <dl> + * <dt><code>thisbox</code></dt> + * <dd><p>A reference to the Box defined by the XML node containing it. It can also be thought + * of as a Box property.</p></dd> + * <dt><code>thisobj</code></dt> + * <dd><p>A reference to the Object/Array/etc defined by the XML node containing it.</p></dd> + * <dt><code>static</code></dt> + * <dd><p>The static context of the template in which it is called.</p></dd> + * <dt><code>vexi</code></dt> + * <dd><p>The immutable global vexi object.</p></dd> + * </dl> + * + * <h3>Function Variables</h3> + * <p>The following special variables are accessible in the body of a function.</p> + * <dl> + * <dt><code>arguments</code></dt> + * <dd><p>An immutable array of the arguments passed into a function. Its length is detirmined + * when the function is called and not by the method signature. </p></dd> + * <dt><code>callee</code></dt> + * <dd><p>The current function being executed</p></dd> + * <dt><code>this</code></dt> + * <dd> + * <p><code>this</code><i> is a keyword</i></p> + * <p>The context of the execution. When a function is being accessed as the property of an + * object the context is the object. For example in <code>a.f()</code> in the body of + * <code>f</code> this refers to <code>a</code>.</p> + * + * <p>If a function is executed as a var then <code>this==null</code>.</p> + * + * <p>In top level code the value of <code>this</code> may be assigned to a relevant + * context.</p> + * </dd> + * </dl> + * + * <h3>Trap Variables</h3> + * <p>The following special variables are accessible in the body of a trap.</p> + * <dl> + * <dt><code>cascade</code></dt> + * <dd> + * <p><code>cascade</code><i> is a keyword</i></p> + * <p>In a read trap, read from cascade to get the value from traps lower in the read trap + * chain or, if there are no further traps, the object property the trap is placed upon. + * (Reading directly from the property will invoke the read trap chain again, possibly causing + * an infinite loop.)</p> + * <p>In a write trap, putting to cascade will caused lower traps in the write trap chain to be + * executed or, if there are no further traps, the value passed to cascade is put to the object + * property the trap is placed upon. (Writing directly to the property will invoke the write + * trap chain again, possibly causing an infinite loop.)</p> + * </dd> + * <dt><code>trapname</code></dt> + * <dd><p>The name of the property upon which a trap is been placed.</p></dd> + * <dt><code>trapee</code></dt> + * <dd><p>The object upon which a trap is been placed. <i>Synonym for this</i></p></dd> + * <dt><code>callee</code></dt> + * <dd><p>The trap function - useful for anonymous removal of traps.</p></dd> + * </dl> + * */ +/** Encapsulates a single JS interpreter (ie call stack) */ +public class Interpreter implements ByteCodes, Tokens, Constants { + + + // FIXME - use Thread.cascadedTo==NULL instead + public final static JSNumber CASCADE_PREVENTED = new JSNumber.I(1); + + // Instance members and methods ////////////////////////////////////////////////////////////////////// + + // FIXME - should not be public but devl core uses them + int pausecount; ///< the number of times pause() has been invoked; -1 indicates unpauseable + public Function f = null; ///< the currently-executing JSFunction + public Scope scope; ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE) + final public Stack stack = new Stack(); ///< the object stack + public int pc = 0; ///< the program counter + + final Thread thread; + final Interpreter old; + + public Interpreter(Thread thread){ + this.thread = thread; + this.old = thread.currentInterpreter; + thread.currentInterpreter = this; + } + Interpreter(Thread thread, JSFunction f, boolean pauseable, JS[] args) { + this(thread); + this.f = f.f; + this.pausecount = pauseable ? 0 : -1; + this.scope = f.parentScope; + try { + stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null + stack.push(new JSArgs(args, f, null)); // FIXME: temprorary bug fix + } catch(JSExn e) { + throw new Error("should never happen"); + } + } + + Interpreter(Thread thread, JS.Trap t, JS val, boolean pauseOnCascade, JS trapname) { + this(thread); + this.pausecount = -1; + try { + setupTrap(t,val,new TrapMarker(null,t,val,pauseOnCascade),trapname); + } catch(JSExn e) { + throw new Error("should never happen"); + } + } + + private boolean get = false; + // FIXME: split this stuff out into a Script instance control object + // so it's possible to make JS either single or multi threaded. + /** this is the only synchronization point we need in order to be threadsafe */ + public synchronized Object run(Object o) throws JSExn { + if (f == null) throw new RuntimeException("function already finished"); + if (scope == null) throw new RuntimeException("scope is null"); + + if( o instanceof JSExn){ + // Fill in empty exceptions. Exceptions coming created in background + // calls don't have their stack traces filled in. + ((JSExn)o).fillIfEmpty(this); + // Throw excpetion - move interpreter to catch block + catchException((JSExn)o); + }else if (get) + stack.push(o); + + return run(); + } + + public void pause(String forwhat) throws JSExn { + if (pausecount == -1 || f == null) throw new JSExn("Cannot "+forwhat+" in foreground thread"); + pausecount++; + switch(f.op[pc]) { + case Tokens.RETURN: case ByteCodes.PUT: get = false; break; + case ByteCodes.GET: case ByteCodes.GET_PRESERVE: case ByteCodes.CALLMETHOD: case ByteCodes.CALL: get = true; break; + default: throw new Error("paused on unexpected bytecode: " + f.op[pc]); + } + } + + int getLine() { + return f == null || pc < 0 || pc >= f.size ? -1 : f.line[pc]; + } + + String getSourceName() { + return f == null ? null : f.sourceName; + } + String getWhere(){ return getSourceName() + ":" + getLine(); } + + private JSExn je(String s) { return new JSExn(getWhere() + " " + s); } + + + // REMARK - encapsulate loop jump operations here, so that we can test + // interuption, but only when there is a chance we're in an infinite loop. + private void jump(int delta_pc) throws InterruptedException{ + jumpAbs(pc+delta_pc); + } + private void jumpAbs(int pc) throws InterruptedException{ + this.pc = pc; + if(java.lang.Thread.interrupted()) throw new InterruptedException(); + } + + private JS run() throws JSExn { + // if pausecount changes after a get/put/call, we know we've been paused + if(pausecount>0) + pausecount --; + + OUTER: for(;; pc++) { + try { + if(Instr.interpreter!=null) Instr.interpreter.handle(this); + + int op = f.op[pc]; + Object arg = f.arg[pc]; + if(op == FINALLY_DONE) { + FinallyData fd = (FinallyData) stack.pop(); + if(fd == null) continue OUTER; // NOP + if(fd.exn != null) throw fd.exn; + op = fd.op; + arg = fd.arg; + } + + // DEBUG + // DevUtil.debugStack(this, op, arg); + + switch(op) { + case LITERAL: stack.push(arg); break; + // GUT ought to fetch constructors + case OBJECT: stack.push(new JS.Obj()); break; + case ARRAY: stack.push(new JSArray(JSU.toInt((JS)arg))); break; + //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; + case JT: if (JSU.isTruthy((JS)stack.pop())) jump(JSU.toInt((JS)arg) - 1); break; + case JF: if (!JSU.isTruthy((JS)stack.pop())) jump(JSU.toInt((JS)arg) - 1); break; + case JMP: jump(JSU.toInt((JS)arg) - 1); break; + case POP: stack.pop(); break; + case SWAP: stack.swap(); break; + case DUP: + if(arg==null) stack.push(stack.peek()); + else stack.push(stack.peek(JSU.toInt((JS)arg))); + break; + case NEWSCOPE: { + int n = JSU.toInt((JS)arg); + scope = new Scope(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: { + // FIXME: HACK: share this around more and find the callee. + Object val = stack.peek(); + if (val != null && val instanceof JS[]) val = new JSArgs((JS[])val, null, null); + scope.put((JS)arg, (JS)val); break; + } + case ASSERT: if (!JSU.isTruthy((JS)stack.pop())) throw je("ibex.assertion.failed"); break; + case BITNOT: stack.push(JSU.N(~JSU.toLong((JS)stack.pop()))); break; + case BANG: stack.push(JSU.B(!JSU.isTruthy((JS)stack.pop()))); break; + case NEWFUNCTION: stack.push(JSU.cloneWithNewParentScope((Function)arg, scope)); break; + case LABEL: break; + + case TYPEOF: { + Object o = stack.pop(); + if (o == null) stack.push(SC_null); + else stack.push(((JS)o).type()); + break; + } + + case INSTANCEOF: { + JS c = (JS)stack.pop(); + JS o = (JS)stack.pop(); + if(c==null) throw new JSExn("Cannot instanceof null"); + stack.push(JSU.B(o==null?false:o.instanceOf(c))); + break; + } + + case PUSHKEYS: { + JS o = (JS)stack.peek(); + stack.push(o == null ? null : o.keys()); + break; + } + + case LOOP: + case SWITCH: + //stack.push(new LoopMarker(pc, (String)(pc > 0 && f.op[pc - 1] == LABEL ? f.arg[pc - 1] : null), scope)); + //stack.push(JSU.T); + stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope, op == LOOP)); + if(op == LOOP) stack.push(JSU.T); + break; + + case BREAK: + case CONTINUE: + while(!stack.empty()) { + Object o = stack.pop(); + if (o instanceof CallMarker) throw je("Tried to '"+(op==BREAK?"break":"continue")+"' when not within a loop"+(op==BREAK?"/switch":"")); + if (o instanceof TryMarker) { + if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going + stack.push(new FinallyData(op, arg)); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc - 1; + continue OUTER; + } + if (o instanceof LoopMarker) { + //if (arg == null || arg.equals(((LoopMarker)o).label)) { + //int loopInstructionLocation = ((LoopMarker)o).location; + LoopMarker lm = (LoopMarker) o; + if(op == CONTINUE && !lm.canContinue) continue; + if (arg == null || arg.equals(lm.label)) { + int loopInstructionLocation = lm.location; + int endOfLoop = JSU.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation; + scope = lm.scope; + if (op == CONTINUE) { stack.push(o); stack.push(JSU.F); } + if(op == BREAK) pc = endOfLoop - 1; + else jumpAbs(loopInstructionLocation); + continue OUTER; + } + } + } + throw new Error((op==BREAK?"BREAK":"CONTINUE")+" invoked but couldn't find LoopMarker at " + + getSourceName() + ":" + getLine()); + + case TRY: { + int[] jmps = (int[]) arg; + // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is + // each can be < 0 if the specified block does not exist + stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], this)); + break; + } + + case RETURN: { + JS retval = (JS)stack.pop(); + while(!stack.empty()) { + Object o = stack.pop(); + if (o instanceof TryMarker) { + if(((TryMarker)o).finallyLoc < 0) continue; + stack.push(retval); + stack.push(new FinallyData(RETURN)); + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc - 1; + continue OUTER; + } else if (o instanceof CallMarker) { + boolean isWriteTrap = false; + if (o instanceof TrapMarker) { // handles return component of a write trap + TrapMarker tm = (TrapMarker) o; + isWriteTrap = tm.t.isWriteTrap(); + if (isWriteTrap) { + // REMOVED automatic cascading(FOOTNOTE:1) + thread.faction.cascadedTo = null; + retval = CASCADE_PREVENTED; + } + } + CallMarker cm = (CallMarker) o; + scope = cm.scope; + pc = cm.pc - 1; + f = cm.f; + if(!isWriteTrap) + stack.push(retval); + if (f == null) return retval; + continue OUTER; + } + } + throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); + } + + case CASCADE: { + boolean write = JSU.T==arg; + JS val = write ? (JS)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"); + JS.Trap t = write ? tm.t.nextWrite() : tm.t.nextRead(); + while (t == null && target instanceof JS.Clone) { + target = ((JS.Clone)target).clonee; + t = target.getTrap(key); + if (t != null) t = write ? t.findWrite() : t.findRead(); + } + if(write) { + stack.push(val); + } + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnCascade), tm.trapargs.trapname); + pc--; // we increment later + } else { + thread.faction.cascadedTo = tm.trapargs.trapname; + if(write) { + if (tm.pauseOnCascade) { pc++; return val; } + target.put(key,val); + } else { + if (tm.pauseOnCascade) { + // FIXME should move this to setup of interpreter + // we need it true for when we restart the interpreter + get = true; + pc++; return null; } + else{ + JS ret = target.get(key); + if (ret != null && ret == JS.METHOD) ret = new Stub(target, key); + stack.push(ret); + } + } + if (pausecount > 0) { pc++; return null; } // we were paused + } + break; + } + + case PUT: { + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS target = (JS)stack.peek(); + if (target == null) throw je("Tried to put " + JSU.toString(val) + " to the " + JSU.toString(key) + " property on the null value"); + if (key == null) throw je("Tried to assign \"" + JSU.toString(val) + "\" to the null key"); + + JS.Trap t = target.getTrap(key); + if(t != null) t = t.findWrite(); + + stack.push(val); + + if(t != null) { + setupTrap(t,val,new TrapMarker(this,t,val,false),null); + pc--; // we increment later + } else { + target.put(key,val); + if (pausecount > 0) { pc++; return null; } // we were paused + } + break; + } + + case GET: + case GET_PRESERVE: { + JS target, key; + if (op == GET) { + key = arg == null ? (JS)stack.pop() : (JS)arg; + target = (JS)stack.pop(); + } else { + if(arg==null){ + key = (JS)stack.pop(); + target = (JS)stack.peek(); + stack.push(key); + }else{ + key = (JS)arg; + target = (JS)stack.peek(); + } + } + JS ret = null; + if (key == null) throw je("Tried to get the null key from " + JSU.toString(target)); + if (target == null) throw je("Tried to get property \"" + JSU.toString(key) + "\" from the null object"); + + JS.Trap t = target.getTrap(key); + if (t != null) t = t.findRead(); + + if(t != null) { + JS f = t.function(); + if(f instanceof JSFunction){ + setupTrap(t,null,new TrapMarker(this,t,null,false),null); + pc--; // we increment later + break; + } + // Trap was an override. Simply return the value. + ret = f; + } + if(ret==null) ret = target.get(key); + if (pausecount > 0) { pc++; return null; } // we were paused + + if (ret != null && ret == JS.METHOD) ret = new Stub(target, key); + stack.push(ret); + break; + } + + case NEW: { + JS[] jsargs = readArgs(arg); + JS constructor = (JS) stack.pop(); + if (constructor == null) + throw new JSExn("null is not a constructor"); + stack.push(constructor.new_(jsargs)); + break; + } + + case CALL: case CALLMETHOD: { + JS[] jsargs = readArgs(arg); + + JS ret = null; + JS callee = (JS) stack.pop(); + JS object = null; + if (op == CALLMETHOD) { + if (callee == null) { + JS method = (JS) stack.pop(); + object = (JS) stack.pop(); + if (object instanceof JSPrimitive) + throw new JSExn("Method '" + + JSU.toString(method) + + "' not defined for " + object.type()); + throw new JSExn("Function '" + JSU.toString(method) + + "' not found in " + JSU.toString(object)); + } else { + stack.pop(); + object = (JS) stack.pop(); + } + } + if (callee == null) + throw new JSExn("Tried to call null object"); + + switch (callee.callType()) { + case JS.CALLTYPE_FUNCTION: { + stack.push(new CallMarker(this)); + JSFunction jsfunc = (JSFunction) callee; + f = jsfunc.f; + stack.push(new JSArgs(jsargs, jsfunc, object)); + scope = jsfunc.parentScope; + pc = -1; + break; + } + case JS.CALLTYPE_APPLY: + JS[] args = JSU.checkApply(jsargs); + JS this_ = jsargs.length>0?jsargs[0]:null; + if(callee instanceof JSFunction.Apply){ + stack.push(new CallMarker(this)); + JSFunction jsfunc = (JSFunction) ((JSFunction.Apply) callee).f; + f = jsfunc.f; + stack.push(new JSArgs(args, jsfunc, this_)); + scope = jsfunc.parentScope; + pc = -1; + break; + } + object = this_; + jsargs = args; + // fallthrough + case JS.CALLTYPE_METHOD: + ret = callee.apply(object,jsargs); + if (pausecount > 0) { + pc++; + return null; + } + stack.push(ret); + break; + } + break; + } + + case THROW:{ + JS thrown = (JS)stack.pop(); + // If already an exception then we recover the JSExn and rethrow it + if(thrown instanceof JSExn.Obj){ + JSExn je = ((JSExn.Obj)thrown).asJSExn(); + je.fillIfEmpty(this); + throw je; + }else + throw new JSExn(thrown, null, null, this); + } + + case ADD_TRAP: case DEL_TRAP: { + // A trap addition/removal + JS val = (JS)stack.pop(); + JS key = (JS)stack.pop(); + JS js = (JS)stack.peek(); + + // some checks to make sure the assignment is valid + // TODO: check val is a function + if (key == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") + + " a trap function using a null key on a box or object"); + if (val == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") + + " a null value as a trap function to property '" + key.coerceToString() + "'"); + if (js == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") + + " a trap to property '" + key.coerceToString() + "' on a null object "); + + if(op == ADD_TRAP) js.addTrap(key, val); + else js.delTrap(key, val); + break; + } + + case ADD: { + int count = ((JSNumber)arg).toInt32(); + if(count < 2) throw new Error("this should never happen"); + if(count == 2) { + // common case + JS right = (JS)stack.pop(); + JS left = (JS)stack.pop(); + JS ret; + if(left instanceof JSString || right instanceof JSString) + ret = JSU.S(JSU.toString(left).concat(JSU.toString(right))); + else { + ret = add(left, right); + } + stack.push(ret); + } else { + JS[] args = new JS[count]; + while(--count >= 0) args[count] = (JS)stack.pop(); + if(args[0] instanceof JSString) { + StringBuffer sb = new StringBuffer(64); + for(int i=0;i<args.length;i++) sb.append(JSU.toString(args[i])); + stack.push(JSU.S(sb.toString())); + } else { + int numStrings = 0; + for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++; + if(numStrings == 0) { + double d = 0.0; + for(int i=0;i<args.length;i++) d += JSU.toDouble(args[i]); + stack.push(JSU.N(d)); + } else { + int i=0; + StringBuffer sb = new StringBuffer(64); + if(!(args[0] instanceof JSString || args[1] instanceof JSString)) { + double d=0.0; + do { + d += JSU.toDouble(args[i++]); + } while(!(args[i] instanceof JSString)); + sb.append(JSU.toString(JSU.N(d))); + } + while(i < args.length) sb.append(JSU.toString(args[i++])); + stack.push(JSU.S(sb.toString())); + } + } + } + break; + } + + default: { + JS right = (JS)stack.pop(); + JS left = (JS)stack.pop(); + switch(op) { + + case BITOR: stack.push(JSU.N(JSU.toLong(left) | JSU.toLong(right))); break; + case BITXOR: stack.push(JSU.N(JSU.toLong(left) ^ JSU.toLong(right))); break; + case BITAND: stack.push(JSU.N(JSU.toLong(left) & JSU.toLong(right))); break; + + case SUB: stack.push(subtract(left, right)); break; + case MUL: stack.push(multiply(left, right)); break; + case DIV: stack.push(divide(left,right)); break; + case MOD: stack.push(modulo(left, right)); break; + + case LSH: stack.push(JSU.N(JSU.toLong(left) << JSU.toLong(right))); break; + case RSH: stack.push(JSU.N(JSU.toLong(left) >> JSU.toLong(right))); break; + case URSH: stack.push(JSU.N(JSU.toLong(left) >>> JSU.toLong(right))); break; + + case LT: { + if(left instanceof JSString && right instanceof JSString) + stack.push(JSU.B(JSU.toString(left).compareTo(JSU.toString(right)) < 0)); + else + stack.push(JSU.B(JSU.toDouble(left) < JSU.toDouble(right))); + break; + } + case LE: { + if(left instanceof JSString && right instanceof JSString) + stack.push(JSU.B(JSU.toString(left).compareTo(JSU.toString(right)) <= 0)); + else + stack.push(JSU.B(JSU.toDouble(left) <= JSU.toDouble(right))); + break; + } + case GT: { + if(left instanceof JSString && right instanceof JSString) + stack.push(JSU.B(JSU.toString(left).compareTo(JSU.toString(right)) > 0)); + else + stack.push(JSU.B(JSU.toDouble(left) > JSU.toDouble(right))); + break; + } + case GE: { + if(left instanceof JSString && right instanceof JSString) + stack.push(JSU.B(JSU.toString(left).compareTo(JSU.toString(right)) >= 0)); + else + stack.push(JSU.B(JSU.toDouble(left) >= JSU.toDouble(right))); + break; + } + + case SHEQ: + case SHNE:{ + boolean ret; + if(left == null && right == null) ret = true; + else if(left == null || right == null) ret = false; + else ret = left.equals(right); + stack.push(JSU.B(op == SHEQ ? ret : !ret)); break; + + } + + case EQ: + case NE: { + boolean ret = JSU.coerceEquals(left, right); + stack.push(JSU.B(op == EQ ? ret : !ret)); break; + } + + default: throw new Error("unknown opcode " + op); + } } + } + + } catch(Exception e) { + if(!(e instanceof JSExn)){ + if(e instanceof InterruptedException) e = new JSExn((InterruptedException)e); + else e = new JSExn(e); + } + catchException((JSExn)e); + pc--; // it'll get incremented on the next iteration + } // end try/catch + } // end for + } + + private JS[] readArgs(Object arg){ + if (arg instanceof JSNumber) { + return readArgs(((JSNumber) arg).toInt32()); + } else + return (JS[]) arg; + } + + private JS[] readArgs(int n){ + // FIXME: we should be able to recycle JS[]'s here + JS[] jsargs = new JS[n]; + for (int i = jsargs.length - 1; i >= 0; i--) + jsargs[i] = (JS) stack.pop(); + return jsargs; + } + + //public void setTraceLine(String s){ nextTraceLine = s;} + + /** tries to find a handler withing the call chain for this exception + if a handler is found the interpreter is setup to call the exception handler + if a handler is not found the exception is thrown + */ + void catchException(JSExn e) throws JSExn { + while(!stack.empty()) { + Object o = stack.pop(); + if (o instanceof CatchMarker || o instanceof TryMarker) { + boolean inCatch = o instanceof CatchMarker; + if(inCatch) { + o = stack.pop(); + if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going + } + if(!inCatch && ((TryMarker)o).catchLoc >= 0) { + // run the catch block, this will implicitly run the finally block, if it exists + stack.push(o); + stack.push(catchMarker); + stack.push(e.asObject()); + f = ((TryMarker)o).f; + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).catchLoc; + return; + } else { + stack.push(new FinallyData(e)); + f = ((TryMarker)o).f; + scope = ((TryMarker)o).scope; + pc = ((TryMarker)o).finallyLoc; + return; + } + } + } + throw e; + } + + void setupTrap(JS.Trap t, JS val, TrapMarker cm, JS trapname) throws JSExn { + stack.push(cm); + JSArgsTrap ta =new JSArgsTrap(t, val, trapname==null?t.key():trapname); + cm.trapargs = ta; + stack.push(ta); + JSFunction jsfunc = (JSFunction)t.function(); + f = jsfunc.f; + scope = jsfunc.parentScope; + pc = 0; + } + + + // Markers ////////////////////////////////////////////////////////////////////// + + static class Marker {} + //String nextTraceLine = null; + static class CallMarker extends Marker implements Backtraceable{ + final int pc; + final Scope scope; + final Function f; + //String traceLine = null; + public CallMarker(Interpreter cx) { + pc = cx == null ? -1 : cx.pc + 1; + scope = cx == null ? null : cx.scope; + f = cx == null ? null : cx.f; + /*if(cx!=null){ + this.traceLine = cx.nextTraceLine; + cx.nextTraceLine = null; + }*/ + } + + public String traceLine(){ + if(f == null) return null; + String s = f.sourceName + ":" + f.line[pc-1]; + if(this instanceof Interpreter.TrapMarker) + s += " (trap on " + JSU.toString(((Interpreter.TrapMarker)this).t.key()) + ")"; + //if(traceLine!=null) s = traceLine +"/"+s; + return s; + + } + } + + static class TrapMarker extends CallMarker { + JS.Trap t; + JS val; + JSArgsTrap trapargs; + final boolean pauseOnCascade; // FOOTNOTE 2 + public TrapMarker(Interpreter cx, JS.Trap t, JS val, boolean pauseOnCascade) { + super(cx); + this.t = t; + this.val = val; + this.pauseOnCascade = pauseOnCascade; + } + } + + static class CatchMarker extends Marker { } + private static final CatchMarker catchMarker = new CatchMarker(); + + static class LoopMarker extends Marker { + final public int location; + final public String label; + final public Scope scope; + final public boolean canContinue; + public LoopMarker(int location, String label, Scope scope, boolean canContinue) { + this.location = location; + this.label = label; + this.scope = scope; + this.canContinue = canContinue; + } + } + static class TryMarker extends Marker { + final public int catchLoc; + final public int finallyLoc; + final public Scope scope; + final public Function f; + public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) { + this.catchLoc = catchLoc; + this.finallyLoc = finallyLoc; + this.scope = cx.scope; + this.f = cx.f; + } + } + static class FinallyData extends Marker { + final public int op; + final public Object arg; + final public JSExn exn; + public FinallyData(int op) { this(op,null); } + public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; } + public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn + } + + + + + static class Stub extends JS.Immutable { + private JS method; + JS obj; + public Stub(JS obj, JS method) { this.obj = obj; this.method = method; } + public JS apply(JS this_, JS[] args) throws JSExn { + return obj.callMethod(this_, method, args); + } + + public JS get(JS key) throws JSExn { + if ("apply".equals(JSU.toString(key))) { + return new JS.Immutable() { + public JS apply(JS this_, JS[] args) throws JSExn { + JS[] args2 = JSU.checkApply(args); + JS this_2 = args.length>0?args[0]:null; + return Stub.this.apply(this_2, args2); + } + }; + } + return super.get(key); + } + } + + final class Stack { + private static final int MAX_STACK_SIZE = 512; + private Object[] stack = new Object[8]; + private int sp = 0; + + boolean empty() { return sp == 0; } + void push(Object o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } + Object peek() { return peek(0); } + Object peek(int i) { + if(sp -i <= 0) throw new RuntimeException("stack underflow"); + return stack[sp-1-i]; + } + final Object pop() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[--sp]; } + void swap() throws JSExn { + if(sp < 2) throw new JSExn("stack overflow"); + Object tmp = stack[sp-2]; + stack[sp-2] = stack[sp-1]; + stack[sp-1] = tmp; + } + CallMarker findCall() { + for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i]; + return null; + } + /* Used in the debugger */ + public int callCount() { + int c = 0; + for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) c++; + return c; + } + void grow() throws JSExn { + if(stack.length >= MAX_STACK_SIZE) throw new JSExn("stack overflow"); + Object[] stack2 = new Object[stack.length * 2]; + System.arraycopy(stack,0,stack2,0,stack.length); + stack = stack2; + } + + private int previous(int current){ + for(int i=current-1;i>=0;i--) { + if (stack[i] instanceof Backtraceable) return i; + } + return -1; + } + + void backtrace(JSExn e) { + int i = sp; + // DEBUG (MAY add this to JSexn) + // e.addBacktrace("(Bytecode " + pc+")"); + + boolean topcall = true; + while( (i=previous(i))!=-1){ + Backtraceable cm = (Backtraceable)stack[i]; + if (topcall && cm instanceof CallMarker) { + e.addBacktrace(f.sourceName + ":" + f.line[pc]); + topcall = false; + } + e.addBacktrace(cm.traceLine()); + } + } + + String stackframe(int offset) { + int i = sp; + boolean topcall = true; + while( (i=previous(i))!=-1){ + Backtraceable cm = (Backtraceable)stack[i]; + if (topcall && cm instanceof CallMarker) { + if(offset==0) return f.sourceName + ":" + f.line[pc]; + topcall = false; + offset--; + } + if(offset==0) return cm.traceLine(); + offset--; + } + return null; + } + + public String toString(){ + String r = ""; + for(int i=0; i<sp; i++){ + r+= JSU.toString(stack[i]); + r+=", "; + } + return r; + } + /* + void backtrace(JSExn e) { + for(int i=sp-1;i>=0;i--) { + if (stack[i] instanceof CallMarker) break; + if (stack[i] instanceof Backtraceable) { + Backtraceable cm = (Backtraceable)stack[i]; + e.addBacktrace(cm.traceLine()); + } + } + if(f!=null) + e.addBacktrace(f.sourceName + ":" + f.line[pc]); + for(int i=sp-1;i>=0;i--) { + if (stack[i] instanceof CallMarker) { + CallMarker cm = (CallMarker)stack[i]; + e.addBacktrace(cm.traceLine()); + } + } + }*/ + } + + public void enterNonJSCall(Backtraceable call) throws JSExn{ + stack.push(call); + } + + public void exitNonJSCall(){ + stack.pop(); + } + + + static private JS add(JS left, JS right) throws JSExn{ + JSNumber leftn = JSU.expectNumber(left); + JSNumber rightn = JSU.expectNumber(right); + int kind = JSNumber.dominant(leftn, rightn); + if(kind == JSNumber.N_INT32){ + long r = (long)leftn.toInt32()+(long)rightn.toInt32(); + return JSU.N(r); + }else if(kind == JSNumber.N_RATIONAL){ + return JSU.N(leftn.toRational().add(rightn.toRational())); + }else{ + return JSU.N(leftn.toDouble()+rightn.toDouble()); + } + } + + static private JS subtract(JS left, JS right) throws JSExn{ + JSNumber leftn = JSU.expectNumber(left); + JSNumber rightn = JSU.expectNumber(right); + int kind = JSNumber.dominant(leftn, rightn); + if(kind == JSNumber.N_INT32){ + long r = (long)leftn.toInt32()-(long)rightn.toInt32(); + return JSU.N(r); + }else if(kind == JSNumber.N_RATIONAL){ + return JSU.N(leftn.toRational().subtract(rightn.toRational())); + }else{ + return JSU.N(leftn.toDouble()-rightn.toDouble()); + } + } + + static private JS multiply(JS left, JS right) throws JSExn{ + JSNumber leftn = JSU.expectNumber(left); + JSNumber rightn = JSU.expectNumber(right); + int kind = JSNumber.dominant(leftn, rightn); + if(kind == JSNumber.N_INT32){ + long r = (long)leftn.toInt32()*(long)rightn.toInt32(); + return JSU.N(r); + }else if(kind == JSNumber.N_RATIONAL){ + return JSU.N(leftn.toRational().multiply(rightn.toRational())); + }else{ + return JSU.N(leftn.toDouble()*rightn.toDouble()); + } + } + + static private JS divide(JS left, JS right) throws JSExn{ + JSNumber leftn = JSU.expectNumber(left); + JSNumber rightn = JSU.expectNumber(right); + int kind = JSNumber.dominant(leftn, rightn); + if(kind == JSNumber.N_RATIONAL){ + return JSU.N(leftn.toRational().divide(rightn.toRational())); + }else{ + return JSU.N(leftn.toDouble()/rightn.toDouble()); + } + } + + static private JS modulo(JS left, JS right) throws JSExn{ + JSNumber leftn = JSU.expectNumber(left); + JSNumber rightn = JSU.expectNumber(right); + int kind = JSNumber.dominant(leftn, rightn); + if(kind == JSNumber.N_INT32){ + int r = leftn.toInt32()%rightn.toInt32(); + return JSU.N(r); + }else if(kind == JSNumber.N_RATIONAL){ + return JSU.N(leftn.toRational().mod(rightn.toRational())); + }else{ + return JSU.N(leftn.toDouble()%rightn.toDouble()); + } + } + + public String toString(){ + return getWhere(); + } +} + +/* FOOTNOTES + * 1. Automatic cascading has been ripped out. Fundamentally unclean, and only + * really there as syntactic sugar, even dubious on that level. + * + * (Automatic cascading is a mechanism for making traps listener like... i.e. + * though do not effect the put operation, they just do something when one happens. + * In practise it makes implementation harder, the resulting JS code becomes less + * clear, and the same effect can be had in JS with the addition of a 'cascade'.) + * + * + * 2. 'pause on cascade' is a mechanism with which at the + * final cascade (i.e. no more traps to be fired) the interpreter gives up control + * (returns out) to let the original calling code decide what to do. + * + * This is necessary to allow (without hacks) + * a) traps in the get(JS) and put(JS,JS) methods as this would cause an infinite loop + * as they call themselves on the final cascade. + * b) speed, as it allows us to store some properties as class fields and not force us + * to go through get/put to modify them. + * c) ignoring of modified values/not putting a value at all (i.e. its just a virtual + * property that traps can 'listen' to). + * (use cases are in org.vexi.core.Box.jpp) + */ + Modified: branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java 2015-02-14 21:59:36 UTC (rev 4762) +++ branches/vexi3/org.vexi-library.js/src/main/java/org/ibex/js/Scheduler.java 2015-02-16 01:01:31 UTC (rev 4763) @@ -212,16 +212,14 @@ if (jsthread==null) { setJSThread(new Thread(this, t.function(), false, null)); } - Interpreter old = jsthread.currentInterpreter; - jsthread.currentInterpreter = new Interpreter(jsthread, t, val, true, trapname); - jsthread.currentInterpreter.old = old; + Interpreter I = new Interpreter(jsthread, t, val, true, trapname); // REMARK - this thread is unpausable, so setting this static variable // cannot be interefered with by other js executions. Could be done in the // interpreter when returning instead of cascading as well. cascadedTo = null; // returns the cascaded value or // Interpreter.CASCADE_PREVENTED if the trap returned instead - return (JS)jsthread.currentInterpreter.run(null); + return (JS)I.run(null); } /** Execute write traps, part 2 */ @@ -270,14 +268,12 @@ if (jsthread==null) { setJSThread(new Thread(this, t.function(), false, null)); } - Interpreter old = jsthread.currentInterpreter; - jsthread.currentInterpreter = new Interpreter(jsthread, t, null, true, trapname); - jsthread.currentInterpreter.old = old; + Interpreter I = new Interpreter(jsthread, t, null, true, trapname); // REMARK - this thread is unpausable, so setting this static variable // cannot be interfered with by other js executions. Could be done in the // interpreter when returning instead of cascading as well. cascadedTo = null; - return (JS)jsthread.currentInterpreter.run(null); + return (JS)I.run(null); // //System.out.println("cascadedTo " + cascadedTo); } @@ -312,10 +308,9 @@ } Interpreter old = jsthread.currentInterpreter; // Always false. Restarting paused nested Interpreters not supported. - jsthread.currentInterpreter = new Interpreter(jsthread, (JSFunction)function, /*jsthread.pauseable*/false, args); - jsthread.currentInterpreter.old = old; + Interpreter I = new Interpreter(jsthread, (JSFunction)function, /*jsthread.pauseable*/false, args); try { - return (JS)jsthread.currentInterpreter.run(null); + return (JS)I.run(null); } finally { if (isFirst) { jsthread.destroy(); Deleted: branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp 2015-02-14 21:59:36 UTC (rev 4762) +++ branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Interpreter.jpp 2015-02-16 01:01:31 UTC (rev 4763) @@ -1,1063 +0,0 @@ -// Copyright 2000-2008 the Contributors, as shown in the revision logs. -// Licensed under the Apache Software License 2.0 ("the License"). -// You may not use this file except in compliance with the License. - - -package org.ibex.js; - -import org.ibex.js.parse.*; - -/*@PAGE(concept=Special Variables) - * - * <h3>General Variables</h3> - * <p>The following special variables are accessible in general template script.</p> - * <dl> - * <dt><code>thisbox</code></dt> - * <dd><p>A reference to the Box defined by the XML node containing it. It can also be thought - * of as a Box property.</p></dd> - * <dt><code>thisobj</code></dt> - * <dd><p>A reference to the Object/Array/etc defined by the XML node containing it.</p></dd> - * <dt><code>static</code></dt> - * <dd><p>The static context of the template in which it is called.</p></dd> - * <dt><code>vexi</code></dt> - * <dd><p>The immutable global vexi object.</p></dd> - * </dl> - * - * <h3>Function Variables</h3> - * <p>The following special variables are accessible in the body of a function.</p> - * <dl> - * <dt><code>arguments</code></dt> - * <dd><p>An immutable array of the arguments passed into a function. Its length is detirmined - * when the function is called and not by the method signature. </p></dd> - * <dt><code>callee</code></dt> - * <dd><p>The current function being executed</p></dd> - * <dt><code>this</code></dt> - * <dd> - * <p><code>this</code><i> is a keyword</i></p> - * <p>The context of the execution. When a function is being accessed as the property of an - * object the context is the object. For example in <code>a.f()</code> in the body of - * <code>f</code> this refers to <code>a</code>.</p> - * - * <p>If a function is executed as a var then <code>this==null</code>.</p> - * - * <p>In top level code the value of <code>this</code> may be assigned to a relevant - * context.</p> - * </dd> - * </dl> - * - * <h3>Trap Variables</h3> - * <p>The following special variables are accessible in the body of a trap.</p> - * <dl> - * <dt><code>cascade</code></dt> - * <dd> - * <p><code>cascade</code><i> is a keyword</i></p> - * <p>In a read trap, read from cascade to get the value from traps lower in the read trap - * chain or, if there are no further traps, the object property the trap is placed upon. - * (Reading directly from the property will invoke the read trap chain again, possibly causing - * an infinite loop.)</p> - * <p>In a write trap, putting to cascade will caused lower traps in the write trap chain to be - * executed or, if there are no further traps, the value passed to cascade is put to the object - * property the trap is placed upon. (Writing directly to the property will invoke the write - * trap chain again, possibly causing an infinite loop.)</p> - * </dd> - * <dt><code>trapname</code></dt> - * <dd><p>The name of the property upon which a trap is been placed.</p></dd> - * <dt><code>trapee</code></dt> - * <dd><p>The object upon which a trap is been placed. <i>Synonym for this</i></p></dd> - * <dt><code>callee</code></dt> - * <dd><p>The trap function - useful for anonymous removal of traps.</p></dd> - * </dl> - * */ -/** Encapsulates a single JS interpreter (ie call stack) */ -public class Interpreter implements ByteCodes, Tokens, Constants { - - - // FIXME - use Thread.cascadedTo==NULL instead - public final static JSNumber CASCADE_PREVENTED = new JSNumber.I(1); - - // Instance members and methods ////////////////////////////////////////////////////////////////////// - - // FIXME - should not be public but devl core uses them - int pausecount; ///< the number of times pause() has been invoked; -1 indicates unpauseable - public Function f = null; ///< the currently-executing JSFunction - public Scope scope; ///< the current top-level scope (LIFO stack via NEWSCOPE/OLDSCOPE) - final public Stack stack = new Stack(); ///< the object stack - public int pc = 0; ///< the program counter - - final Thread thread; - Interpreter old = null; - - public Interpreter(Thread thread){this.thread = thread;} - Interpreter(Thread thread, JSFunction f, boolean pauseable, JS[] args) { - this(thread); - this.f = f.f; - this.pausecount = pauseable ? 0 : -1; - this.scope = f.parentScope; - try { - stack.push(new CallMarker(null)); // the "root function returned" marker -- f==null - stack.push(new JSArgs(args, f, null)); // FIXME: temprorary bug fix - } catch(JSExn e) { - throw new Error("should never happen"); - } - } - - Interpreter(Thread thread, JS.Trap t, JS val, boolean pauseOnCascade, JS trapname) { - this(thread); - this.pausecount = -1; - try { - setupTrap(t,val,new TrapMarker(null,t,val,pauseOnCascade),trapname); - } catch(JSExn e) { - throw new Error("should never happen"); - } - } - - private boolean get = false; - // FIXME: split this stuff out into a Script instance control object - // so it's possible to make JS either single or multi threaded. - /** this is the only synchronization point we need in order to be threadsafe */ - public synchronized Object run(Object o) throws JSExn { - if (f == null) throw new RuntimeException("function already finished"); - if (scope == null) throw new RuntimeException("scope is null"); - - if( o instanceof JSExn){ - // Fill in empty exceptions. Exceptions coming created in background - // calls don't have their stack traces filled in. - ((JSExn)o).fillIfEmpty(this); - // Throw excpetion - move interpreter to catch block - catchException((JSExn)o); - }else if (get) - stack.push(o); - - return run(); - } - - public void pause(String forwhat) throws JSExn { - if (pausecount == -1 || f == null) throw new JSExn("Cannot "+forwhat+" in foreground thread"); - pausecount++; - switch(f.op[pc]) { - case Tokens.RETURN: case ByteCodes.PUT: get = false; break; - case ByteCodes.GET: case ByteCodes.GET_PRESERVE: case ByteCodes.CALLMETHOD: case ByteCodes.CALL: get = true; break; - default: throw new Error("paused on unexpected bytecode: " + f.op[pc]); - } - } - - int getLine() { - return f == null || pc < 0 || pc >= f.size ? -1 : f.line[pc]; - } - - String getSourceName() { - return f == null ? null : f.sourceName; - } - String getWhere(){ return getSourceName() + ":" + getLine(); } - - private JSExn je(String s) { return new JSExn(getWhere() + " " + s); } - - - // REMARK - encapsulate loop jump operations here, so that we can test - // interuption, but only when there is a chance we're in an infinite loop. - private void jump(int delta_pc) throws InterruptedException{ - jumpAbs(pc+delta_pc); - } - private void jumpAbs(int pc) throws InterruptedException{ - this.pc = pc; - if(java.lang.Thread.interrupted()) throw new InterruptedException(); - } - - private JS run() throws JSExn { - // if pausecount changes after a get/put/call, we know we've been paused - if(pausecount>0) - pausecount --; - - OUTER: for(;; pc++) { - try { - if(Instr.interpreter!=null) Instr.interpreter.handle(this); - - int op = f.op[pc]; - Object arg = f.arg[pc]; - if(op == FINALLY_DONE) { - FinallyData fd = (FinallyData) stack.pop(); - if(fd == null) continue OUTER; // NOP - if(fd.exn != null) throw fd.exn; - op = fd.op; - arg = fd.arg; - } - - // DEBUG - // DevUtil.debugStack(this, op, arg); - - switch(op) { - case LITERAL: stack.push(arg); break; - // GUT ought to fetch constructors - case OBJECT: stack.push(new JS.Obj()); break; - case ARRAY: stack.push(new JSArray(JSU.toInt((JS)arg))); break; - //case DECLARE: scope.declare((JS)(arg==null ? stack.peek() : arg)); if(arg != null) stack.push((JS)arg); break; - case JT: if (JSU.isTruthy((JS)stack.pop())) jump(JSU.toInt((JS)arg) - 1); break; - case JF: if (!JSU.isTruthy((JS)stack.pop())) jump(JSU.toInt((JS)arg) - 1); break; - case JMP: jump(JSU.toInt((JS)arg) - 1); break; - case POP: stack.pop(); break; - case SWAP: stack.swap(); break; - case DUP: - if(arg==null) stack.push(stack.peek()); - else stack.push(stack.peek(JSU.toInt((JS)arg))); - break; - case NEWSCOPE: { - int n = JSU.toInt((JS)arg); - scope = new Scope(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: { - // FIXME: HACK: share this around more and find the callee. - Object val = stack.peek(); - if (val != null && val instanceof JS[]) val = new JSArgs((JS[])val, null, null); - scope.put((JS)arg, (JS)val); break; - } - case ASSERT: if (!JSU.isTruthy((JS)stack.pop())) throw je("ibex.assertion.failed"); break; - case BITNOT: stack.push(JSU.N(~JSU.toLong((JS)stack.pop()))); break; - case BANG: stack.push(JSU.B(!JSU.isTruthy((JS)stack.pop()))); break; - case NEWFUNCTION: stack.push(JSU.cloneWithNewParentScope((Function)arg, scope)); break; - case LABEL: break; - - case TYPEOF: { - Object o = stack.pop(); - if (o == null) stack.push(SC_null); - else stack.push(((JS)o).type()); - break; - } - - case INSTANCEOF: { - JS c = (JS)stack.pop(); - JS o = (JS)stack.pop(); - if(c==null) throw new JSExn("Cannot instanceof null"); - stack.push(JSU.B(o==null?false:o.instanceOf(c))); - break; - } - - case PUSHKEYS: { - JS o = (JS)stack.peek(); - stack.push(o == null ? null : o.keys()); - break; - } - - case LOOP: - case SWITCH: - //stack.push(new LoopMarker(pc, (String)(pc > 0 && f.op[pc - 1] == LABEL ? f.arg[pc - 1] : null), scope)); - //stack.push(JSU.T); - stack.push(new LoopMarker(pc, pc > 0 && f.op[pc - 1] == LABEL ? (String)f.arg[pc - 1] : (String)null, scope, op == LOOP)); - if(op == LOOP) stack.push(JSU.T); - break; - - case BREAK: - case CONTINUE: - while(!stack.empty()) { - Object o = stack.pop(); - if (o instanceof CallMarker) throw je("Tried to '"+(op==BREAK?"break":"continue")+"' when not within a loop"+(op==BREAK?"/switch":"")); - if (o instanceof TryMarker) { - if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going - stack.push(new FinallyData(op, arg)); - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).finallyLoc - 1; - continue OUTER; - } - if (o instanceof LoopMarker) { - //if (arg == null || arg.equals(((LoopMarker)o).label)) { - //int loopInstructionLocation = ((LoopMarker)o).location; - LoopMarker lm = (LoopMarker) o; - if(op == CONTINUE && !lm.canContinue) continue; - if (arg == null || arg.equals(lm.label)) { - int loopInstructionLocation = lm.location; - int endOfLoop = JSU.toInt((JS)f.arg[loopInstructionLocation]) + loopInstructionLocation; - scope = lm.scope; - if (op == CONTINUE) { stack.push(o); stack.push(JSU.F); } - if(op == BREAK) pc = endOfLoop - 1; - else jumpAbs(loopInstructionLocation); - continue OUTER; - } - } - } - throw new Error((op==BREAK?"BREAK":"CONTINUE")+" invoked but couldn't find LoopMarker at " + - getSourceName() + ":" + getLine()); - - case TRY: { - int[] jmps = (int[]) arg; - // jmps[0] is how far away the catch block is, jmps[1] is how far away the finally block is - // each can be < 0 if the specified block does not exist - stack.push(new TryMarker(jmps[0] < 0 ? -1 : pc + jmps[0], jmps[1] < 0 ? -1 : pc + jmps[1], this)); - break; - } - - case RETURN: { - JS retval = (JS)stack.pop(); - while(!stack.empty()) { - Object o = stack.pop(); - if (o instanceof TryMarker) { - if(((TryMarker)o).finallyLoc < 0) continue; - stack.push(retval); - stack.push(new FinallyData(RETURN)); - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).finallyLoc - 1; - continue OUTER; - } else if (o instanceof CallMarker) { - boolean isWriteTrap = false; - if (o instanceof TrapMarker) { // handles return component of a write trap - TrapMarker tm = (TrapMarker) o; - isWriteTrap = tm.t.isWriteTrap(); - if (isWriteTrap) { - // REMOVED automatic cascading(FOOTNOTE:1) - thread.faction.cascadedTo = null; - retval = CASCADE_PREVENTED; - } - } - CallMarker cm = (CallMarker) o; - scope = cm.scope; - pc = cm.pc - 1; - f = cm.f; - if(!isWriteTrap) - stack.push(retval); - if (f == null) return retval; - continue OUTER; - } - } - throw new Error("error: RETURN invoked but couldn't find a CallMarker!"); - } - - case CASCADE: { - boolean write = JSU.T==arg; - JS val = write ? (JS)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"); - JS.Trap t = write ? tm.t.nextWrite() : tm.t.nextRead(); - while (t == null && target instanceof JS.Clone) { - target = ((JS.Clone)target).clonee; - t = target.getTrap(key); - if (t != null) t = write ? t.findWrite() : t.findRead(); - } - if(write) { - stack.push(val); - } - if(t != null) { - setupTrap(t,val,new TrapMarker(this,t,val,tm.pauseOnCascade), tm.trapargs.trapname); - pc--; // we increment later - } else { - thread.faction.cascadedTo = tm.trapargs.trapname; - if(write) { - if (tm.pauseOnCascade) { pc++; return val; } - target.put(key,val); - } else { - if (tm.pauseOnCascade) { - // FIXME should move this to setup of interpreter - // we need it true for when we restart the interpreter - get = true; - pc++; return null; } - else{ - JS ret = target.get(key); - if (ret != null && ret == JS.METHOD) ret = new Stub(target, key); - stack.push(ret); - } - } - if (pausecount > 0) { pc++; return null; } // we were paused - } - break; - } - - case PUT: { - JS val = (JS)stack.pop(); - JS key = (JS)stack.pop(); - JS target = (JS)stack.peek(); - if (target == null) throw je("Tried to put " + JSU.toString(val) + " to the " + JSU.toString(key) + " property on the null value"); - if (key == null) throw je("Tried to assign \"" + JSU.toString(val) + "\" to the null key"); - - JS.Trap t = target.getTrap(key); - if(t != null) t = t.findWrite(); - - stack.push(val); - - if(t != null) { - setupTrap(t,val,new TrapMarker(this,t,val,false),null); - pc--; // we increment later - } else { - target.put(key,val); - if (pausecount > 0) { pc++; return null; } // we were paused - } - break; - } - - case GET: - case GET_PRESERVE: { - JS target, key; - if (op == GET) { - key = arg == null ? (JS)stack.pop() : (JS)arg; - target = (JS)stack.pop(); - } else { - if(arg==null){ - key = (JS)stack.pop(); - target = (JS)stack.peek(); - stack.push(key); - }else{ - key = (JS)arg; - target = (JS)stack.peek(); - } - } - JS ret = null; - if (key == null) throw je("Tried to get the null key from " + JSU.toString(target)); - if (target == null) throw je("Tried to get property \"" + JSU.toString(key) + "\" from the null object"); - - JS.Trap t = target.getTrap(key); - if (t != null) t = t.findRead(); - - if(t != null) { - JS f = t.function(); - if(f instanceof JSFunction){ - setupTrap(t,null,new TrapMarker(this,t,null,false),null); - pc--; // we increment later - break; - } - // Trap was an override. Simply return the value. - ret = f; - } - if(ret==null) ret = target.get(key); - if (pausecount > 0) { pc++; return null; } // we were paused - - if (ret != null && ret == JS.METHOD) ret = new Stub(target, key); - stack.push(ret); - break; - } - - case NEW: { - JS[] jsargs = readArgs(arg); - JS constructor = (JS) stack.pop(); - if (constructor == null) - throw new JSExn("null is not a constructor"); - stack.push(constructor.new_(jsargs)); - break; - } - - case CALL: case CALLMETHOD: { - JS[] jsargs = readArgs(arg); - - JS ret = null; - JS callee = (JS) stack.pop(); - JS object = null; - if (op == CALLMETHOD) { - if (callee == null) { - JS method = (JS) stack.pop(); - object = (JS) stack.pop(); - if (object instanceof JSPrimitive) - throw new JSExn("Method '" - + JSU.toString(method) - + "' not defined for " + object.type()); - throw new JSExn("Function '" + JSU.toString(method) - + "' not found in " + JSU.toString(object)); - } else { - stack.pop(); - object = (JS) stack.pop(); - } - } - if (callee == null) - throw new JSExn("Tried to call null object"); - - switch (callee.callType()) { - case JS.CALLTYPE_FUNCTION: { - stack.push(new CallMarker(this)); - JSFunction jsfunc = (JSFunction) callee; - f = jsfunc.f; - stack.push(new JSArgs(jsargs, jsfunc, object)); - scope = jsfunc.parentScope; - pc = -1; - break; - } - case JS.CALLTYPE_APPLY: - JS[] args = JSU.checkApply(jsargs); - JS this_ = jsargs.length>0?jsargs[0]:null; - if(callee instanceof JSFunction.Apply){ - stack.push(new CallMarker(this)); - JSFunction jsfunc = (JSFunction) ((JSFunction.Apply) callee).f; - f = jsfunc.f; - stack.push(new JSArgs(args, jsfunc, this_)); - scope = jsfunc.parentScope; - pc = -1; - break; - } - object = this_; - jsargs = args; - // fallthrough - case JS.CALLTYPE_METHOD: - ret = callee.apply(object,jsargs); - if (pausecount > 0) { - pc++; - return null; - } - stack.push(ret); - break; - } - break; - } - - case THROW:{ - JS thrown = (JS)stack.pop(); - // If already an exception then we recover the JSExn and rethrow it - if(thrown instanceof JSExn.Obj){ - JSExn je = ((JSExn.Obj)thrown).asJSExn(); - je.fillIfEmpty(this); - throw je; - }else - throw new JSExn(thrown, null, null, this); - } - - case ADD_TRAP: case DEL_TRAP: { - // A trap addition/removal - JS val = (JS)stack.pop(); - JS key = (JS)stack.pop(); - JS js = (JS)stack.peek(); - - // some checks to make sure the assignment is valid - // TODO: check val is a function - if (key == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") - + " a trap function using a null key on a box or object"); - if (val == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") - + " a null value as a trap function to property '" + key.coerceToString() + "'"); - if (js == null) throw new JSExn("Tried to " + (op == ADD_TRAP ? "add" : "remove") - + " a trap to property '" + key.coerceToString() + "' on a null object "); - - if(op == ADD_TRAP) js.addTrap(key, val); - else js.delTrap(key, val); - break; - } - - case ADD: { - int count = ((JSNumber)arg).toInt32(); - if(count < 2) throw new Error("this should never happen"); - if(count == 2) { - // common case - JS right = (JS)stack.pop(); - JS left = (JS)stack.pop(); - JS ret; - if(left instanceof JSString || right instanceof JSString) - ret = JSU.S(JSU.toString(left).concat(JSU.toString(right))); - else { - ret = add(left, right); - } - stack.push(ret); - } else { - JS[] args = new JS[count]; - while(--count >= 0) args[count] = (JS)stack.pop(); - if(args[0] instanceof JSString) { - StringBuffer sb = new StringBuffer(64); - for(int i=0;i<args.length;i++) sb.append(JSU.toString(args[i])); - stack.push(JSU.S(sb.toString())); - } else { - int numStrings = 0; - for(int i=0;i<args.length;i++) if(args[i] instanceof JSString) numStrings++; - if(numStrings == 0) { - double d = 0.0; - for(int i=0;i<args.length;i++) d += JSU.toDouble(args[i]); - stack.push(JSU.N(d)); - } else { - int i=0; - StringBuffer sb = new StringBuffer(64); - if(!(args[0] instanceof JSString || args[1] instanceof JSString)) { - double d=0.0; - do { - d += JSU.toDouble(args[i++]); - } while(!(args[i] instanceof JSString)); - sb.append(JSU.toString(JSU.N(d))); - } - while(i < args.length) sb.append(JSU.toString(args[i++])); - stack.push(JSU.S(sb.toString())); - } - } - } - break; - } - - default: { - JS right = (JS)stack.pop(); - JS left = (JS)stack.pop(); - switch(op) { - - case BITOR: stack.push(JSU.N(JSU.toLong(left) | JSU.toLong(right))); break; - case BITXOR: stack.push(JSU.N(JSU.toLong(left) ^ JSU.toLong(right))); break; - case BITAND: stack.push(JSU.N(JSU.toLong(left) & JSU.toLong(right))); break; - - case SUB: stack.push(subtract(left, right)); break; - case MUL: stack.push(multiply(left, right)); break; - case DIV: stack.push(divide(left,right)); break; - case MOD: stack.push(modulo(left, right)); break; - - case LSH: stack.push(JSU.N(JSU.toLong(left) << JSU.toLong(right))); break; - case RSH: stack.push(JSU.N(JSU.toLong(left) >> JSU.toLong(right))); break; - case URSH: stack.push(JSU.N(JSU.toLong(left) >>> JSU.toLong(right))); break; - - //#repeat </<=/>/>= LT/LE/GT/GE - case LT: { - if(left instanceof JSString && right instanceof JSString) - stack.push(JSU.B(JSU.toString(left).compareTo(JSU.toString(right)) < 0)); - else - stack.push(JSU.B(JSU.toDouble(left) < JSU.toDouble(right))); - break; - } - //#end - - case SHEQ: - case SHNE:{ - boolean ret; - if(left == null && right == null) ret = true; - else if(left == null || right == null) ret = false; - else ret = left.equals(right); - stack.push(JSU.B(op == SHEQ ? ret : !ret)); break; - - } - - case EQ: - case NE: { - boolean ret = JSU.coerceEquals(left, right); - stack.push(JSU.B(op == EQ ? ret : !ret)); break; - } - - default: throw new Error("unknown opcode " + op); - } } - } - - } catch(Exception e) { - if(!(e instanceof JSExn)){ - if(e instanceof InterruptedException) e = new JSExn((InterruptedException)e); - else e = new JSExn(e); - } - catchException((JSExn)e); - pc--; // it'll get incremented on the next iteration - } // end try/catch - } // end for - } - - private JS[] readArgs(Object arg){ - if (arg instanceof JSNumber) { - return readArgs(((JSNumber) arg).toInt32()); - } else - return (JS[]) arg; - } - - private JS[] readArgs(int n){ - // FIXME: we should be able to recycle JS[]'s here - JS[] jsargs = new JS[n]; - for (int i = jsargs.length - 1; i >= 0; i--) - jsargs[i] = (JS) stack.pop(); - return jsargs; - } - - //public void setTraceLine(String s){ nextTraceLine = s;} - - /** tries to find a handler withing the call chain for this exception - if a handler is found the interpreter is setup to call the exception handler - if a handler is not found the exception is thrown - */ - void catchException(JSExn e) throws JSExn { - while(!stack.empty()) { - Object o = stack.pop(); - if (o instanceof CatchMarker || o instanceof TryMarker) { - boolean inCatch = o instanceof CatchMarker; - if(inCatch) { - o = stack.pop(); - if(((TryMarker)o).finallyLoc < 0) continue; // no finally block, keep going - } - if(!inCatch && ((TryMarker)o).catchLoc >= 0) { - // run the catch block, this will implicitly run the finally block, if it exists - stack.push(o); - stack.push(catchMarker); - stack.push(e.asObject()); - f = ((TryMarker)o).f; - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).catchLoc; - return; - } else { - stack.push(new FinallyData(e)); - f = ((TryMarker)o).f; - scope = ((TryMarker)o).scope; - pc = ((TryMarker)o).finallyLoc; - return; - } - } - } - throw e; - } - - void setupTrap(JS.Trap t, JS val, TrapMarker cm, JS trapname) throws JSExn { - stack.push(cm); - TrapArgs ta =new TrapArgs(t, val, trapname==null?t.key():trapname); - cm.trapargs = ta; - stack.push(ta); - JSFunction jsfunc = (JSFunction)t.function(); - f = jsfunc.f; - scope = jsfunc.parentScope; - pc = 0; - } - - - // Markers ////////////////////////////////////////////////////////////////////// - - static class Marker {} - //String nextTraceLine = null; - static class CallMarker extends Marker implements Backtraceable{ - final int pc; - final Scope scope; - final Function f; - //String traceLine = null; - public CallMarker(Interpreter cx) { - pc = cx == null ? -1 : cx.pc + 1; - scope = cx == null ? null : cx.scope; - f = cx == null ? null : cx.f; - /*if(cx!=null){ - this.traceLine = cx.nextTraceLine; - cx.nextTraceLine = null; - }*/ - } - - public String traceLine(){ - if(f == null) return null; - String s = f.sourceName + ":" + f.line[pc-1]; - if(this instanceof Interpreter.TrapMarker) - s += " (trap on " + JSU.toString(((Interpreter.TrapMarker)this).t.key()) + ")"; - //if(traceLine!=null) s = traceLine +"/"+s; - return s; - - } - } - - static class TrapMarker extends CallMarker { - JS.Trap t; - JS val; - TrapArgs trapargs; - final boolean pauseOnCascade; // FOOTNOTE 2 - public TrapMarker(Interpreter cx, JS.Trap t, JS val, boolean pauseOnCascade) { - super(cx); - this.t = t; - this.val = val; - this.pauseOnCascade = pauseOnCascade; - } - } - - static class CatchMarker extends Marker { } - private static final CatchMarker catchMarker = new CatchMarker(); - - static class LoopMarker extends Marker { - final public int location; - final public String label; - final public Scope scope; - final public boolean canContinue; - public LoopMarker(int location, String label, Scope scope, boolean canContinue) { - this.location = location; - this.label = label; - this.scope = scope; - this.canContinue = canContinue; - } - } - static class TryMarker extends Marker { - final public int catchLoc; - final public int finallyLoc; - final public Scope scope; - final public Function f; - public TryMarker(int catchLoc, int finallyLoc, Interpreter cx) { - this.catchLoc = catchLoc; - this.finallyLoc = finallyLoc; - this.scope = cx.scope; - this.f = cx.f; - } - } - static class FinallyData extends Marker { - final public int op; - final public Object arg; - final public JSExn exn; - public FinallyData(int op) { this(op,null); } - public FinallyData(int op, Object arg) { this.op = op; this.arg = arg; this.exn = null; } - public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn - } - - static class JSArgs extends JS.Immutable implements JSArrayLike{ - private final JS[] args; - private final JS callee; - private final JS this_; - - public JSArgs(JS[] args, JS callee, JS this_) { this.args = args; this.callee = callee; this.this_ = this_; } - - public JS getElement(int i) throws JSExn { return i>=args.length ? null : args[i]; } - public JS get(JS key) throws JSExn { - if(JSU.isInt(key)) { - int i = JSU.toInt32(key); - return getElement(i); - } - //#switch(JSU.toString(key)) - case "callee": return callee; - case "length": return JSU.N(args.length); - case "this": return this_; - //#end - return super.get(key); - } - public JS.Keys keys() throws JSExn { return new JSArrayLike.Keys(this);} - public JS[] toArray() { return args;} - public int size(){ return args.length; } - } - - static class TrapArgs extends JS.Immutable { - private Trap t; - private JS val; - private JS trapname; - public TrapArgs(Trap t, JS val, JS trapname) { - this.t = t; this.trapname = trapname; this.val = val; - } - public JS get(JS key) throws JSExn { - if(JSU.isInt(key) && JSU.toInt(key) == 0) return val; - //#switch(JSU.toString(key)) - case "trapee": return t.target(); - case "callee": return t.function(); - case "trapname": return trapname; - case "length": return t.isWriteTrap() ? NC_1 : NC_0; - //#end - return super.get(key); - } - public void put(JS key, JS val) throws JSExn { - String prop = JSU.toString(key); - if(prop.equals("trapname")){ - trapname = val; - }else - super.put(key, val); - } - - } - - - static class Stub extends JS.Immutable { - private JS method; - JS obj; - public Stub(JS obj, JS method) { this.obj = obj; this.method = method; } - public JS apply(JS this_, JS[] args) throws JSExn { - return obj.callMethod(this_, method, args); - } - - public JS get(JS key) throws JSExn { - if ("apply".equals(JSU.toString(key))) { - return new JS.Immutable() { - public JS apply(JS this_, JS[] args) throws JSExn { - JS[] args2 = JSU.checkApply(args); - JS this_2 = args.length>0?args[0]:null; - return Stub.this.apply(this_2, args2); - } - }; - } - return super.get(key); - } - } - - final class Stack { - private static final int MAX_STACK_SIZE = 512; - private Object[] stack = new Object[8]; - private int sp = 0; - - boolean empty() { return sp == 0; } - void push(Object o) throws JSExn { if(sp == stack.length) grow(); stack[sp++] = o; } - Object peek() { return peek(0); } - Object peek(int i) { - if(sp -i <= 0) throw new RuntimeException("stack underflow"); - return stack[sp-1-i]; - } - final Object pop() { if(sp == 0) throw new RuntimeException("stack underflow"); return stack[--sp]; } - void swap() throws JSExn { - if(sp < 2) throw new JSExn("stack overflow"); - Object tmp = stack[sp-2]; - stack[sp-2] = stack[sp-1]; - stack[sp-1] = tmp; - } - CallMarker findCall() { - for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) return (CallMarker) stack[i]; - return null; - } - /* Used in the debugger */ - public int callCount() { - int c = 0; - for(int i=sp-1;i>=0;i--) if(stack[i] instanceof CallMarker) c++; - return c; - } - void grow() throws JSExn { - if(stack.length >= MAX_STACK_SIZE) throw new JSExn("stack overflow"); - Object[] stack2 = new Object[stack.length * 2]; - System.arraycopy(stack,0,stack2,0,stack.length); - stack = stack2; - } - - private int previous(int current){ - for(int i=current-1;i>=0;i--) { - if (stack[i] instanceof Backtraceable) return i; - } - return -1; - } - - void backtrace(JSExn e) { - int i = sp; - // DEBUG (MAY add this to JSexn) - // e.addBacktrace("(Bytecode " + pc+")"); - - boolean topcall = true; - while( (i=previous(i))!=-1){ - Backtraceable cm = (Backtraceable)stack[i]; - if (topcall && cm instanceof CallMarker) { - e.addBacktrace(f.sourceName + ":" + f.line[pc]); - topcall = false; - } - e.addBacktrace(cm.traceLine()); - } - } - - String stackframe(int offset) { - int i = sp; - boolean topcall = true; - while( (i=previous(i))!=-1){ - Backtraceable cm = (Backtraceable)stack[i]; - if (topcall && cm instanceof CallMarker) { - if(offset==0) return f.sourceName + ":" + f.line[pc]; - topcall = false; - offset--; - } - if(offset==0) return cm.traceLine(); - offset--; - } - return null; - } - - public String toString(){ - String r = ""; - for(int i=0; i<sp; i++){ - r+= JSU.toString(stack[i]); - r+=", "; - } - return r; - } - /* - void backtrace(JSExn e) { - for(int i=sp-1;i>=0;i--) { - if (stack[i] instanceof CallMarker) break; - if (stack[i] instanceof Backtraceable) { - Backtraceable cm = (Backtraceable)stack[i]; - e.addBacktrace(cm.traceLine()); - } - } - if(f!=null) - e.addBacktrace(f.sourceName + ":" + f.line[pc]); - for(int i=sp-1;i>=0;i--) { - if (stack[i] instanceof CallMarker) { - CallMarker cm = (CallMarker)stack[i]; - e.addBacktrace(cm.traceLine()); - } - } - }*/ - } - - public void enterNonJSCall(Backtraceable call) throws JSExn{ - stack.push(call); - } - - public void exitNonJSCall(){ - stack.pop(); - } - - - static private JS add(JS left, JS right) throws JSExn{ - JSNumber leftn = JSU.expectNumber(left); - JSNumber rightn = JSU.expectNumber(right); - int kind = JSNumber.dominant(leftn, rightn); - if(kind == JSNumber.N_INT32){ - long r = (long)leftn.toInt32()+(long)rightn.toInt32(); - return JSU.N(r); - }else if(kind == JSNumber.N_RATIONAL){ - return JSU.N(leftn.toRational().add(rightn.toRational())); - }else{ - return JSU.N(leftn.toDouble()+rightn.toDouble()); - } - } - - static private JS subtract(JS left, JS right) throws JSExn{ - JSNumber leftn = JSU.expectNumber(left); - JSNumber rightn = JSU.expectNumber(right); - int kind = JSNumber.dominant(leftn, rightn); - if(kind == JSNumber.N_INT32){ - long r = (long)leftn.toInt32()-(long)rightn.toInt32(); - return JSU.N(r); - }else if(kind == JSNumber.N_RATIONAL){ - return JSU.N(leftn.toRational().subtract(rightn.toRational())); - }else{ - return JSU.N(leftn.toDouble()-rightn.toDouble()); - } - } - - static private JS multiply(JS left, JS right) throws JSExn{ - JSNumber leftn = JSU.expectNumber(left); - JSNumber rightn = JSU.expectNumber(right); - int kind = JSNumber.dominant(leftn, rightn); - if(kind == JSNumber.N_INT32){ - long r = (long)leftn.toInt32()*(long)rightn.toInt32(); - return JSU.N(r); - }else if(kind == JSNumber.N_RATIONAL){ - return JSU.N(leftn.toRational().multiply(rightn.toRational())); - }else{ - return JSU.N(leftn.toDouble()*rightn.toDouble()); - } - } - - static private JS divide(JS left, JS right) throws JSExn{ - JSNumber leftn = JSU.expectNumber(left); - JSNumber rightn = JSU.expectNumber(right); - int kind = JSNumber.dominant(leftn, rightn); - if(kind == JSNumber.N_RATIONAL){ - return JSU.N(leftn.toRational().divide(rightn.toRational())); - }else{ - return JSU.N(leftn.toDouble()/rightn.toDouble()); - } - } - - static private JS modulo(JS left, JS right) throws JSExn{ - JSNumber leftn = JSU.expectNumber(left); - JSNumber rightn = JSU.expectNumber(right); - int kind = JSNumber.dominant(leftn, rightn); - if(kind == JSNumber.N_INT32){ - int r = leftn.toInt32()%rightn.toInt32(); - return JSU.N(r); - }else if(kind == JSNumber.N_RATIONAL){ - return JSU.N(leftn.toRational().mod(rightn.toRational())); - }else{ - return JSU.N(leftn.toDouble()%rightn.toDouble()); - } - } - - public String toString(){ - return getWhere(); - } -} - -/* FOOTNOTES - * 1. Automatic cascading has been ripped out. Fundamentally unclean, and only - * really there as syntactic sugar, even dubious on that level. - * - * (Automatic cascading is a mechanism for making traps listener like... i.e. - * though do not effect the put operation, they just do something when one happens. - * In practise it makes implementation harder, the resulting JS code becomes less - * clear, and the same effect can be had in JS with the addition of a 'cascade'.) - * - * - * 2. 'pause on cascade' is a mechanism with which at the - * final cascade (i.e. no more traps to be fired) the interpreter gives up control - * (returns out) to let the original calling code decide what to do. - * - * This is necessary to allow (without hacks) - * a) traps in the get(JS) and put(JS,JS) methods as this would cause an infinite loop - * as they call themselves on the final cascade. - * b) speed, as it allows us to store some properties as class fields and not force us - * to go through get/put to modify them. - * c) ignoring of modified values/not putting a value at all (i.e. its just a virtual - * property that traps can 'listen' to). - * (use cases are in org.vexi.core.Box.jpp) - */ - Added: branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgs.jpp =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgs.jpp (rev 0) +++ branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgs.jpp 2015-02-16 01:01:31 UTC (rev 4763) @@ -0,0 +1,26 @@ +package org.ibex.js; + +class JSArgs extends JS.Immutable implements JSArrayLike{ + private final JS[] args; + private final JS callee; + private final JS this_; + + public JSArgs(JS[] args, JS callee, JS this_) { this.args = args; this.callee = callee; this.this_ = this_; } + + public JS getElement(int i) throws JSExn { return i>=args.length ? null : args[i]; } + public JS get(JS key) throws JSExn { + if(JSU.isInt(key)) { + int i = JSU.toInt32(key); + return getElement(i); + } + //#switch(JSU.toString(key)) + case "callee": return callee; + case "length": return JSU.N(args.length); + case "this": return this_; + //#end + return super.get(key); + } + public JS.Keys keys() throws JSExn { return new JSArrayLike.Keys(this);} + public JS[] toArray() { return args;} + public int size(){ return args.length; } +} Added: branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgsTrap.jpp =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgsTrap.jpp (rev 0) +++ branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/JSArgsTrap.jpp 2015-02-16 01:01:31 UTC (rev 4763) @@ -0,0 +1,28 @@ +package org.ibex.js; + +class JSArgsTrap extends JS.Immutable { + private Trap t; + private JS val; + JS trapname; + public JSArgsTrap(Trap t, JS val, JS trapname) { + this.t = t; this.trapname = trapname; this.val = val; + } + public JS get(JS key) throws JSExn { + if(JSU.isInt(key) && JSU.toInt(key) == 0) return val; + //#switch(JSU.toString(key)) + case "trapee": return t.target(); + case "callee": return t.function(); + case "trapname": return trapname; + case "length": return t.isWriteTrap() ? NC_1 : NC_0; + //#end + return super.get(key); + } + public void put(JS key, JS val) throws JSExn { + String prop = JSU.toString(key); + if(prop.equals("trapname")){ + trapname = val; + }else + super.put(key, val); + } + +} \ No newline at end of file Modified: branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Scope.jpp =================================================================== --- branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Scope.jpp 2015-02-14 21:59:36 UTC (rev 4762) +++ branches/vexi3/org.vexi-library.js/src/main/jpp/org/ibex/js/Scope.jpp 2015-02-16 01:01:31 UTC (rev 4763) @@ -5,7 +5,7 @@ package org.ibex.js; import org.ibex.js.Interpreter.JSArgs; -import org.ibex.js.Interpreter.TrapArgs; +import org.ibex.js.Interpreter.JSArgsTrap; import org.ibex.js.parse.Parser; /** Implementation of a JavaScript Scope */ @@ -58,7 +58,7 @@ // in these scopes. if (i==base) { if (o instanceof JSArgs)mode = MODE_FUNC; - else if(o instanceof TrapArgs)mode = MODE_TRAP; + else if(o instanceof JSArgsTrap)mode = MODE_TRAP; } } Added: branches/vexi3/org.vexi-library.js/src/poke/java/org/ibex/js/JSUX.java =================================================================== --- branches/vexi3/org.vexi-library.js/src/poke/java/org/ibex/js/JSUX.java (rev 0) +++ branches/vexi3/org.vexi-library.js/src/poke/java/org/ibex/js/JSUX.java 2015-02-16 01:01:31 UTC (rev 4763) @@ -0,0 +1,135 @@ +package org.ibex.js; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; + +import org.ibex.js.JS.Enumeration; + +// TODO merge most of this with js project somehow +public class JSUX { + static public boolean isDouble(JS js){ return js instanceof JSNumber.D; } + static public boolean isInt(JS js){ return js instanceof JSNumber.I; } + static public boolean isBool(JS js){ return js instanceof JSBoolean; } + + static public String messageWithTrace(JSExn e){ + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } + + static public String backtrace(JSExn e) { + StringBuilder r = new StringBuilder(); + String msg = e.asObject().getMessage(); + if(msg!=null) + r.append(msg+"\n"); + for (int i=0; i < e.backtrace.size(); i++){ + r.append(" at " + (String) e.backtrace.get(i)); + r.append("\n"); + } + return r.toString(); + } + + static public String backtrace(Interpreter i) { + JSExn e = new JSExn(""); + e.fillIfEmpty(i); + return backtrace(e); + } + + static public Object jsToDynamic(JS js) throws JSExn{ + // VERIFY - can js send every type even if the value + // doesn't put it in that types range (double, but no + // decimal, long with a small value ...) + if(js == null) return null; + if(js instanceof JSNumber){ + return JSU.toNumber((JSNumber)js); + } + if(js instanceof JSBoolean) + return Boolean.valueOf(JSU.toBoolean(js)); + if(js instanceof JSArrayLike){ + JSArrayLike js_ = (JSArrayLike)js; + List r = new ArrayList(js_.size()); + for(int i=0; i<js_.size(); i++){ + r.add(jsToDynamic(js_.get(JSU.N(i)))); + } + return r; + } + if(js instanceof JS.Obj){ + Map r = new HashMap(); + Enumeration I = js.keys().iterator(); + while(I.hasNext()){ + JS keyJS = I.next(); + String key = JSU.toString(keyJS); + r.put(key, jsToDynamic(js.get(keyJS))); + } + return r; + } + return JSU.toString(js); + //throw new JSExn("Cannot convert request: " + js); + } + + // TBD - remerge this with the JSProxy version + static public JS dynamicToJs(Object o) throws JSExn{ + if(o==null) return null; + if(o instanceof Object[]){ + Object[] o_ =(Object[])o; + JSArray r = new JSArray(o_.length); + for(int i=0; i<o_.length; i++){ + r.add(dynamicToJs(o_[i])); + } + return r; + } + if(o instanceof List){ + List o_ =(List)o; + JSArray r = new JSArray(o_.size()); + for(int i=0; i<o_.size(); i++){ + r.add(dynamicToJs(o_.get(i))); + } + return r; + } + if(o instanceof Map){ + Map o_ = (Map)o; + JS r = new JS.Obj(); + Iterator I = o_.keySet().iterator(); + while(I.hasNext()){ + Object k = I.next(); + Object v = o_.get(k); + r.put(dynamicToJs(k), dynamicToJs(v)); + } + return r; + } + if(o instanceof Number){ + return JSU.N((Number)o); + } + if(o instanceof Boolean){ + return JSU.B(((Boolean)o).booleanValue()); + } + if(o instanceof String){ + return JSU.S((String) o); + } + + throw new JSExn("Cannot convert response: " + o); + } + + + static public String format(JSFunction f){ + return f.f.sourceName+":"+f.f.firstLine; + } + + static public Collection<String> listMethods(JS obj) throws JSExn{ + TreeSet<String> methods = new TreeSet(); + for(Object key: obj.keySet()){ + JS value = obj.get((JS)key); + if(value instanceof JSFunction){ + methods.add(key.toString()); + } + } + return methods; + } +} Property changes on: branches/vexi3/org.vexi-library.js/src/poke/java/org/ibex/js/JSUX.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Download BIRT iHub F-Type - The Free Enterprise-Grade BIRT Server from Actuate! Instantly Supercharge Your Business Reports and Dashboards with Interactivity, Sharing, Native Excel Exports, App Integration & more Get technology previously reserved for billion-dollar corporations, FREE http://pubads.g.doubleclick.net/gampad/clk?id=190641631&iu=/4140/ostg.clktrk _______________________________________________ Vexi-svn mailing list Vexi-svn@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/vexi-svn