ljr/wcmtools/s2/danga/s2/NodeTerm.java

718 lines
20 KiB
Java
Executable File

package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class NodeTerm extends NodeExpr
{
int type = 0;
public final static int INTEGER = 1;
TokenIntegerLiteral tokInt;
public final static int STRING = 2;
TokenStringLiteral tokStr;
Node nodeString;
String ctorclass; // if not null, then we're not a string, but calling a class ctor with a string
public final static int BOOL = 3;
boolean boolValue;
public final static int VARREF = 4;
NodeVarRef var;
public final static int SUBEXPR = 5;
public final static int DEFINEDTEST = 6;
public final static int SIZEFUNC = 7;
public final static int REVERSEFUNC = 8;
public final static int ISNULLFUNC = 12;
NodeExpr subExpr;
Type subType; // for backend, set by getType()
public final static int NEW = 9;
public final static int NEWNULL = 13; // Like NEW, but sets object to be null
TokenIdent newClass;
public final static int FUNCCALL = 10;
public final static int METHCALL = 11;
int derefLine; // keep track of where we saw the deref token
TokenIdent funcIdent;
String funcClass; // null or classname of the method call
NodeArguments funcArgs;
boolean funcBuiltin; // is this function call a builtin?
// (if so, don't use vtable)
boolean parentMethod; // is this a method call on a super class? if so,
// can't optimize method call since instance class won't
// necessarily be known until run-time (without a lot of analysis)
String funcID; // used by backend; set after getType()
String funcID_noclass;
int funcNum; // used by perl backend for function vtables
public final static int ARRAY = 14;
public static boolean canStart (Tokenizer toker) throws Exception
{
Token t = toker.peek();
if (t instanceof TokenIntegerLiteral ||
t instanceof TokenStringLiteral ||
t instanceof TokenIdent ||
t.equals(TokenPunct.LPAREN) ||
t.equals(TokenPunct.DOLLAR) ||
t.equals(TokenKeyword.DEFINED) ||
t.equals(TokenKeyword.TRUE) ||
t.equals(TokenKeyword.FALSE) ||
t.equals(TokenKeyword.NEW) ||
t.equals(TokenKeyword.SIZE) ||
t.equals(TokenKeyword.REVERSE) ||
t.equals(TokenKeyword.ISNULL) ||
t.equals(TokenKeyword.NULL) ||
t.equals(TokenPunct.LBRACK) ||
t.equals(TokenPunct.LBRACE)
)
return true;
return false;
}
public Type getType (Checker ck) throws Exception
{
return getType(ck, null);
}
public Type getType (Checker ck, Type wanted) throws Exception
{
if (type == INTEGER) return Type.INT;
if (type == STRING) {
if (nodeString != null) {
return nodeString.getType(ck, Type.STRING);
}
if (ck.isStringCtor(wanted)) {
ctorclass = wanted.baseType();
return wanted;
}
return Type.STRING;
}
if (type == SUBEXPR) return subExpr.getType(ck);
if (type == BOOL) return Type.BOOL;
if (type == DEFINEDTEST) {
System.err.println("FIXME: check type of defined expression");
return Type.BOOL;
}
if (type == SIZEFUNC) {
subType = subExpr.getType(ck);
if (subType.equals(Type.STRING))
return Type.INT;
if (subType.isArrayOf())
return Type.INT;
// complain
throw new Exception("Can't use size on expression that's "+
"not a string or array at "+getFilePos());
}
if (type == REVERSEFUNC) {
subType = subExpr.getType(ck);
// reverse a string
if (subType.equals(Type.STRING))
return Type.STRING;
// reverse an array
if (subType.isArrayOf())
return subType;
// complain
throw new Exception("Can't use reverse on expression that's "+
"not a string or array at "+getFilePos());
}
if (type == ISNULLFUNC) {
subType = subExpr.getType(ck);
if (subExpr instanceof NodeTerm) {
NodeTerm nt = (NodeTerm) subExpr;
if (nt.type != VARREF && nt.type != FUNCCALL && nt.type != METHCALL)
throw new Exception("isnull must only be used on an object variable, "+
"function call or method call at "+getFilePos());
} else {
throw new Exception("isnull must only be used on an object variable, "+
"function call or method call at "+getFilePos());
}
// can't be used on arrays and hashes
if (subType.isArrayOf() || subType.isHashOf())
throw new Exception("Can't use isnull on an array or hash at "+getFilePos());
// not primitive types either
if (subType.equals(Type.BOOL) || subType.equals(Type.STRING) || subType.equals(Type.INT))
throw new Exception("Can't use isnull on primitive types at "+getFilePos());
// nor void
if (subType.equals(Type.VOID))
throw new Exception("Can't use isnull on a void value at "+getFilePos());
return Type.BOOL;
}
if (type == NEW || type == NEWNULL) {
String clas = newClass.getIdent();
NodeClass nc = ck.getClass(clas);
if (nc == null) {
throw new Exception("Can't instantiate unknown class at "+
getFilePos());
}
return new Type(clas);
}
if (type == VARREF) {
if (! ck.getInFunction()) {
throw new Exception("Can't reference a variable outside of a function at "+getFilePos());
}
return var.getType(ck, wanted);
}
if (type == METHCALL || type == FUNCCALL) {
if (! ck.getInFunction()) {
throw new Exception("Can't call a function or method outside of a function at "+getFilePos());
}
// find the classname of the variable the method was being called on
if (type == METHCALL) {
Type vartype = var.getType(ck);
if (! vartype.isSimple()) {
throw new Exception("Cannot call a method on an array or hash of "+
"objects at "+getFilePos());
}
funcClass = vartype.toString();
NodeClass methClass = ck.getClass(funcClass);
if (methClass == null) {
throw new Exception("Can't call a method on an instance of an "+
"undefined class at "+getFilePos());
}
if (ck.hasDerClasses(funcClass))
parentMethod = true;
}
funcID = Checker.functionID(funcClass, funcIdent.getIdent(),
funcArgs.typeList(ck));
funcBuiltin = ck.isFuncBuiltin(funcID);
// and remember the funcID without a class for use later when
// we have to generate a funcID at run-time by concatenating
// this to the end of a $instance->{'_type'}
funcID_noclass = Checker.functionID(null, funcIdent.getIdent(),
funcArgs.typeList(ck));
Type t = ck.functionType(funcID);
if (! funcBuiltin)
funcNum = ck.functionNum(funcID);
if (t == null) {
throw new Exception("Unknown function "+funcID+" at "+
funcIdent.getFilePos());
}
return t;
}
if (type == ARRAY) {
return subExpr.getType(ck, wanted);
}
throw new Exception("ERROR: unknown NodeTerm type at "+getFilePos());
}
public boolean isLValue ()
{
if (type == VARREF) return true;
if (type == SUBEXPR) {
return subExpr.isLValue();
}
return false;
}
public boolean makeAsString(Checker ck)
{
if (type == VARREF) {
try {
Type t = var.getType(ck);
if (t.isSimple()) {
String bt = t.baseType();
// class has .toString() method
if (ck.classHasToString(bt)) {
// let's change this VARREF into a METHCALL!
// warning: ugly hacks ahead...
type = METHCALL;
funcIdent = new TokenIdent("toString");
funcClass = bt;
funcArgs = (NodeArguments)
NodeArguments.makeEmptyArgs();
funcID_noclass = "toString()";
funcID = bt + "::" + funcID_noclass;
funcBuiltin = ck.isFuncBuiltin(funcID);
funcNum = ck.functionNum(funcID);
if (ck.hasDerClasses(funcClass))
parentMethod = true;
return true;
}
// class has $.as_string string member
if (ck.classHasAsString(bt)) {
var.useAsString();
return true;
}
}
} catch (Exception e) {
return false;
}
}
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeTerm nt = new NodeTerm();
Token t = toker.peek();
// integer literal
if (t instanceof TokenIntegerLiteral) {
nt.type = NodeTerm.INTEGER;
nt.tokInt = (TokenIntegerLiteral) nt.eatToken(toker);
return nt;
}
// boolean literal
if (t.equals(TokenKeyword.TRUE) || t.equals(TokenKeyword.FALSE)) {
nt.type = NodeTerm.BOOL;
nt.boolValue = t.equals(TokenKeyword.TRUE);
nt.eatToken(toker);
return nt;
}
// string literal
if (t instanceof TokenStringLiteral) {
TokenStringLiteral ts = (TokenStringLiteral) t;
int ql = ts.getQuotesLeft();
int qr = ts.getQuotesRight();
if (qr != 0) {
// whole string literal
nt.type = NodeTerm.STRING;
nt.tokStr = (TokenStringLiteral) nt.eatToken(toker);
nt.setStart(nt.tokStr);
return nt;
}
// interpolated string literal (turn into a subexpr)
LinkedList toklist = new LinkedList();
nt.type = NodeTerm.STRING;
nt.tokStr = (TokenStringLiteral) nt.eatToken(toker);
toklist.add(nt.tokStr.clone()); // cloned before it's changed.
nt.tokStr.setQuotesRight(ql);
Node lhs = nt;
FilePos filepos = (FilePos) nt.tokStr.getFilePos();
boolean loop = true;
while (loop) {
Node rhs = null;
Token tok = toker.peek();
if (tok instanceof TokenStringLiteral) {
rhs = new NodeTerm();
NodeTerm rhsnt = (NodeTerm) rhs;
ts = (TokenStringLiteral) tok;
rhsnt.type = NodeTerm.STRING;
rhsnt.tokStr = (TokenStringLiteral) rhsnt.eatToken(toker);
toklist.add(rhsnt.tokStr.clone()); // cloned before it's changed.
if (ts.getQuotesRight() == ql) {
loop = false;
}
ts.setQuotesRight(ql);
ts.setQuotesLeft(ql);
}
else if (tok.equals(TokenPunct.DOLLAR)) {
rhs = NodeTerm.parse(toker);
toklist.add(rhs);
}
else {
throw new Exception("Error parsing "+
"interpolated string.");
}
// don't make a sum out of a blank string on either side
boolean join = true;
if (lhs instanceof NodeTerm) {
NodeTerm lhst = (NodeTerm) lhs;
if (lhst.type == STRING &&
lhst.tokStr.getString().length() == 0) {
lhs = rhs;
join = false;
}
}
if (rhs instanceof NodeTerm) {
NodeTerm rhst = (NodeTerm) rhs;
if (rhst.type == STRING &&
rhst.tokStr.getString().length() == 0) {
join = false;
}
}
if (join) {
lhs = new NodeSum(lhs, TokenPunct.PLUS, rhs);
}
}
lhs.setTokenList(toklist);
lhs.setStart(filepos);
NodeTerm rnt = new NodeTerm();
rnt.type = NodeTerm.STRING;
rnt.nodeString = lhs;
rnt.addNode(lhs);
return rnt;
}
// Sub-expression (in parenthesis)
if (t.equals(TokenPunct.LPAREN)) {
nt.type = NodeTerm.SUBEXPR;
nt.setStart(nt.eatToken(toker));
nt.subExpr = (NodeExpr) NodeExpr.parse(toker);
nt.addNode(nt.subExpr);
nt.requireToken(toker, TokenPunct.RPAREN);
return nt;
}
// defined test
if (t.equals(TokenKeyword.DEFINED)) {
nt.type = NodeTerm.DEFINEDTEST;
nt.eatToken(toker);
nt.subExpr = (NodeTerm) NodeTerm.parse(toker);
nt.addNode(nt.subExpr);
return nt;
}
// reverse function
if (t.equals(TokenKeyword.REVERSE)) {
nt.type = NodeTerm.REVERSEFUNC;
nt.eatToken(toker);
nt.subExpr = (NodeTerm) NodeTerm.parse(toker);
nt.addNode(nt.subExpr);
return nt;
}
// size function
if (t.equals(TokenKeyword.SIZE)) {
nt.type = NodeTerm.SIZEFUNC;
nt.eatToken(toker);
nt.subExpr = (NodeTerm) NodeTerm.parse(toker);
nt.addNode(nt.subExpr);
return nt;
}
// isnull function
if (t.equals(TokenKeyword.ISNULL)) {
nt.type = NodeTerm.ISNULLFUNC;
nt.eatToken(toker);
nt.subExpr = (NodeTerm) NodeTerm.parse(toker);
nt.addNode(nt.subExpr);
return nt;
}
// new and null
if (t.equals(TokenKeyword.NEW) || t.equals(TokenKeyword.NULL)) {
nt.type = (t.equals(TokenKeyword.NEW) ? NodeTerm.NEW : NodeTerm.NEWNULL);
nt.eatToken(toker);
nt.newClass = nt.getIdent(toker);
return nt;
}
// VarRef
if (t.equals(TokenPunct.DOLLAR)) {
nt.type = VARREF;
nt.var = (NodeVarRef) NodeVarRef.parse(toker);
nt.addNode(nt.var);
// check for -> after, like: $object->method(arg1, arg2, ...)
if (toker.peek().equals(TokenPunct.DEREF)) {
nt.derefLine = toker.peek().getFilePos().line;
nt.eatToken(toker);
nt.type = METHCALL;
// don't return... parsing continues below.
} else {
return nt;
}
}
// function/method call
if (t instanceof TokenIdent || nt.type == METHCALL) {
if (nt.type != METHCALL) nt.type = FUNCCALL;
nt.funcIdent = nt.getIdent(toker);
nt.funcArgs = (NodeArguments) NodeArguments.parse(toker);
nt.addNode(nt.funcArgs);
return nt;
}
// array/hash literal
if (NodeArrayLiteral.canStart(toker)) {
nt.type = ARRAY;
nt.subExpr = (NodeExpr) NodeArrayLiteral.parse(toker);
nt.addNode(nt.subExpr);
return nt;
}
throw new Exception("Can't finish parsing NodeTerm at " +
toker.locationString() +
", toker.peek() = " + t.toString());
}
public void asS2 (Indenter o)
{
if (type == INTEGER) {
tokInt.asS2(o);
return;
}
if (type == STRING) {
if (nodeString != null) {
nodeString.asS2(o);
return;
}
tokStr.asS2(o);
return;
}
if (type == BOOL) {
if (boolValue)
o.write("true");
else
o.write("false");
return;
}
if (type == SUBEXPR) {
o.write("(");
subExpr.asS2(o);
o.write(")");
return;
}
if (type == NEW) {
o.write("new ");
o.write(newClass.getIdent());
return;
}
if (type == NEWNULL) {
o.write("null ");
o.write(newClass.getIdent());
return;
}
if (type == DEFINEDTEST) {
o.write("defined ");
subExpr.asS2(o);
return;
}
if (type == SIZEFUNC) {
o.write("size ");
subExpr.asS2(o);
return;
}
if (type == REVERSEFUNC) {
o.write("reverse ");
subExpr.asS2(o);
return;
}
if (type == ISNULLFUNC) {
o.write("isnull ");
subExpr.asS2(o);
return;
}
if (type == VARREF || type == METHCALL) {
var.asS2(o);
}
if (type == METHCALL) {
o.write("->");
}
if (type == METHCALL || type == FUNCCALL) {
o.write(funcIdent.getIdent());
funcArgs.asS2(o);
}
if (type == VARREF || type == METHCALL || type == FUNCCALL)
return;
if (type == ARRAY) {
subExpr.asS2(o);
return;
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (type == INTEGER) {
tokInt.asPerl(bp, o);
return;
}
if (type == STRING) {
if (nodeString != null) {
o.write("(");
nodeString.asPerl(bp, o);
o.write(")");
return;
}
if (ctorclass != null)
o.write("S2::Builtin::"+ctorclass+"__"+ctorclass+"(");
tokStr.asPerl(bp, o);
if (ctorclass != null)
o.write(")");
return;
}
if (type == BOOL) {
if (boolValue)
o.write("1");
else
o.write("0");
return;
}
if (type == SUBEXPR) {
o.write("(");
subExpr.asPerl(bp, o);
o.write(")");
return;
}
if (type == ARRAY) {
subExpr.asPerl(bp, o);
return;
}
if (type == NEW) {
o.write("{'_type'=>" +
bp.quoteString(newClass.getIdent())+
"}");
return;
}
if (type == NEWNULL) {
o.write("{'_type'=>" +
bp.quoteString(newClass.getIdent())+
", '_isnull'=>1}");
return;
}
if (type == DEFINEDTEST) {
o.write("defined(");
subExpr.asPerl(bp, o);
o.write(")");
return;
}
if (type == REVERSEFUNC) {
if (subType.isArrayOf()) {
o.write("[reverse(");
o.write("@{");
subExpr.asPerl(bp, o);
o.write("})");
o.write("]");
} else if (subType.equals(Type.STRING)) {
o.write("reverse(");
subExpr.asPerl(bp, o);
o.write(")");
}
return;
}
if (type == SIZEFUNC) {
if (subType.equals(Type.STRING)) {
o.write("length(");
subExpr.asPerl(bp, o);
o.write(")");
}
else if (subType.isArrayOf()) {
o.write("scalar(@{");
subExpr.asPerl(bp, o);
o.write("})");
}
return;
}
if (type == ISNULLFUNC) {
o.write("(ref ");
subExpr.asPerl(bp, o);
o.write(" ne \"HASH\" || ");
subExpr.asPerl(bp, o);
o.write("->{'_isnull'})");
return;
}
if (type == VARREF) {
var.asPerl(bp, o);
return;
}
if (type == FUNCCALL || type == METHCALL) {
boolean funcDumped = false;
// builtin functions can be optimized.
if (funcBuiltin) {
// these built-in functions can be inlined.
if (funcID.equals("string(int)")) {
funcArgs.asPerl(bp, o, false);
return;
}
if (funcID.equals("int(string)")) {
// cast from string to int by adding zero to it
o.write("(0+");
funcArgs.asPerl(bp, o, false);
o.write(")");
return;
}
// otherwise, call the builtin function (avoid a layer
// of indirection), unless it's for a class that has
// children (won't know until run-time which class to call)
if(funcClass == null || (funcClass != null && ! parentMethod)) {
o.write("S2::Builtin::");
if (funcClass != null) {
o.write(funcClass + "__");
}
o.write(funcIdent.getIdent());
funcDumped = true;
}
}
if (funcDumped == false) {
if (type == METHCALL && ! funcClass.equals("string")) {
o.write("$_ctx->[VTABLE]->{get_object_func_num(");
o.write(bp.quoteString(funcClass));
o.write(",");
var.asPerl(bp, o);
o.write(",");
o.write(bp.quoteString(funcID_noclass));
o.write(",");
o.write(bp.getLayerID());
o.write(",");
o.write(derefLine);
if (var.isSuper()) {
o.write(",1");
}
o.write(")}->");
} else if (type == METHCALL) {
o.write("$_ctx->[VTABLE]->{get_func_num(");
o.write(bp.quoteString(funcID));
o.write(")}->");
} else {
o.write("$_ctx->[VTABLE]->{$_l2g_func["+funcNum+"]}->");
}
}
o.write("($_ctx, ");
// this pointer
if (type == METHCALL) {
var.asPerl(bp, o);
o.write(", ");
}
funcArgs.asPerl(bp, o, false);
o.write(")");
return;
}
}
}