package org.xwt.js;
import org.xwt.util.*;
-
+
/** The public API for the JS engine */
+// FEATURE: try using mutable, recycled 'Num' objects
public interface JS {
- public Object get(Object key) throws JS.Exn;
- public Object put(Object key, Object val) throws JS.Exn;
- public Object[] enumerateProperties();
- public String coerceToString() throws JS.Exn;
- /*
- public Num coerceToNumber() throws JS.Exn;
- public boolean coerceToBoolean() throws JS.Exn;
- public Object call(Object[] args) throws JS.Exn;
- */
+ public abstract Object get(Object key) throws JS.Exn;
+ public abstract void put(Object key, Object val) throws JS.Exn;
+ public abstract Object[] enumerateProperties();
+
+ public abstract Number coerceToNumber() throws JS.Exn;
+ public abstract boolean coerceToBoolean() throws JS.Exn;
+ public abstract String coerceToString() throws JS.Exn;
+
+ public static class Obj implements JS {
+ private Hash entries;
+ public Obj() { }
+ public Object get(Object key) { return entries.get(key); }
+ public void put(Object key, Object val) { entries.put(key, val); }
+ public Object[] enumerateProperties() { return(entries.keys()); }
+ public Number coerceToNumber() { throw new Error("tried to turn a Object into a Number"); }
+ public String coerceToString() { throw new Error("tried to turn a Object into a String"); }
+ public boolean coerceToBoolean() { throw new Error("tried to turn a Object into a Boolean"); }
+ }
+
+ public static class Array extends Obj {
+ private Vec vec = new Vec();
+ private static int intVal(Object o) {
+ if (o instanceof Number) {
+ int intVal = ((Number)o).intValue();
+ if (intVal == ((Number)o).doubleValue()) return intVal;
+ return Integer.MIN_VALUE;
+ }
+ if (!(o instanceof String)) return Integer.MIN_VALUE;
+ String s = (String)o;
+ for(int i=0; i<s.length(); i++) if (s.charAt(i) < '0' || s.charAt(i) > '9') return Integer.MIN_VALUE;
+ return Integer.parseInt(s);
+ }
+ public Object get(Object key) {
+ if (key.equals("length")) return new Long(vec.size());
+ int i = intVal(key);
+ if (i == Integer.MIN_VALUE) return super.get(key);
+ return vec.elementAt(i);
+ }
+ public void put(Object key, Object val) {
+ if (key.equals("length")) vec.setSize(Expr.toNumber(val).intValue());
+ int i = intVal(key);
+ if (i == Integer.MIN_VALUE) super.put(key, val);
+ else vec.setElementAt(val, i);
+ }
+ public Object[] enumerateProperties() { return null; } // FIXME
+ void addElement(Object o) { vec.addElement(o); }
+ }
+
+ public static interface Function extends JS {
+ public abstract Object call(JS.Array args) throws JS.Exn; // FEATURE: try to recycle these arrays?
+ public abstract Scope getParentScopeOfDeclaration();
+ }
+
+ public static abstract class ObjFunction extends Obj implements Function { }
+
/** if JS calls a Java method, and the Java method throws an exception, it can only be caught by JS if it is a subclass of Exn. */
public static class Exn extends RuntimeException {
private Object js = null;
public Exn(Object js) { this.js = js; }
public Object getObject() { return js; }
}
-
+
/** Any object which becomes part of the scope chain must support this interface */
- public static interface Scope extends JS {
- public boolean has(Object key);
- public void declare(String s);
- public JS getParentScope();
+ public static class Scope extends Obj {
+ private Scope parentScope;
+ private static Object NULL = new Object();
+ public Scope(Scope parentScope) { this.parentScope = parentScope; }
+ public JS getParentScope() { return parentScope; }
+ public boolean has(Object key) { return super.get(key) != null; }
+ public Object get(Object key) {
+ if (!has(key)) return getParentScope().get(key);
+ Object ret = super.get(key); return ret == NULL ? null : ret;
+ }
+ public void put(Object key, Object val) {
+ if (!has(key)) getParentScope().put(key, val);
+ else super.put(key, val == null ? NULL : val);
+ }
+ public Object[] enumerateProperties() { throw new Error("you can't enumerate the properties of a Scope"); }
+ public void declare(String s) { super.put(s, NULL); }
}
- /** A mutable, boxed numeric value. These are recycled -- never duplicate references -- use duplicate() instead. */
- /*
- public static class Num implements Cloneable, JS {
-
- private Num() { }
- public boolean isDouble = false;
- public long longVal = -1;
- public double doubleVal = -1;
-
- private static Vec pool = new Vec();
- public static synchronized void recycle(Num n) { pool.push(n); }
- public static synchronized Num getOne() { return (pool.size() > 0) ? (Num)pool.pop() : new Num(); }
-
- public Num duplicate() { try { return (Num)clone(); } catch (CloneNotSupportedException c) { throw new Error(c); } }
-
- public Object get(Object key) throws JS.Exn { return null; }
- public Object put(Object key, Object val) throws JS.Exn { throw new JS.Exn("attempt to set a property on a Number"); }
- public Object[] enumerateProperties() { return new Object[] { }; }
- public String coerceToString() throws JS.Exn { return isDouble ? String.valueOf(doubleVal) : String.valueOf(longVal); }
- public Num coerceToNumber() throws JS.Exn { return duplicate(); }
- public Object call(Object[] args) throws JS.Exn { throw new JS.Exn("attempt to apply the () operator to a Number"); }
- }
- */
}
+
+
+
+
import org.xwt.util.*;
import java.io.*;
+
/** parses a stream of lexed tokens into a tree of Expr's */
public class Parser extends Lexer {
Expr tail = null;
OUTER: while(true) {
Expr smt;
+
switch(tok = peekToken()) {
case -1: break OUTER;
case LC: smt = parseBlock(true); break;
smt = new Expr(tok, parseMaximalExpr());
if (getToken() != SEMI) throw new Error("expected ;");
break;
+
case GOTO: case BREAK: case CONTINUE:
getToken();
if (getToken() == NAME)
/** parses the largest possible expression */
public Expr parseMaximalExpr() throws IOException { return parseMaximalExpr(null, -1); }
public Expr parseMaximalExpr(Expr prefix, int minPrecedence) throws IOException {
- Expr save = null;
while(true) {
- save = prefix;
if (peekToken() == -1) break;
+ Expr save = prefix;
prefix = parseSingleExpr(prefix, minPrecedence);
if (save == prefix) break;
- if (prefix == null) throw new Error("parseSingleExpr_() returned null");
+ if (prefix == null) throw new Error("parseSingleExpr() returned null");
}
return prefix;
}
- /** parses the smallest possible complete expression */
- public Expr parseSingleExpr() throws IOException { return parseSingleExpr(null, 0); }
-
- /** parses the smallest possible complete expression beginning with prefix and only using operators with at least minPrecedence */
public Expr parseSingleExpr(Expr prefix, int minPrecedence) throws IOException {
Expr e1 = null, e2 = null, e3 = null, head = null, tail = null, ret = null;
case BITOR: case BITXOR: case BITAND: case SHEQ: case SHNE: case LSH:
case RSH: case URSH: case ADD: case SUB: case MUL: case DIV: case MOD:
case COMMA: case ASSIGN: case GT: case GE: case OR: case AND:
- case EQ: case NE: case LT: case LE: case DOT:
+ case EQ: case NE: case LT: case LE:
return new Expr(tok, prefix, parseMaximalExpr(null, precedence[tok]));
+ case DOT:
+ e1 = parseMaximalExpr(null, precedence[tok]);
+ if (e1.code == NAME) e1.code = STRING;
+ else throw new Error("argument to DOT must be a NAME");
+ return new Expr(DOT, prefix, e1);
+
case BITNOT: case INSTANCEOF:
if (prefix != null) throw new Error("didn't expect non-null prefix!");
return new Expr(tok, parseMaximalExpr(null, precedence[tok]));
case INC: case DEC:
if (prefix == null) {
// prefix
- return new Expr(tok, parseMaximalExpr(null, precedence[tok]));
+ e1 = parseMaximalExpr(null, precedence[tok]);
+ return new Expr(ASSIGN, e1, new Expr(tok == INC ? ADD : SUB, e1, new Expr(new Integer(1))));
} else {
// postfix
- return new Expr(tok, null, prefix);
+ return new Expr(tok, prefix);
}
case LP:
} else { // invocation
while(peekToken() != RP) {
- Expr e = parseMaximalExpr(null, precedence[COMMA]);
+ Expr e = parseMaximalExpr();
if (head == null) head = tail = e; else tail = tail.next = e;
tok = getToken();
if (tok == RP) { pushBackToken(); break; }
// subscripting
e1 = parseMaximalExpr();
if (getToken() != RB) throw new Error("expected a right brace");
- return new Expr(LB, prefix, e1);
+ return new Expr(DOT, prefix, e1);
} else {
// array ctor
tok = getToken();
while(true) {
if (tok == RB) return new Expr(LB, prefix, head);
- if (head == null) head = tail = parseMaximalExpr(null, precedence[COMMA]);
- else tail = tail.next = parseMaximalExpr(null, precedence[COMMA]);
+ if (head == null) head = tail = parseMaximalExpr();
+ else tail = tail.next = parseMaximalExpr();
tok = getToken();
if (tok != COMMA && tok != RP) throw new Error("expected right bracket or comma");
}
if (prefix != null) throw new Error("didn't expect non-null prefix");
tok = getToken();
while(true) {
- if (tok == RC) return new Expr(LC, head);
+ if (tok == RC) return new Expr(RC, head);
if (tok != NAME) throw new Error("expecting name");
expect(NAME); getToken();
Expr name = new Expr(NAME, string);
if (tok != COLON) throw new Error("expecting colon");
- e1 = new Expr(COLON, name, parseMaximalExpr(null, precedence[COMMA]));
+ e1 = new Expr(COLON, name, parseMaximalExpr());
if (head == null) head = tail = e1; else tail = tail.next = e1;
tok = getToken();
if (tok != COMMA && tok != RP) throw new Error("expected right curly or comma");
Expr firstExpr = null;
Expr lastExpr = null;
while(true) {
- if (getToken() != CASE) throw new Error("expected CASE");
- Expr caseExpr = parseMaximalExpr();
- if (getToken() != COLON) throw new Error("expected COLON");
- Expr e = new Expr(CASE, caseExpr, parseBlock(false));
- if (lastExpr == null) firstExpr = e;
- else lastExpr.next = e;
- lastExpr = e;
+ tok = getToken();
+ Expr caseExpr;
+ if (tok == DEFAULT) {
+ caseExpr = null;
+ } else if (tok == CASE) {
+ caseExpr = parseMaximalExpr();
+ } else {
+ throw new Error("expected CASE");
+ }
+ expect(COLON); getToken();
+ e1 = new Expr(tok, caseExpr, parseBlock(false));
+ if (lastExpr == null) firstExpr = e1;
+ else lastExpr.next = e1;
+ lastExpr = e1;
if (getToken() == RC) return new Expr(SWITCH, switchExpr, firstExpr);
}
}
Expr e = null;
if (tok == ASSIGN) {
getToken();
- initVal = parseMaximalExpr(null, precedence[COMMA]);
+ initVal = parseMaximalExpr();
tok = peekToken();
e = new Expr(ASSIGN, name, initVal);
} else {
// We deliberately allow you to omit braces in catch{}/finally{} if they are single statements...
if (prefix != null) throw new Error("didn't expect non-null prefix");
Expr tryBlock = parseBlock(true);
- while ((tok = peekToken()) == CATCH || tok == FINALLY) {
+
+ tok = peekToken();
+ if (tok == CATCH) {
getToken();
if (getToken() != LP) throw new Error("expected (");
if (getToken() != NAME) throw new Error("expected name");
Expr name = new Expr(NAME, string);
if (getToken() != RP) throw new Error("expected )");
- e1 = new Expr(tok, name, parseBlock(false));
+ head = tail = new Expr(CATCH, name, parseBlock(false));
+ tok = peekToken();
+ }
+ if (tok == FINALLY) {
+ getToken();
+ e1 = new Expr(FINALLY, parseBlock(false));
if (head == null) head = tail = e1; else tail = tail.next = e1;
}
+
if (head == null) throw new Error("try without catch or finally");
return new Expr(TRY, tryBlock, head);
}