key = tm.key;
tm.cascadeHappened = true;
t = tm.t;
- if(t.readTrap()) throw new JSExn("can't put to cascade in a read trap");
+ if(t.readTrap()) throw new JSExn("can't do a write cascade in a read trap");
t = t.next;
while(t != null && t.readTrap()) t = t.next;
}
args.addElement(val);
stack.push(args);
f = t.f;
- scope = new JSScope(f.parentScope);
+ scope = new TrapScope(f.parentScope,target,f,key);
pc = -1;
break;
} else {
target = tm.trapee;
key = tm.key;
t = tm.t;
- if(t.writeTrap()) throw new JSExn("can't do a write cascade in a read trap");
+ if(t.writeTrap()) throw new JSExn("can't do a read cascade in a write trap");
t = t.next;
while(t != null && t.writeTrap()) t = t.next;
if(t != null) tm.cascadeHappened = true;
stack.push(new TrapMarker(this,t,(JS)target,key,null));
stack.push(new JSArray());
f = t.f;
- scope = new JSScope(f.parentScope);
+ scope = new TrapScope(f.parentScope,target,f,key);
pc = -1;
break;
} else {
method = stack.pop();
object = stack.pop();
} else if (object == null) {
- Object name = stack.pop();
- stack.pop();
- throw new JSExn("function '"+name+"' not found");
+ method = stack.pop();
+ object = stack.pop();
+ throw new JSExn("function '"+JS.debugToString(method)+"' not found in " + object.getClass().getName());
} else {
stack.pop();
stack.pop();
public FinallyData(JSExn exn) { this.exn = exn; this.op = -1; this.arg = null; } // Just throw this exn
}
+ static class TrapScope extends JSScope {
+ JS trapee;
+ JS callee;
+ JS trapname;
+ public TrapScope(JSScope parent, JS trapee, JS callee, JS trapname) {
+ super(parent); this.trapee = trapee; this.callee = callee; this.trapname = trapname;
+ }
+ public JS get(JS key) throws JSExn {
+ if(JS.isString(key)) {
+ //#switch(JS.toString(key))
+ case "trapee": return trapee;
+ case "callee": return callee;
+ case "trapname": return trapname;
+ //#end
+ }
+ return super.get(key);
+ }
+ }
// Operations on Primitives //////////////////////////////////////////////////////////////////////
public final int indexNode(Object o) { return bt().indexNode(o); }
}
+ // FEATURE: JS.StringKeys
+ /* public static StringKeys extends JS {
+ public JS get(JS key) { return JS.isString(key) ? get(JS.toString(key) : null; }
+ ...
+ */
+
JS _unclone() { return this; }
public interface Cloneable { }
public final int length() { return size(); }
public final JS elementAt(int i) {
if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
- JS o = (JS) getNode(i);
- return o == NULL ? null : o;
+ Object o = getNode(i);
+ return o == NULL ? (JS)null : (JS)o;
}
public final void addElement(JS o) {
insertNode(size(),o==null ? NULL : o);
}
public final JS removeElementAt(int i) {
if(i < 0 || i >= size()) throw new ArrayIndexOutOfBoundsException(i);
- JS o = (JS) deleteNode(i);
- return o == NULL ? null : o;
+ Object o = deleteNode(i);
+ return o == NULL ? (JS)null : (JS)o;
}
public final int size() { return treeSize(); }
--- /dev/null
+package org.ibex.js;
+
+abstract class JSNumber extends JSPrimitive {
+ boolean jsequals(JS o) {
+ if(o == this) return true;
+ if(o instanceof JSNumber) {
+ JSNumber n = (JSNumber) o;
+ if(this instanceof D || n instanceof D) return n.toDouble() == toDouble();
+ return n.toLong() == toLong();
+ } else if(o instanceof JSString) {
+ String s = ((JSString)o).s.trim();
+ try {
+ if(this instanceof D || s.indexOf('.') != -1) return Double.parseDouble(s) == toDouble();
+ return Long.parseLong(s) == toLong();
+ } catch(NumberFormatException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ // FEATURE: Better hash function? (if d != (int) d then do something double specific)
+ public int hashCode() { return toInt(); }
+
+ abstract int toInt();
+ long toLong() { return toInt(); }
+ boolean toBoolean() { return toInt() != 0; }
+ double toDouble() { return toLong(); }
+ float toFloat() { return (float) toDouble(); }
+ // FIXME: Number functions
+
+ final static class I extends JSNumber {
+ final int i;
+ I(int i) { this.i = i; }
+ int toInt() { return i; }
+ public String coerceToString() { return Integer.toString(i); }
+ }
+
+ final static class L extends JSNumber {
+ private final long l;
+ L(long l) { this.l = l; }
+ int toInt() { return (int) l; }
+ long toLong() { return l; }
+ public String coerceToString() { return Long.toString(l); }
+ }
+
+ final static class D extends JSNumber {
+ private final double d;
+ D(double d) { this.d = d; }
+ int toInt() { return (int) d; }
+ long toLong() { return (long) d; }
+ double toDouble() { return d; }
+ boolean toBoolean() { return d == d && d != 0.0; }
+ public String coerceToString() { return d == (long) d ? Long.toString((long)d) : Double.toString(d); }
+ }
+
+ final static class B extends JSNumber {
+ private final boolean b;
+ B(boolean b) { this.b = b; }
+ int toInt() { return b ? 1 : 0; }
+ public String coerceToString() { return b ? "true" : "false"; }
+ }
+}
--- /dev/null
+package org.ibex.js;
+
+class JSPrimitive extends JS {
+ public JS callMethod(JS method_, JS arg0, JS arg1, JS arg2, JS[] rest, int alength) throws JSExn {
+ if(!JS.isString(method_)) return super.callMethod(method_,arg0,arg1,arg2,rest,alength);
+ String method = JS.toString(method_);
+
+ //#switch(method)
+ case "toFixed": throw new JSExn("toFixed() not implemented");
+ case "toExponential": throw new JSExn("toExponential() not implemented");
+ case "toPrecision": throw new JSExn("toPrecision() not implemented");
+ case "toString": return this instanceof JSString ? this : JS.S(JS.toString(this));
+ //#end
+
+ String s = coerceToString();
+ int slength = s.length();
+
+ //#switch(method)
+ case "substring": {
+ int a = alength >= 1 ? JS.toInt(arg0) : 0;
+ int b = alength >= 2 ? JS.toInt(arg1) : slength;
+ if (a > slength) a = slength;
+ if (b > slength) b = slength;
+ if (a < 0) a = 0;
+ if (b < 0) b = 0;
+ if (a > b) { int tmp = a; a = b; b = tmp; }
+ return JS.S(s.substring(a,b));
+ }
+ case "substr": {
+ int start = alength >= 1 ? JS.toInt(arg0) : 0;
+ int len = alength >= 2 ? JS.toInt(arg1) : Integer.MAX_VALUE;
+ if (start < 0) start = slength + start;
+ if (start < 0) start = 0;
+ if (len < 0) len = 0;
+ if (len > slength - start) len = slength - start;
+ if (len <= 0) return JS.S("");
+ return JS.S(s.substring(start,start+len));
+ }
+ case "charAt": {
+ int p = alength >= 1 ? JS.toInt(arg0) : 0;
+ if (p < 0 || p >= slength) return JS.S("");
+ return JS.S(s.substring(p,p+1));
+ }
+ case "charCodeAt": {
+ int p = alength >= 1 ? JS.toInt(arg0) : 0;
+ if (p < 0 || p >= slength) return JS.N(Double.NaN);
+ return JS.N(s.charAt(p));
+ }
+ case "concat": {
+ StringBuffer sb = new StringBuffer(slength*2).append(s);
+ for(int i=0;i<alength;i++) sb.append(i==0?arg0:i==1?arg1:i==2?arg2:rest[i-3]);
+ return JS.S(sb.toString());
+ }
+ case "indexOf": {
+ String search = alength >= 1 ? JS.toString(arg0) : "null";
+ int start = alength >= 2 ? JS.toInt(arg1) : 0;
+ // Java's indexOf handles an out of bounds start index, it'll return -1
+ return JS.N(s.indexOf(search,start));
+ }
+ case "lastIndexOf": {
+ String search = alength >= 1 ? JS.toString(arg0) : "null";
+ int start = alength >= 2 ? JS.toInt(arg1) : 0;
+ // Java's indexOf handles an out of bounds start index, it'll return -1
+ return JS.N(s.lastIndexOf(search,start));
+ }
+ case "match": return JSRegexp.stringMatch(this,arg0);
+ case "replace": return JSRegexp.stringReplace(this,arg0,arg1);
+ case "search": return JSRegexp.stringSearch(this,arg0);
+ case "split": return JSRegexp.stringSplit(this,arg0,arg1,alength);
+ case "toLowerCase": return JS.S(s.toLowerCase());
+ case "toUpperCase": return JS.S(s.toUpperCase());
+ case "slice": {
+ int a = alength >= 1 ? JS.toInt(arg0) : 0;
+ int b = alength >= 2 ? JS.toInt(arg1) : slength;
+ if (a < 0) a = slength + a;
+ if (b < 0) b = slength + b;
+ if (a < 0) a = 0;
+ if (b < 0) b = 0;
+ if (a > slength) a = slength;
+ if (b > slength) b = slength;
+ if (a > b) return JS.S("");
+ return JS.S(s.substring(a,b));
+ }
+ //#end
+ return super.callMethod(method_,arg0,arg1,arg2,rest,alength);
+ }
+
+ public JS get(JS key_) throws JSExn {
+ if(!JS.isString(key_)) return super.get(key_);
+ String key = JS.toString(key_);
+ //#switch(key)
+ case "length": return JS.N(JS.toString(this).length());
+ case "substring": return METHOD;
+ case "charAt": return METHOD;
+ case "charCodeAt": return METHOD;
+ case "concat": return METHOD;
+ case "indexOf": return METHOD;
+ case "lastIndexOf": return METHOD;
+ case "match": return METHOD;
+ case "replace": return METHOD;
+ case "search": return METHOD;
+ case "slice": return METHOD;
+ case "split": return METHOD;
+ case "toLowerCase": return METHOD;
+ case "toUpperCase": return METHOD;
+ case "toString": return METHOD;
+ case "substr": return METHOD;
+ case "toPrecision": return METHOD;
+ case "toExponential": return METHOD;
+ case "toFixed": return METHOD;
+ //#end
+ return super.get(key_);
+ }
+}
}
- public static JS stringSplit(String s, JS arg0, JS arg1, int nargs) throws JSExn {
+ public static JS stringSplit(JS s_, JS arg0, JS arg1, int nargs) throws JSExn {
+ String s = JS.toString(s_);
int limit = nargs < 2 ? Integer.MAX_VALUE : JS.toInt(arg1);
if(limit < 0) limit = Integer.MAX_VALUE;
if(limit == 0) return new JSArray();
--- /dev/null
+package org.ibex.js;
+
+import org.ibex.util.*;
+
+final class JSString extends JSPrimitive {
+ final String s;
+ public JSString(String s) { this.s = s; }
+ public int hashCode() { return s.hashCode(); }
+
+ public boolean jsequals(JS o) {
+ if(o == this) return true;
+ if(o instanceof JSString) {
+ return ((JSString)o).s.equals(s);
+ } else if(o instanceof JSNumber) {
+ return o.jsequals(this);
+ } else {
+ return false;
+ }
+ }
+
+ int length() { return s.length(); }
+
+ private static Hash internHash = new Hash();
+ static synchronized JS intern(String s) {
+ JS js = (JS)internHash.get(s);
+ if(js == null) internHash.put(s,js = new JSString(s));
+ return js;
+ }
+ JS intern() { return intern(s); }
+
+ String coerceToString() { return s; }
+}
// Public Interface //////////////////////////////////////////////////////////////////////////////
+ // FIXME: This should be in JS, "everything has a stream"
public static InputStream getInputStream(JS js) throws IOException { return ((Stream)js.unclone()).getInputStream();}
public static class NotCacheableException extends Exception { }
private Cache getCache = new Cache(100);
- protected JS _get(Object key) { return null; }
- public final JS get(JS key) {
+ // FEATURE: Mandate that Streams use only String keys?
+ protected JS _get(JS key) throws JSExn { return null; }
+ public final JS get(JS key) throws JSExn {
JS ret = (JS) getCache.get(key);
if (ret == null) getCache.put(key, ret = _get(key));
return ret;