ljr/wcmtools/s2/danga/s2/NodeVarRef.java

357 lines
9.4 KiB
Java
Executable File

package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class NodeVarRef extends Node
{
public final static int LOCAL = 1;
public final static int OBJECT = 2;
public final static int PROPERTY = 3;
class Deref {
public char type;
public NodeExpr expr;
}
class VarLevel {
public String var;
public LinkedList derefs;
}
LinkedList levels;
boolean braced;
int type = LOCAL;
boolean useAsString = false;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenPunct.DOLLAR))
return true;
return false;
}
public static Node parse (Tokenizer tokermain) throws Exception
{
// voo-doo so tokenizer won't continue parsing a string
// if we're in a string and trying to parse interesting things
// involved in a VarRef:
NodeVarRef n = new NodeVarRef();
n.levels = new LinkedList();
n.setStart(n.requireToken(tokermain, TokenPunct.DOLLAR, false));
Tokenizer toker = tokermain.inString == 0 ?
tokermain : tokermain.getVarTokenizer();
if (toker.peekChar() == '{') {
n.requireToken(toker, TokenPunct.LBRACE, false);
n.braced = true;
} else {
n.braced = false;
}
if (toker.peekChar() == '.') {
n.requireToken(toker, TokenPunct.DOT, false);
n.type = OBJECT;
} else if (toker.peekChar() == '*') {
n.requireToken(toker, TokenPunct.MULT, false);
n.type = PROPERTY;
}
boolean requireDot = false;
// only peeking at characters, not tokens, otherwise
// we could force tokens could be created in the wrong
// context.
while (TokenIdent.canStart(toker) ||
toker.peekChar() == '.') {
if (requireDot) {
n.requireToken(toker, TokenPunct.DOT, false);
} else {
requireDot = true;
}
TokenIdent ident = (TokenIdent) n.getIdent(toker, true, false);
VarLevel vl = n.new VarLevel();
vl.var = ident.getIdent();
vl.derefs = new LinkedList();
// more preventing of token peeking:
while (toker.peekChar() == '[' ||
toker.peekChar() == '{') {
Deref dr = n.new Deref();
Token t = n.eatToken(toker, false);
if (t.equals(TokenPunct.LBRACK)) {
dr.type = '[';
n.addNode(dr.expr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.RBRACK, false);
} else if (t.equals(TokenPunct.LBRACE)) {
dr.type = '{';
n.addNode(dr.expr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.RBRACE, false);
} else {
throw new Exception("shouldn't get here");
}
vl.derefs.add(dr);
}
n.levels.add(vl);
} // end while
// did we parse just $ ?
if (n.levels.size() == 0) {
throw new Exception("Malformed variable reference at "+
n.getFilePos());
}
if (n.braced) {
// false argument necessary to prevent peeking at token
// stream while it's in the interpolated variable parsing state,
// else the string text following the variable would be
// treated as if it were outside the string.
n.requireToken(toker, TokenPunct.RBRACE, false);
}
// now we must skip white space that requireToken above would've
// done had we not told it not to, but not if the main tokenizer
// is in a quoted string
if (tokermain.inString == 0) {
n.skipWhite(toker);
}
return n;
}
// if told by NodeTerm.java, add another varlevel to point to
// this object's $.as_string
public void useAsString ()
{
VarLevel vl = new VarLevel();
vl.var = "as_string";
vl.derefs = new LinkedList(); // empty
levels.add(vl);
}
public boolean isHashElement ()
{
if (type != OBJECT && type != LOCAL)
return false;
// need to get the last deref of the last varlevel
if (levels.size() == 0)
return false;
VarLevel l = (VarLevel) levels.getLast();
if (l.derefs.size() == 0)
return false;
Deref d = (Deref) l.derefs.getLast();
return d.type == '{';
}
public Type getType (Checker ck, Type wanted) throws Exception
{
Type t = getType(ck);
if (wanted == null) return t;
if (! wanted.equals(Type.STRING)) return t;
String type = t.toString();
if (ck.classHasAsString(type)) {
useAsString = true;
return Type.STRING;
}
return t;
}
public Type getType (Checker ck) throws Exception
{
// must have at least reference something.
if (levels.size() == 0) return null;
ListIterator levi = levels.listIterator();
VarLevel lev = (VarLevel) levi.next();
Type vart = null;
// properties
if (type == PROPERTY) {
vart = ck.propertyType(lev.var);
if (vart == null)
throw new Exception("Unknown property at "+getFilePos());
vart = (Type) vart.clone();
}
// local variables.
if (type == LOCAL) {
vart = (Type) ck.localType(lev.var);
if (vart == null) {
throw new Exception("Unknown local variable $"+lev.var+" at "+
getFilePos());
}
}
// properties & locals
if (type == PROPERTY || type == LOCAL) {
vart = (Type) vart.clone(); // since we'll be modifying it
// dereference [] and {} stuff
doDerefs(ck, lev.derefs, vart);
// if no more levels, return now. otherwise deferencing
// happens below.
if (! levi.hasNext()) {
return vart;
} else {
lev = (VarLevel) levi.next();
}
}
// initialize the name of the current object
if (type == OBJECT) {
String curclass = ck.getCurrentFunctionClass();
if (curclass == null) {
throw new Exception("Can't reference member variable in non-method "+
"function at "+getFilePos());
}
vart = new Type(curclass);
}
while (lev != null) {
NodeClass nc = ck.getClass(vart.toString());
if (nc == null)
throw new Exception("Can't use members of undefined class "+vart+" at "+getFilePos());
vart = nc.getMemberType(lev.var);
if (vart == null) {
throw new Exception("Can't find member '"+lev.var+"' in '"+nc.getName()+"'");
}
vart = (Type) vart.clone();
// dereference [] and {} stuff
doDerefs(ck, lev.derefs, vart);
lev = levi.hasNext() ? (VarLevel) levi.next() : null;
}
return vart;
}
private void doDerefs (Checker ck, LinkedList derefs, Type vart) throws Exception
{
// remove [] and {} references
ListIterator lm = derefs.listIterator();
while (lm.hasNext()) {
Deref d = (Deref) lm.next();
Type et = d.expr.getType(ck);
if (d.type == '{') {
if (! vart.isHashOf()) {
throw new Exception("Can't dereference a non-hash as a hash at "+
getFilePos());
}
if (! (et.equals(Type.STRING) || et.equals(Type.INT))) {
throw new Exception("Must deference a hash with a string or "+
"int, not a "+et+" at "+getFilePos());
}
vart.removeMod(); // not a hash anymore
} else if (d.type == '[') {
if (! vart.isArrayOf()) {
throw new Exception("Can't dereference a non-array as an array at "+
getFilePos());
}
if (! et.equals(Type.INT)) {
throw new Exception("Must deference an array with an integer, "+
"not a "+et+" at "+getFilePos());
}
vart.removeMod(); // not an array anymore
}
}
}
private String typeChar () {
if (type == OBJECT) return ".";
if (type == PROPERTY) return "*";
return "";
}
public void asS2 (Indenter o)
{
o.write("$" + typeChar());
ListIterator li = levels.listIterator(0);
boolean first = true;
while (li.hasNext()) {
VarLevel lev = (VarLevel) li.next();
if (! first) o.write("."); else first = false;
o.write(lev.var);
ListIterator dli = lev.derefs.listIterator(0);
while (dli.hasNext()) {
Deref d = (Deref) dli.next();
if (d.type == '[') { o.write("["); }
if (d.type == '{') { o.write("{"); }
d.expr.asS2(o);
if (d.type == '[') { o.write("]"); }
if (d.type == '{') { o.write("}"); }
}
} // end levels
}
// is this variable $super ?
public boolean isSuper ()
{
if (type != LOCAL) return false;
if (levels.size() > 1) return false;
VarLevel v = (VarLevel) levels.getFirst();
return (v.var.equals("super") &&
v.derefs.size() == 0);
}
public void asPerl (BackendPerl bp, Indenter o)
{
ListIterator li = levels.listIterator();
boolean first = true;
if (type == LOCAL) {
o.write("$");
} else if (type == OBJECT) {
o.write("$this");
} else if (type == PROPERTY) {
o.write("$_ctx->[PROPS]");
first = false;
}
while (li.hasNext()) {
VarLevel lev = (VarLevel) li.next();
if (! first || type == OBJECT) {
o.write("->{'" + lev.var + "'}");
} else {
String v = lev.var;
if (first && type == LOCAL && lev.var.equals("super"))
v = "this";
o.write(v);
first = false;
}
ListIterator dli = lev.derefs.listIterator(0);
while (dli.hasNext()) {
o.write("->");
Deref d = (Deref) dli.next();
if (d.type == '[') { o.write("["); }
if (d.type == '{') { o.write("{"); }
d.expr.asPerl(bp, o);
if (d.type == '[') { o.write("]"); }
if (d.type == '{') { o.write("}"); }
}
} // end levels
if (useAsString) {
o.write("->{'as_string'}");
}
}
};