This commit is contained in:
2019-02-06 00:49:12 +03:00
commit 8dbb1bb605
4796 changed files with 506072 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
package danga.s2;
public abstract class Backend {
Layer layer;
public abstract void output (Output o);
public static String quoteString (String s)
{
int len = s.length();
StringBuffer sb = new StringBuffer(len + 20);
sb.append("\"");
sb.append(quoteStringInner(s));
sb.append("\"");
return sb.toString();
}
public static String quoteStringInner (String s)
{
int len = s.length();
StringBuffer sb = new StringBuffer(len + 20);
for (int i=0; i<len; i++) {
char c = s.charAt(i);
if (c=='\\' || c=='$' || c=='"')
sb.append('\\');
sb.append(c);
}
return sb.toString();
}
};

View File

@@ -0,0 +1,69 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class BackendHTML extends Backend {
public final static boolean addBreaks = false;
public final static String CommentColor = new String("#008000");
public final static String IdentColor = new String("#000000");
public final static String KeywordColor = new String("#0000FF");
public final static String StringColor = new String("#008080");
public final static String PunctColor = new String("#000000");
public final static String BracketColor = new String("#800080");
public final static String TypeColor = new String("#000080");
public final static String VarColor = new String("#000000");
public final static String IntegerColor = new String("#000000");
public BackendHTML (Layer l) {
layer = l;
}
public void output (Output o) {
String layername = (s2compile.topLayerName == null ?
"untitled layer" :
s2compile.topLayerName);
o.write("<html><head><title>Source for "+layername+"</title>\n");
o.write("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
o.write("<style type=\"text/css\">\n");
o.write("body { background: #ffffff none; color: #000000; }\n");
o.write(".c { background: #ffffff none; color: "+CommentColor+"; }\n");
o.write(".i { background: #ffffff none; color: "+IdentColor+"; }\n");
o.write(".k { background: #ffffff none; color: "+KeywordColor+"; }\n");
o.write(".s { background: #ffffff none; color: "+StringColor+"; }\n");
o.write(".p { background: #ffffff none; color: "+PunctColor+"; }\n");
o.write(".b { background: #ffffff none; color: "+BracketColor+"; }\n");
o.write(".t { background: #ffffff none; color: "+TypeColor+"; }\n");
o.write(".v { background: #ffffff none; color: "+VarColor+"; }\n");
o.write(".n { background: #ffffff none; color: "+IntegerColor+"; }\n");
o.write("</style></head>\n<body>\n<pre>");
LinkedList nodes = layer.getNodes();
ListIterator li = nodes.listIterator();
while (li.hasNext()) {
Node n = (Node) li.next();
n.asHTML(o);
}
o.write("</pre></body></html>"); o.newline();
}
public static String quoteHTML (String s)
{
int len = s.length();
StringBuffer sb = new StringBuffer(len + len / 10);
for (int i=0; i<len; i++) {
char c = s.charAt(i);
if (c=='<')
sb.append("&lt;");
else if (c=='>')
sb.append("&gt;");
else if (c=='&')
sb.append("&amp;");
else
sb.append(c);
}
return sb.toString();
}
};

View File

@@ -0,0 +1,67 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class BackendPerl extends Backend {
int layerID;
public BackendPerl (Layer l, int layerID) {
layer = l;
this.layerID = layerID;
}
public int getLayerID () { return layerID; }
public String getLayerIDString () {
return (new Integer(layerID)).toString();
}
public void output (Output o) {
Indenter io = new Indenter(o, 4);
io.writeln("#!/usr/bin/perl");
io.writeln("# auto-generated Perl code from input S2 code");
io.writeln("package S2;");
io.writeln("use strict;");
io.writeln("use constant VTABLE => 0;");
io.writeln("use constant STATIC => 1;");
io.writeln("use constant PROPS => 2;");
io.writeln("register_layer("+layerID+");");
LinkedList nodes = layer.getNodes();
ListIterator li = nodes.listIterator();
while (li.hasNext()) {
Node n = (Node) li.next();
n.asPerl(this, io);
}
io.writeln("1;");
io.writeln("# end.");
}
public static String quoteString (String s)
{
int len = s.length();
StringBuffer sb = new StringBuffer(len + 20);
sb.append("\"");
sb.append(quoteStringInner(s));
sb.append("\"");
return sb.toString();
}
public static String quoteStringInner (String s)
{
int len = s.length();
StringBuffer sb = new StringBuffer(len + 20);
for (int i=0; i<len; i++) {
char c = s.charAt(i);
if (c=='\n') {
sb.append("\\n");
} else {
if (c=='\\' || c=='$' || c=='"' || c=='@')
sb.append('\\');
sb.append(c);
}
}
return sb.toString();
}
};

View File

@@ -0,0 +1,29 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class BackendS2 extends Backend {
private final static boolean MANY_QUOTES = false;
public BackendS2 (Layer l) {
layer = l;
}
public void output (Output o)
{
Indenter io = new Indenter(o, 4);
io.writeln("# auto-generated S2 code from input S2 code");
LinkedList nodes = layer.getNodes();
ListIterator li = nodes.listIterator();
while (li.hasNext()) {
Node n = (Node) li.next();
n.asS2(io);
}
}
public static void LParen (Indenter o) { if (MANY_QUOTES) o.write("("); }
public static void RParen (Indenter o) { if (MANY_QUOTES) o.write(")"); }
};

View File

@@ -0,0 +1,19 @@
package danga.s2;
// NOTE: wrote this, used it for awhile, then decided not to use it.
// it works, but it'll probably bit rot.
public class BufferedIndenter extends Indenter
{
public BufferedIndenter (Indenter i) {
depth = i.depth;
tabsize = i.tabsize;
makeSpaces();
o = new OutputStringBuffer();
}
public void writeTo (Indenter i) {
OutputStringBuffer osb = (OutputStringBuffer) o;
osb.writeTo(i);
}
}

367
wcmtools/s2/danga/s2/Checker.java Executable file
View File

@@ -0,0 +1,367 @@
package danga.s2;
import java.util.Hashtable;
import java.util.ListIterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.Collections;
import java.util.TreeSet;
import java.util.Iterator;
public class Checker
{
// combined (all layers)
private Hashtable classes; // class name -> NodeClass
private Hashtable props; // property name -> Type
private Hashtable funcs; // FuncID -> return type
private Hashtable funcBuiltin; // FuncID -> Boolean (is builtin)
private LinkedList localblocks; // NodeStmtBlock scopes .. last is deepest (closest)
private Type returnType;
private String funcClass; // current function class
private Hashtable derclass; // classname -> LinkedList<classname>
private boolean inFunction; // checking in a function now?
// per-layer
private Hashtable funcDist; // FuncID -> [ distance, NodeFunction ]
private Hashtable funcIDs; // NodeFunction -> Set<FuncID>
private boolean hitFunction; // true once a function has been declared/defined
// per function
private int funcNum = 0;
private Hashtable funcNums; // FuncID -> Integer(funcnum)
private LinkedList funcNames; // Strings
public Checker ()
{
classes = new Hashtable();
props = new Hashtable();
funcs = new Hashtable();
funcBuiltin = new Hashtable();
derclass = new Hashtable();
localblocks = new LinkedList();
}
// class functions
public void addClass (String name, NodeClass nc) {
classes.put(name, nc);
// make sure that the list of classes that derive from this
// one exists.
if (derclass.get(name) == null) {
derclass.put(name, new LinkedList());
}
// and if this class derives from another, add ourselves
// to that list.
String parent = nc.getParentName();
if (parent != null) {
LinkedList l = (LinkedList) derclass.get(parent);
l.add(name);
}
}
public NodeClass getClass (String name) {
if (name == null) return null;
return (NodeClass) classes.get(name);
}
public String getParentClassName (String name) {
NodeClass nc = getClass(name);
if (nc == null) return null;
return nc.getParentName();
}
public boolean isValidType (Type t) {
if (t == null) return false;
if (t.isPrimitive()) return true;
if (getClass(t.baseType()) != null) return true;
return false;
}
// property functions
public void addProperty (String name, Type t) {
props.put(name, t);
}
public Type propertyType (String name) {
return (Type) props.get(name);
}
// return type of null means no return type.
public void setReturnType (Type t) {
returnType = t;
}
public Type getReturnType () {
return returnType;
}
// function functions
public void addFunction (String funcid, Type t, boolean builtin)
throws Exception
{
// make sure function doesn't mask a lower one with a different type
Type existing = functionType(funcid);
if (existing != null && ! existing.equals(t)) {
throw new Exception("Can't override function '" + funcid + "' with new "+
"return type.");
}
funcs.put(funcid, t);
funcBuiltin.put(funcid, new Boolean(builtin));
}
public Type functionType (String funcid) {
return (Type) funcs.get(funcid);
}
public boolean isFuncBuiltin (String funcid) {
Boolean b = (Boolean)funcBuiltin.get(funcid);
return b == null ? false : b.booleanValue();
}
// returns true if there's a string -> t class constructor
public boolean isStringCtor (Type t) {
if (t == null) return false;
if (! t.isSimple()) return false;
String cname = t.baseType();
String ctorid = cname+"::"+cname+"(string)";
Type rt = functionType(ctorid);
if (rt == null || ! rt.isSimple() || ! rt.baseType().equals(cname) ||
! isFuncBuiltin(ctorid))
return false;
return true;
}
// setting/getting the current function class we're in
public void setCurrentFunctionClass (String f) {
funcClass = f;
}
public String getCurrentFunctionClass () {
return funcClass;
}
// setting/getting whether in a function now
public void setInFunction (boolean in) {
inFunction = in;
}
public boolean getInFunction () {
return inFunction;
}
// variable lookup
public void pushLocalBlock (NodeStmtBlock nb) {
localblocks.addLast(nb);
}
public void popLocalBlock () {
localblocks.removeLast();
}
public NodeStmtBlock getLocalScope () {
if (localblocks.size() == 0)
return null;
return (NodeStmtBlock) localblocks.getLast();
}
public Type localType (String local)
{
if (localblocks.size() == 0)
return null;
ListIterator li = localblocks.listIterator(localblocks.size());
while (li.hasPrevious()) {
NodeStmtBlock nb = (NodeStmtBlock) li.previous();
Type t = nb.getLocalVar(local);
if (t != null)
return t;
}
return null;
}
public Type memberType (String clas, String member)
{
NodeClass nc = getClass(clas);
if (nc == null) return null;
return nc.getMemberType(member);
}
public void setHitFunction (boolean b) {
hitFunction = b;
}
public boolean getHitFunction () {
return hitFunction;
}
public boolean hasDerClasses (String clas) {
LinkedList l = (LinkedList) derclass.get(clas);
return l.size() > 0;
}
public ListIterator getDerClassesIter (String clas) {
LinkedList l = (LinkedList) derclass.get(clas);
return l.listIterator();
}
public void setFuncDistance (String funcID, DerItem df)
{
//System.err.println("setFuncDistance(\""+funcID+"\", "+df+")");
DerItem existing = (DerItem) funcDist.get(funcID);
if (existing == null || df.dist < existing.dist) {
funcDist.put(funcID, df);
///// keep the funcIDs hashes -> FuncID set up-to-date
// removing the existing funcID from the old set first
if (existing != null) {
Set oldset = (Set) funcIDs.get(existing.nf);
oldset.remove(funcID);
}
// first, make sure the set exists
Set idset = (Set) funcIDs.get(df.nf);
if (idset == null) {
idset = Collections.synchronizedSortedSet(new TreeSet());
funcIDs.put(df.nf, idset);
}
// now, insert this funcID for this function.
idset.add(funcID);
}
}
public Iterator getFuncIDsIter (NodeFunction nf)
{
Set s = (Set) funcIDs.get(nf);
if (s == null) {
System.err.println("WARN: no funcids for nf="+nf);
return null;
}
return s.iterator();
}
// per function
public void resetFunctionNums () {
funcNum = 0;
funcNums = new Hashtable();
funcNames = new LinkedList();
}
public int functionNum (String funcID) {
Integer num = (Integer) funcNums.get(funcID);
if (num == null) {
num = new Integer(++funcNum);
funcNums.put(funcID, num);
funcNames.add(funcID);
}
return num.intValue();
}
public Hashtable getFuncNums () {
return funcNums;
}
public LinkedList getFuncNames () {
return funcNames;
}
// check if type 't' is a subclass of 'w'
public boolean typeIsa (Type t, Type w)
{
if (! Type.sameMods(t, w))
return false;
String is = t.baseType();
String parent = w.baseType();
while (is != null) {
if (is.equals(parent))
return true;
NodeClass nc = getClass(is);
is = nc != null ? nc.getParentName() : null;
};
return false ;
}
// check to see if a class or parents has a
// "toString()" method
public boolean classHasToString (String clas) {
Type et = functionType(clas+"::toString()");
if (et != null && et.equals(Type.STRING))
return true;
return false;
}
// check to see if a class or parents has an
// "as_string" string member
public boolean classHasAsString (String clas) {
Type et = memberType(clas, "as_string");
if (et != null && et.equals(Type.STRING))
return true;
return false;
}
// ------
public void checkLayer (Layer lay) throws Exception
{
// initialize layer-specific data structures
funcDist = new Hashtable();
funcIDs = new Hashtable();
hitFunction = false;
// check to see that they declared the layer type, and that
// it isn't bogus.
{
// what the S2 source says the layer is
String dtype = lay.getDeclaredType();
if (dtype == null)
throw new Exception("Layer type not declared.");
// what type s2compile thinks it is
String type = lay.getType();
if (! dtype.equals(type)) {
throw new Exception("Layer is declared " + dtype +
" but expecting a "+type+" layer.");
}
// now that we've validated their type is okay
lay.setType(dtype);
}
LinkedList nodes = lay.getNodes();
ListIterator li = nodes.listIterator();
while (li.hasNext()) {
Node n = (Node) li.next();
n.check(lay, this);
}
if (lay.getType().equals("core")) {
String mv = lay.getLayerInfo("majorversion");
if (mv == null) {
throw new Exception("Core layers must declare 'majorversion' layerinfo.");
}
}
}
// static stuff
// returns the signature of a function and its arguments, in the form
// of: Classname::funcName(String,UserPage,int)
public static String functionID (String clas, String func, Object o)
{
StringBuffer sb = new StringBuffer(70);
if (clas != null) {
sb.append(clas); sb.append("::");
}
sb.append(func);
sb.append("(");
// where Object can be a NodeFormals or FIXME: other stuff
if (o == null) {
// do nothing.
} else if (o instanceof NodeFormals) {
NodeFormals nf = (NodeFormals) o;
sb.append(nf.typeList());
} else if (o instanceof String) {
String s = (String) o;
sb.append(s);
} else {
sb.append("[-----]");
}
sb.append(")");
return sb.toString();
}
}

View File

@@ -0,0 +1,22 @@
package danga.s2;
public class DerItem
{
public int dist;
public NodeClass nc;
public NodeFunction nf;
public DerItem (NodeClass nc, int dist) {
this.dist = dist;
this.nc = nc;
}
public DerItem (NodeFunction nf, int dist) {
this.dist = dist;
this.nf = nf;
}
public String toString () {
return (nc != null ? nc.toString() : nf.toString()) + "-@" + dist;
}
}

View File

@@ -0,0 +1,24 @@
package danga.s2;
public class FilePos implements Cloneable
{
public int line;
public int col;
public FilePos (int l, int c) {
line = l;
col = c;
}
public Object clone () {
return new FilePos(line, col);
}
public String locationString () {
return ("line " + line + ", column " + col);
}
public String toString () {
return locationString();
}
}

View File

@@ -0,0 +1,49 @@
package danga.s2;
public class Indenter
{
int depth = 0;
int tabsize = 4;
Output o;
String spaces;
public Indenter () {
o = new OutputConsole();
}
public Indenter (Output o, int tabsize) {
this.tabsize = tabsize;
this.o = o;
makeSpaces();
}
public void write (String s) { o.write(s); }
public void writeln (String s) { o.writeln(s); }
public void write (int i) { o.write(i); }
public void writeln (int i) { o.writeln(i); }
public void tabwrite (String s) { doTab(); o.write(s); }
public void tabwriteln (String s) { doTab(); o.writeln(s); }
public void newline () { o.newline(); }
public void tabIn () { depth++; makeSpaces(); }
public void tabOut () { depth--; makeSpaces(); }
protected void makeSpaces () {
int tsize = depth * tabsize;
char[] spaces = new char[tsize];
for (int i=0; i<tsize; i++) {
spaces[i] = ' ';
}
this.spaces = new String(spaces);
}
public void doTab () {
o.write(spaces);
}
}

81
wcmtools/s2/danga/s2/Layer.java Executable file
View File

@@ -0,0 +1,81 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Hashtable;
import java.util.Enumeration;
public class Layer
{
String type;
String declaredType;
LinkedList nodes = new LinkedList();
Hashtable layerinfo = new Hashtable();
public Layer (Tokenizer toker, String type) throws Exception
{
this.type = type;
Node n;
Token t;
while ((t=toker.peek()) != null) {
if (NodeUnnecessary.canStart(toker)) {
nodes.add(NodeUnnecessary.parse(toker));
continue;
}
if (NodeLayerInfo.canStart(toker)) {
NodeLayerInfo nli = (NodeLayerInfo) NodeLayerInfo.parse(toker);
nodes.add(nli);
// Remember the 'type' layerinfo value for checking later:
if (nli.getKey().equals("type")) {
declaredType = nli.getValue();
}
continue;
}
if (NodeSet.canStart(toker)) {
nodes.add(NodeSet.parse(toker));
continue;
}
if (NodeProperty.canStart(toker)) {
nodes.add(NodeProperty.parse(toker));
continue;
}
if (NodeFunction.canStart(toker)) {
nodes.add(NodeFunction.parse(toker, false));
continue;
}
if (NodeClass.canStart(toker)) {
nodes.add(NodeClass.parse(toker));
continue;
}
throw new Exception("Unknown token encountered while parsing layer: "+
t.toString());
}
}
public void setLayerInfo (String key, String val) {
layerinfo.put(key, val);
}
public String getLayerInfo (String key) {
return (String) layerinfo.get(key);
}
public Enumeration getLayerInfoKeys () { return layerinfo.keys(); }
public String getType () { return type; }
public String getDeclaredType () { return declaredType; }
public void setType (String newtype) { type = newtype; }
public String toString () { return type; }
public LinkedList getNodes() { return nodes; }
public boolean isCoreOrLayout () {
return (type.equals("core") || type.equals("layout"));
}
}

222
wcmtools/s2/danga/s2/Node.java Executable file
View File

@@ -0,0 +1,222 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public abstract class Node
{
protected FilePos startPos;
protected LinkedList tokenlist = new LinkedList ();
public void setStart (Token t) {
startPos = (FilePos) t.getFilePos().clone();
}
public void setStart (FilePos p) {
startPos = (FilePos) p.clone();
}
public void check (Layer l, Checker ck) throws Exception {
System.err.println("FIXME: check not implemented for " + this.toString());
}
public void asHTML (Output o) {
ListIterator li = tokenlist.listIterator(0);
while (li.hasNext()) {
Object el = li.next();
if (el instanceof Token) {
Token t = (Token) el;
t.asHTML(o);
} else if (el instanceof Node) {
Node n = (Node) el;
n.asHTML(o);
}
}
}
public void asS2 (Indenter o) {
o.tabwriteln("###Node::asS2###");
return;
}
public void asPerl (BackendPerl bp, Indenter o) {
o.tabwriteln("###"+this+"::asPerl###");
/*
ListIterator li = tokenlist.listIterator(0);
while (li.hasNext()) {
Object el = li.next();
if (el instanceof Token) {
Token t = (Token) el;
t.asPerl(o);
} else if (el instanceof Node) {
Node n = (Node) el;
n.asPerl(o);
}
}
*/
}
public void setTokenList (LinkedList newlist) {
tokenlist = newlist;
}
public void addNode (Node subnode) {
tokenlist.add(subnode);
}
public void addToken (Token t) {
tokenlist.add(t);
}
public Token eatToken (Tokenizer toker, boolean ignoreSpace) throws Exception {
Token t = toker.getToken();
tokenlist.add(t);
if (ignoreSpace) skipWhite(toker);
return t;
}
public Token eatToken (Tokenizer toker) throws Exception {
return eatToken(toker, true);
}
public Token requireToken (Tokenizer toker, Token t) throws Exception {
return requireToken(toker, t, true);
}
public Token requireToken (Tokenizer toker, Token t, boolean ignoreSpace)
throws Exception
{
if (ignoreSpace) skipWhite(toker);
Token next = toker.getToken();
if (next == null) {
throw new Exception("Unexpected end of file found");
}
if (! next.equals(t)) {
System.err.println("Expecting: " + t.toString());
System.err.println("Got: " + next.toString());
throw new Exception("Unexpected token found at " + toker.locationString());
}
tokenlist.add(next);
if (ignoreSpace) skipWhite(toker);
return next;
}
public TokenStringLiteral
getStringLiteral (Tokenizer toker)
throws Exception
{
return getStringLiteral(toker, true);
}
public TokenStringLiteral
getStringLiteral (Tokenizer toker, boolean ignoreSpace)
throws Exception
{
if (ignoreSpace) skipWhite(toker);
if (! (toker.peek() instanceof TokenStringLiteral)) {
throw new Exception("Expected string literal");
}
tokenlist.add(toker.peek());
return (TokenStringLiteral) toker.getToken();
}
public TokenIdent getIdent (Tokenizer toker) throws Exception {
return getIdent(toker, true, true);
}
public TokenIdent getIdent (Tokenizer toker, boolean addToList) throws Exception {
return getIdent(toker, addToList, true);
}
public TokenIdent getIdent (Tokenizer toker,
boolean addToList,
boolean ignoreSpace) throws Exception
{
Token id = toker.peek();
if (! (id instanceof TokenIdent)) {
throw new Exception("Expected identifer at " + toker.locationString());
}
if (addToList) {
eatToken(toker, ignoreSpace);
}
return (TokenIdent) id;
}
public void skipWhite (Tokenizer toker) throws Exception {
Token next;
while ((next=toker.peek()) != null) {
if (next.isNecessary()) {
return;
}
tokenlist.add(toker.getToken());
}
}
public FilePos getFilePos ()
{
// most nodes should set their position
if (startPos != null)
return startPos;
// if the node didn't record its position, try to figure it out
// from where the first token is at
ListIterator li = tokenlist.listIterator(0);
if (li.hasNext()) {
Object el = li.next();
// usually tokenlist is tokens, but can also be nodes:
if (el instanceof Node) {
Node eln = (Node) el;
return eln.getFilePos();
}
Token elt = (Token) el;
return elt.getFilePos();
}
return null;
}
protected static void dbg (String s) {
System.err.println(s);
}
public Type getType (Checker ck) throws Exception
{
throw new Exception("FIXME: getType(ck) not implemented in "+this);
}
public Type getType (Checker ck, Type wanted) throws Exception
{
return getType(ck);
}
// kinda a crappy part to put this, perhaps. but all expr
// nodes don't inherit from NodeExpr. maybe they should?
public boolean isLValue ()
{
// hack: only NodeTerms inside NodeExprs can be true
if (this instanceof NodeExpr) {
NodeExpr ne = (NodeExpr) this;
Node n = ne.getExpr();
if (n instanceof NodeTerm) {
NodeTerm nt = (NodeTerm) n;
return nt.isLValue();
}
}
return false;
}
public boolean makeAsString(Checker ck)
{
System.err.println("Node::makeAsString() on "+this);
return false;
}
};

View File

@@ -0,0 +1,106 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class NodeArguments extends Node
{
public LinkedList args = new LinkedList();
public static NodeArguments makeEmptyArgs ()
{
NodeArguments n = new NodeArguments();
n.args = new LinkedList();
return n;
}
public void addArg (NodeExpr ne) {
args.add(ne);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeArguments n = new NodeArguments();
n.setStart(n.requireToken(toker, TokenPunct.LPAREN));
boolean loop = true;
while (loop) {
Token tp = toker.peek();
if (tp.equals(TokenPunct.RPAREN)) {
n.eatToken(toker);
loop = false;
} else {
Node expr = NodeExpr.parse(toker);
n.args.add(expr);
n.addNode(expr);
if (toker.peek().equals(TokenPunct.COMMA)) {
n.eatToken(toker);
}
}
}
return n;
}
public void asS2 (Indenter o)
{
o.write("(");
ListIterator li = args.listIterator(0);
boolean didFirst = false;
while (li.hasNext()) {
Node n = (Node) li.next();
if (didFirst) {
o.write(", ");
} else {
didFirst = true;
}
n.asS2(o);
}
o.write(")");
}
public void asPerl (BackendPerl bp, Indenter o) {
asPerl(bp, o, true);
}
public void asPerl (BackendPerl bp, Indenter o, boolean doCurlies)
{
if (doCurlies)
o.write("(");
ListIterator li = args.listIterator(0);
boolean didFirst = false;
while (li.hasNext()) {
Node n = (Node) li.next();
if (didFirst) {
o.write(", ");
} else {
didFirst = true;
}
n.asPerl(bp, o);
}
if (doCurlies)
o.write(")");
}
public String typeList (Checker ck) throws Exception
{
StringBuffer sb = new StringBuffer(50);
if (args.size() == 0) return sb.toString();
ListIterator li = args.listIterator();
boolean first = true;
while (li.hasNext()) {
NodeExpr n = (NodeExpr) li.next();
if (! first) sb.append(",");
first = false;
sb.append(n.getType(ck).toString());
}
return sb.toString();
}
};

View File

@@ -0,0 +1,161 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
// [ <NodeExpr>? (, <NodeExpr>)* ,? ]
// { (<NodeExpr> => <NodeExpr> ,)* }
public class NodeArrayLiteral extends NodeExpr
{
boolean isHash = false;
boolean isArray = false;
LinkedList keys = new LinkedList();
LinkedList vals = new LinkedList();
public static boolean canStart (Tokenizer toker) throws Exception
{
return (toker.peek().equals(TokenPunct.LBRACK) ||
toker.peek().equals(TokenPunct.LBRACE));
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeArrayLiteral nal = new NodeArrayLiteral();
Token t = toker.peek();
if (t.equals(TokenPunct.LBRACK)) {
nal.isArray = true;
nal.setStart(nal.requireToken(toker, TokenPunct.LBRACK));
} else {
nal.isHash = true;
nal.setStart(nal.requireToken(toker, TokenPunct.LBRACE));
}
boolean need_comma = false;
while (true) {
t = toker.peek();
// find the ends
if (nal.isArray && t.equals(TokenPunct.RBRACK)) {
nal.requireToken(toker, TokenPunct.RBRACK);
return nal;
}
if (nal.isHash && t.equals(TokenPunct.RBRACE)) {
nal.requireToken(toker, TokenPunct.RBRACE);
return nal;
}
if (need_comma) {
throw new Exception("Expecting comma at "+toker.getPos());
}
if (nal.isArray) {
NodeExpr ne = (NodeExpr) NodeExpr.parse(toker);
nal.vals.add(ne);
nal.addNode(ne);
}
if (nal.isHash) {
NodeExpr ne = (NodeExpr) NodeExpr.parse(toker);
nal.keys.add(ne);
nal.addNode(ne);
nal.requireToken(toker, TokenPunct.HASSOC);
ne = (NodeExpr) NodeExpr.parse(toker);
nal.vals.add(ne);
nal.addNode(ne);
}
need_comma = true;
if (toker.peek().equals(TokenPunct.COMMA)) {
nal.requireToken(toker, TokenPunct.COMMA);
need_comma = false;
}
}
}
public void asS2 (Indenter o)
{
o.writeln(isArray ? "[" : "{");
o.tabIn();
ListIterator liv = vals.listIterator();
ListIterator lik = keys.listIterator();
Node n;
while (liv.hasNext()) {
o.tabwrite("");
if (isHash) {
n = (Node) lik.next();
n.asS2(o);
o.write(" => ");
}
n = (Node) liv.next();
n.asS2(o);
o.writeln(",");
}
o.tabOut();
o.tabwrite(isArray ? "]" : "}");
}
public Type getType (Checker ck, Type wanted) throws Exception
{
// in case of empty array [] or hash {}, the type is what they wanted,
// if they wanted something, otherwise void[] or void{}
Type t;
if (vals.size() == 0) {
if (wanted != null) return wanted;
t = new Type("void");
if (isArray) t.makeArrayOf();
if (isHash) t.makeHashOf();
return t;
}
ListIterator liv = vals.listIterator();
ListIterator lik = keys.listIterator();
t = (Type) ((Node) liv.next()).getType(ck).clone();
while (liv.hasNext()) {
Node n = (Node) liv.next();
Type next = n.getType(ck);
if (! t.equals(next)) {
throw new Exception("Array literal with inconsistent types: "+
"starts with "+t+", but then has "+next+" at "+
n.getFilePos());
}
}
if (isArray) t.makeArrayOf();
if (isHash) t.makeHashOf();
return t;
}
public Type getType (Checker ck) throws Exception
{
return getType(ck, null);
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.writeln(isArray ? "[" : "{");
o.tabIn();
ListIterator liv = vals.listIterator();
ListIterator lik = keys.listIterator();
Node n;
while (liv.hasNext()) {
o.tabwrite("");
if (isHash) {
n = (Node) lik.next();
n.asPerl(bp, o);
o.write(" => ");
}
n = (Node) liv.next();
n.asPerl(bp, o);
o.writeln(",");
}
o.tabOut();
o.tabwrite(isArray ? "]" : "}");
}
};

View File

@@ -0,0 +1,87 @@
package danga.s2;
public class NodeAssignExpr extends Node
{
Node lhs;
TokenPunct op;
Node rhs;
boolean builtin;
String baseType;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeCondExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeAssignExpr n = new NodeAssignExpr();
n.lhs = NodeCondExpr.parse(toker);
n.addNode(n.lhs);
if (toker.peek().equals(TokenPunct.ASSIGN)) {
n.op = (TokenPunct) toker.peek();
n.eatToken(toker);
n.skipWhite(toker);
} else {
return n.lhs;
}
n.rhs = NodeAssignExpr.parse(toker);
n.addNode(n.rhs);
return n;
}
public Type getType (Checker ck, Type wanted) throws Exception
{
Type lt = lhs.getType(ck, wanted);
Type rt = rhs.getType(ck, lt);
if (lt.isReadOnly()) {
throw new Exception("Left-hand side of assignment at "+getFilePos()+
" is a read-only value.");
}
if (! (lhs instanceof NodeTerm) ||
! lhs.isLValue()) {
throw new Exception("Left-hand side of assignment at "+getFilePos()+
" must be an lvalue.");
}
if (ck.typeIsa(rt, lt))
return lt;
// types don't match, but maybe class for left hand side has
// a constructor which takes a string.
if (rt.equals(Type.STRING) && ck.isStringCtor(lt)) {
rt = rhs.getType(ck, lt);
if (lt.equals(rt)) return lt;
}
throw new Exception("Can't assign type "+rt+" to "+lt+" at "+
getFilePos());
}
public void asS2 (Indenter o)
{
lhs.asS2(o);
if (op != null) {
o.write(" = ");
rhs.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
lhs.asPerl(bp, o);
if (op != null) {
o.write(" = ");
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,296 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Hashtable;
public class NodeClass extends Node
{
TokenIdent name;
TokenIdent parentName;
String docstring;
LinkedList vars = new LinkedList(); // NodeNamedType
Hashtable varType = new Hashtable(); // token String -> Type
LinkedList functions = new LinkedList(); // NodeFunction
Hashtable funcType = new Hashtable(); // funcID String -> Type
NodeClass parentClass; // Not set until check() is run
// this is kinda ugly, keeping a reference to the checker for use
// later, but there's only ever one checker, so it's okay.
Checker ck;
public String getParentName() {
if (parentName == null) return null;
return parentName.getIdent();
}
public static boolean canStart (Tokenizer toker) throws Exception {
if (toker.peek().equals(TokenKeyword.CLASS))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeClass n = new NodeClass();
n.setStart(n.requireToken(toker, TokenKeyword.CLASS));
n.name = n.getIdent(toker);
if (toker.peek().equals(TokenKeyword.EXTENDS)) {
n.eatToken(toker);
n.parentName = n.getIdent(toker);
}
// docstring
if (toker.peek() instanceof TokenStringLiteral) {
TokenStringLiteral t = (TokenStringLiteral) n.eatToken(toker);
n.docstring = t.getString();
}
n.requireToken(toker, TokenPunct.LBRACE);
while (toker.peek() != null && toker.peek() instanceof TokenKeyword) {
if (toker.peek().equals(TokenKeyword.VAR)) {
NodeClassVarDecl ncvd = (NodeClassVarDecl) NodeClassVarDecl.parse(toker);
n.vars.add(ncvd);
n.addNode(ncvd);
} else if (toker.peek().equals(TokenKeyword.FUNCTION)) {
NodeFunction nm = (NodeFunction) NodeFunction.parse(toker, true);
n.functions.add(nm);
n.addNode(nm);
}
}
n.requireToken(toker, TokenPunct.RBRACE);
return n;
}
public String getName () {
return name.getIdent();
}
public Type getFunctionType (String funcID) {
Type t = (Type) funcType.get(funcID);
if (t != null) return t;
if (parentClass != null)
return parentClass.getFunctionType(funcID);
return null;
}
public NodeClass getFunctionDeclClass (String funcID) {
Type t = (Type) funcType.get(funcID);
if (t != null) return this;
if (parentClass != null)
return parentClass.getFunctionDeclClass(funcID);
return null;
}
public Type getMemberType (String mem) {
Type t = (Type) varType.get(mem);
if (t != null) return t;
if (parentClass != null) {
return parentClass.getMemberType(mem);
}
return null;
}
// returns LinkedList<DerClass> from the current class down to
// all children classes.
public LinkedList getDerClasses () {
return getDerClasses(null, 0);
}
private LinkedList getDerClasses (LinkedList l, int depth) {
if (l == null) l = new LinkedList();
l.add(new DerItem(this, depth));
ListIterator li = ck.getDerClassesIter(getName());
while (li.hasNext()) {
String cname = (String) li.next();
NodeClass c = ck.getClass(cname);
c.getDerClasses(l, depth+1);
}
return l;
}
// returns the class/parent-class the named member variable was
// defined in.
public NodeClass getMemberDeclClass (String mem) {
Type t = (Type) varType.get(mem);
if (t != null) return this;
if (parentClass != null) {
return parentClass.getMemberDeclClass(mem);
}
return null;
}
public void check (Layer l, Checker ck) throws Exception
{
ListIterator li;
this.ck = ck;
// can't declare classes inside of a layer if functions
// have already been declared or defined.
if (ck.getHitFunction()) {
throw new Exception("Can't declare a class inside a layer "+
"file after functions have been defined at "+
getFilePos());
}
// if this is an extended class, make sure parent class exists
parentClass = null;
if (parentName != null) {
String pname = parentName.getIdent();
parentClass = ck.getClass(pname);
if (parentClass == null) {
throw new Exception("Can't extend non-existent class '"+
pname+"' at "+getFilePos());
}
}
// make sure the class isn't already defined.
String cname = name.getIdent();
if (ck.getClass(cname) != null) {
throw new Exception("Can't redeclare class '"+cname+"' at "+
getFilePos());
}
// register all var and function declarations in hash & check for both
// duplicates and masking of parent class's declarations
// register self. this needs to be done before checking member
// variables so we can have members of our own type.
ck.addClass(cname, this);
// member vars
for (li = vars.listIterator(); li.hasNext(); ) {
NodeClassVarDecl nnt = (NodeClassVarDecl) li.next();
boolean readonly = nnt.isReadOnly();
String vn = nnt.getName();
Type vt = nnt.getType();
Type et = getMemberType(vn);
if (et != null) {
NodeClass oc = getMemberDeclClass(vn);
throw new Exception("Can't declare the variable '"+vn+"' "+
"as '"+vt+"' in class '"+cname+"' at "+
nnt.getFilePos()+" because it's "+
"already defined in class '"+oc.getName()+"' as "+
"type '"+et+"'.");
}
// check to see if type exists
if (ck.isValidType(vt) != true) {
throw new Exception("Can't declare member variable '"+vn+"' "+
"as unknown type '"+vt+"' in class '"+cname+"' at "+
nnt.getFilePos());
}
vt.setReadOnly(readonly);
varType.put(vn, vt); // register member variable
}
// all parent class functions need to be inherited:
registerFunctions(ck, cname);
}
private void registerFunctions (Checker ck, String clas) throws Exception
{
// register parent's functions first.
if (parentClass != null)
parentClass.registerFunctions(ck, clas);
// now do our own
for (ListIterator li = functions.listIterator(); li.hasNext(); ) {
NodeFunction nf = (NodeFunction) li.next();
Type rettype = nf.getReturnType();
nf.registerFunction(ck, clas);
}
}
public void asS2 (Indenter o)
{
ListIterator li;
o.tabwrite("class " + name.getIdent() + " ");
if (parentName != null) {
o.write("extends " + parentName.getIdent() + " ");
}
o.writeln("{");
o.tabIn();
// vars
for (li = vars.listIterator(0); li.hasNext(); ) {
NodeClassVarDecl vd = (NodeClassVarDecl) li.next();
vd.asS2(o);
}
// functions
for (li = functions.listIterator(0); li.hasNext(); ) {
NodeFunction nf = (NodeFunction) li.next();
nf.asS2(o);
}
o.tabOut();
o.writeln("}");
}
public void asPerl (BackendPerl bp, Indenter o) {
o.tabwriteln("register_class(" + bp.getLayerIDString() +
", " + bp.quoteString(name.getIdent()) + ", {");
o.tabIn();
if (parentName != null) {
o.tabwriteln("'parent' => " + bp.quoteString(parentName.getIdent()) + ",");
}
if (docstring != null) {
o.tabwriteln("'docstring' => " + bp.quoteString(docstring) + ",");
}
// vars
o.tabwriteln("'vars' => {");
o.tabIn();
for (ListIterator li = vars.listIterator(); li.hasNext(); ) {
NodeClassVarDecl nnt = (NodeClassVarDecl) li.next();
String vn = nnt.getName();
Type vt = nnt.getType();
Type et = getMemberType(vn);
o.tabwrite(bp.quoteString(vn) + " => { 'type' => " + bp.quoteString(vt.toString()));
if (vt.isReadOnly()) {
o.write(", 'readonly' => 1");
}
if (nnt.getDocString() != null) {
o.write(", 'docstring' => " + bp.quoteString(nnt.getDocString()));
}
o.writeln(" },");
}
o.tabOut();
o.tabwriteln("},");
// methods
o.tabwriteln("'funcs' => {");
o.tabIn();
for (ListIterator li = functions.listIterator(); li.hasNext(); ) {
NodeFunction nf = (NodeFunction) li.next();
String name = nf.getName();
NodeFormals nfo = nf.getFormals();
Type rt = nf.getReturnType();
o.tabwrite(bp.quoteString(name + ((nfo != null) ? nfo.toString() : "()"))
+ " => { 'returntype' => "
+ bp.quoteString(rt.toString()));
if (nf.getDocString() != null) {
o.write(", 'docstring' => " + bp.quoteString(nf.getDocString()));
}
o.writeln(" },");
}
o.tabOut();
o.tabwriteln("},");
o.tabOut();
o.tabwriteln("});");
}
};

View File

@@ -0,0 +1,80 @@
package danga.s2;
public class NodeClassVarDecl extends Node
{
public Type type;
public NodeType typenode;
public String name;
public String docstring;
boolean readonly = false;
public Type getType () {
return type;
}
public String getName () {
return name;
}
public String getDocString () {
return docstring;
}
public boolean isReadOnly () {
return readonly;
}
public NodeClassVarDecl () {
}
public NodeClassVarDecl (String name, Type type) {
this.name = name;
this.type = type;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeClassVarDecl n = new NodeClassVarDecl();
n.setStart(n.requireToken(toker, TokenKeyword.VAR));
if (toker.peek() == TokenKeyword.READONLY) {
n.readonly = true;
n.eatToken(toker);
}
n.typenode = (NodeType) NodeType.parse(toker);
n.type = n.typenode.getType();
n.addNode(n.typenode);
n.name = n.getIdent(toker).getIdent();
// docstring
if (toker.peek() instanceof TokenStringLiteral) {
TokenStringLiteral t = (TokenStringLiteral) n.eatToken(toker);
n.docstring = t.getString();
}
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void asS2 (Indenter o)
{
o.tabwrite("var ");
if (readonly) o.write("readonly ");
typenode.asS2(o);
o.write(" " + name);
if (docstring != null) {
o.write(BackendPerl.quoteString(" " + docstring));
}
o.writeln(";");
}
public String asString ()
{
return type.toString() + " " + name;
}
};

View File

@@ -0,0 +1,78 @@
package danga.s2;
public class NodeCondExpr extends Node
{
Node test_expr;
Node true_expr;
Node false_expr;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeRange.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeCondExpr n = new NodeCondExpr();
n.test_expr = NodeRange.parse(toker);
n.addNode(n.test_expr);
if (toker.peek().equals(TokenPunct.QMARK)) {
n.eatToken(toker);
n.skipWhite(toker);
} else {
return n.test_expr;
}
n.true_expr = NodeRange.parse(toker);
n.addNode(n.true_expr);
n.requireToken(toker, TokenPunct.COLON);
n.false_expr = NodeRange.parse(toker);
n.addNode(n.false_expr);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type ctype = test_expr.getType(ck);
if (! ctype.isBoolable()) {
throw new Exception("Conditional expression not a boolean at "+
getFilePos());
}
Type lt = true_expr.getType(ck);
Type rt = false_expr.getType(ck);
if (! lt.equals(rt)) {
throw new Exception("Types must match in condition expression at "+
getFilePos());
}
return lt;
}
public void asS2 (Indenter o)
{
test_expr.asS2(o);
if (true_expr != null) {
o.write(" ? ");
true_expr.asS2(o);
o.write(" : ");
false_expr.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
test_expr.asPerl(bp, o);
if (true_expr != null) {
o.write(" ? ");
true_expr.asPerl(bp, o);
o.write(" : ");
false_expr.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,52 @@
package danga.s2;
public class NodeDeleteStmt extends Node
{
NodeVarRef var;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.DELETE))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeDeleteStmt n = new NodeDeleteStmt();
Token t = toker.peek();
n.requireToken(toker, TokenKeyword.DELETE);
n.addNode(n.var = (NodeVarRef) NodeVarRef.parse(toker));
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
// type check the innards, but we don't care what type it
// actually is.
var.getType(ck);
// but it must be a hash reference
if (! var.isHashElement()) {
throw new Exception("Delete statement argument is not a hash at "+
var.getFilePos());
}
}
public void asS2 (Indenter o)
{
o.tabwrite("delete ");
var.asS2(o);
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwrite("delete ");
var.asPerl(bp, o);
o.writeln(";");
}
};

View File

@@ -0,0 +1,85 @@
package danga.s2;
public class NodeEqExpr extends Node
{
Node lhs;
TokenPunct op;
Node rhs;
// use this for the backend to decide which add operator to use
private Type myType;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeRelExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeEqExpr n = new NodeEqExpr();
n.lhs = NodeRelExpr.parse(toker);
n.addNode(n.lhs);
Token t = toker.peek();
if (t.equals(TokenPunct.EQ) || t.equals(TokenPunct.NE)) {
n.op = (TokenPunct) t;
n.eatToken(toker);
n.skipWhite(toker);
} else {
return n.lhs;
}
n.rhs = NodeRelExpr.parse(toker);
n.addNode(n.rhs);
n.skipWhite(toker);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type lt = lhs.getType(ck);
Type rt = rhs.getType(ck);
if (! lt.equals(rt))
throw new Exception("The types of the left and right hand side of "+
"equality test expression don't match at "+getFilePos());
myType = lt;
if (lt.equals(Type.BOOL) || lt.equals(Type.STRING) || lt.equals(Type.INT)) {
return Type.BOOL;
}
throw new Exception ("Only bool, string, and int types can be tested for "+
"equality at "+getFilePos());
}
public void asS2 (Indenter o)
{
lhs.asS2(o);
if (op != null) {
o.write(" " + op.getPunct() + " ");
rhs.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
lhs.asPerl(bp, o);
if (op != null) {
if (op.equals(TokenPunct.EQ)) {
if (myType.equals(Type.STRING))
o.write(" eq ");
else
o.write(" == ");
} else {
if (myType.equals(Type.STRING))
o.write(" ne ");
else
o.write(" != ");
}
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,49 @@
package danga.s2;
public class NodeExpr extends Node
{
Node expr;
public NodeExpr () { }
public NodeExpr (Node n) {
expr = n;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeAssignExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeExpr n = new NodeExpr();
n.expr = NodeAssignExpr.parse(toker);
n.addNode(n.expr);
return n; // Note: always return a NodeExpr here
}
public void asS2 (Indenter o)
{
expr.asS2(o);
}
public void asPerl (BackendPerl bp, Indenter o)
{
expr.asPerl(bp, o);
}
public Type getType (Checker ck) throws Exception
{
return expr.getType(ck, null);
}
public Type getType (Checker ck, Type wanted) throws Exception
{
return expr.getType(ck, wanted);
}
public Node getExpr () {
return expr;
}
}

View File

@@ -0,0 +1,42 @@
package danga.s2;
public class NodeExprStmt extends Node
{
NodeExpr expr;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeExprStmt n = new NodeExprStmt();
n.addNode(n.expr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
Type t = expr.getType(ck); // checks the type
}
public void asS2 (Indenter o)
{
o.doTab();
expr.asS2(o);
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.doTab();
expr.asPerl(bp, o);
o.writeln(";");
}
};

View File

@@ -0,0 +1,136 @@
package danga.s2;
public class NodeForeachStmt extends Node
{
NodeExpr listexpr;
NodeStmtBlock stmts;
NodeVarDecl vardecl;
NodeVarRef varref;
boolean isHash; // otherwise it's an array or a string
boolean isString;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.FOREACH))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeForeachStmt n = new NodeForeachStmt();
n.requireToken(toker, TokenKeyword.FOREACH);
if (NodeVarDecl.canStart(toker)) {
n.addNode(n.vardecl = (NodeVarDecl) NodeVarDecl.parse(toker));
} else {
n.addNode(n.varref = (NodeVarRef) NodeVarRef.parse(toker));
}
// expression in parenthesis representing an array to iterate over:
n.requireToken(toker, TokenPunct.LPAREN);
n.addNode(n.listexpr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.RPAREN);
// and what to do on each element
n.addNode(n.stmts = (NodeStmtBlock) NodeStmtBlock.parse(toker));
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
Type ltype = listexpr.getType(ck);
isHash = false;
if (ltype.isHashOf()) {
isHash = true;
} else if (ltype.equals(Type.STRING)) { // Iterate over characters in a string
isString = true;
} else if (! ltype.isArrayOf()) {
throw new Exception("Must use an array, hash or string in foreach statement at "+
listexpr.getFilePos());
}
Type itype = null;
if (vardecl != null) {
vardecl.populateScope(stmts);
itype = vardecl.getType();
}
if (varref != null) {
itype = varref.getType(ck);
}
if (isHash) {
// then iter type must be a string or int
if (! itype.equals(Type.STRING) && ! itype.equals(Type.INT)) {
throw new Exception("Foreach iteration variable must be a "+
"string or int when interating over the keys "+
"in a hash at "+getFilePos());
}
} else if (isString) {
if (! itype.equals(Type.STRING)) {
throw new Exception("Foreach iteration variable must be a "+
"string when interating over the characters "+
"in a string at "+getFilePos());
}
} else {
// iter type must be the same as the list type minus
// the final array ref
// figure out the desired type
Type dtype = (Type) ltype.clone();
dtype.removeMod();
if (! dtype.equals(itype)) {
throw new Exception("Foreach iteration variable is of type "+
itype+", not the expected type of "+dtype+" at "+
getFilePos());
}
}
ck.pushLocalBlock(stmts);
stmts.check(l, ck);
ck.popLocalBlock();
}
public void asS2 (Indenter o)
{
o.tabwrite("foreach ");
if (vardecl != null)
vardecl.asS2(o);
if (varref != null)
varref.asS2(o);
o.write(" (");
listexpr.asS2(o);
o.write(") ");
stmts.asS2(o);
o.newline();
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwrite("foreach ");
if (vardecl != null)
vardecl.asPerl(bp, o);
if (varref != null)
varref.asPerl(bp, o);
if (isHash) {
o.write(" (keys %{");
} else if (isString) {
o.write(" (S2::get_characters(");
} else {
o.write(" (@{");
}
listexpr.asPerl(bp, o);
if (isString) {
o.write(")) ");
} else {
o.write("}) ");
}
stmts.asPerl(bp, o);
o.newline();
}
};

View File

@@ -0,0 +1,148 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Hashtable;
public class NodeFormals extends Node
{
public LinkedList listFormals = new LinkedList(); // NodeNamedType
public NodeFormals () { }
public NodeFormals (LinkedList formals) {
listFormals = formals;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeFormals n = new NodeFormals();
int count = 0;
n.requireToken(toker, TokenPunct.LPAREN);
while (toker.peek() != null && ! toker.peek().equals(TokenPunct.RPAREN)) {
if (count > 0) {
n.requireToken(toker, TokenPunct.COMMA);
}
n.skipWhite(toker);
NodeNamedType nf = (NodeNamedType) NodeNamedType.parse(toker);
n.listFormals.add(nf);
n.tokenlist.add(nf);
n.skipWhite(toker);
count++;
}
n.requireToken(toker, TokenPunct.RPAREN);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
Hashtable h = new Hashtable();
ListIterator li = listFormals.listIterator();
while (li.hasNext()) {
NodeNamedType nt = (NodeNamedType) li.next();
String name = nt.getName();
if (h.get(name) != null)
throw new Exception("Duplicate argument named '" + name + "' at "+
nt.getFilePos());
h.put(name, name);
Type t = nt.getType();
if (! ck.isValidType(t))
throw new Exception("Unknown type '" + t + "' at "+nt.getFilePos());
}
}
public void asS2 (Indenter o) {
if (listFormals.size() == 0) return; // no empty parens necessary in S2
o.write(toString());
}
public String toString () {
StringBuffer sb = new StringBuffer("(");
ListIterator li = listFormals.listIterator();
boolean first = true;
while (li.hasNext()) {
NodeNamedType nf = (NodeNamedType) li.next();
if (! first) {
sb.append(", ");
}
first = false;
sb.append(nf.toString());
}
sb.append(")");
return sb.toString();
}
// returns a ListIterator returning variations of this NodeFormal
// object using derived classes as well.
public static ListIterator variationIterator (NodeFormals nf, Checker ck)
{
LinkedList l = new LinkedList();
if (nf == null) {
l.add(new NodeFormals(new LinkedList()));
} else {
nf.getVariations(ck, l, new LinkedList(), 0);
}
return l.listIterator();
}
private void getVariations (Checker ck,
LinkedList vars,
LinkedList temp,
int col)
{
if (col == listFormals.size()) {
vars.add(new NodeFormals(temp));
return;
}
NodeNamedType nt = (NodeNamedType) listFormals.get(col);
Type t = nt.getType();
for (ListIterator li = t.subTypesIter(ck); li.hasNext(); ) {
t = (Type) li.next();
LinkedList newtemp = (LinkedList) temp.clone();
newtemp.add(new NodeNamedType(nt.getName(), t));
getVariations(ck, vars, newtemp, col+1);
}
}
public String typeList ()
{
StringBuffer sb = new StringBuffer(50);
if (listFormals.size() == 0) return sb.toString();
ListIterator li = listFormals.listIterator();
boolean first = true;
while (li.hasNext()) {
NodeNamedType nt = (NodeNamedType) li.next();
if (! first) sb.append(",");
first = false;
sb.append(nt.getType().toString());
}
return sb.toString();
}
// adds all these variables to the stmtblock's symbol table
public void populateScope (NodeStmtBlock nb)
{
if (listFormals.size() == 0) return;
ListIterator li = listFormals.listIterator();
while (li.hasNext()) {
NodeNamedType nt = (NodeNamedType) li.next();
nb.addLocalVar(nt.getName(), nt.getType());
}
}
public ListIterator iterator() {
return listFormals.listIterator();
}
};

View File

@@ -0,0 +1,364 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Iterator;
import java.util.Hashtable;
import java.util.Enumeration;
public class NodeFunction extends Node
{
TokenIdent classname;
TokenIdent name;
NodeType rettype;
NodeFormals formals;
NodeStmtBlock stmts;
boolean builtin = false;
boolean isCtor = false;
LinkedList funcNames = null;
Checker ck;
String docstring;
public String getDocString () {
return docstring;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.FUNCTION))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
return parse(toker, false);
}
public static Node parse (Tokenizer toker, boolean isDecl) throws Exception
{
NodeFunction n = new NodeFunction();
// get the function keyword
n.setStart(n.requireToken(toker, TokenKeyword.FUNCTION));
// is the builtin keyword on?
if (toker.peek().equals(TokenKeyword.BUILTIN)) {
n.builtin = true;
n.eatToken(toker);
}
// and the class name or function name (if no class)
n.name = n.getIdent(toker);
// check for a double colon
if (toker.peek().equals(TokenPunct.DCOLON)) {
// so last ident was the class name
n.classname = n.name;
n.eatToken(toker);
n.name = n.getIdent(toker);
}
// Argument list is optional.
if (toker.peek().equals(TokenPunct.LPAREN)) {
n.addNode(n.formals = (NodeFormals) NodeFormals.parse(toker));
}
// return type is optional too.
if (toker.peek().equals(TokenPunct.COLON)) {
n.requireToken(toker, TokenPunct.COLON);
n.addNode(n.rettype = (NodeType) NodeType.parse(toker));
}
// docstring
if (toker.peek() instanceof TokenStringLiteral) {
TokenStringLiteral t = (TokenStringLiteral) n.eatToken(toker);
n.docstring = t.getString();
}
// if inside a class declaration, only a declaration now.
if (isDecl || n.builtin) {
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
// otherwise, parsing the function definition.
n.stmts = (NodeStmtBlock) NodeStmtBlock.parse(toker);
n.addNode(n.stmts);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
// keep a reference to the checker for later
this.ck = ck;
ck.setInFunction(true);
// reset the functionID -> local funcNum mappings
ck.resetFunctionNums();
// only core and layout layers can define functions
if (! l.isCoreOrLayout()) {
throw new Exception("Only core and layout layers can define new functions.");
}
// tell the checker we've seen a function now so it knows
// later to complain if it then sees a new class declaration.
// (builtin functions are okay)
if (! builtin)
ck.setHitFunction(true);
String cname = className();
String funcID = Checker.functionID(cname, name.getIdent(), formals);
Type t = getReturnType();
if (cname != null && cname.equals(name.getIdent())) {
isCtor = true;
}
// if this function is global, no declaration is done, but if
// this is class-scoped, we must check the class exists and
// that it declares this function.
if (cname != null) {
NodeClass nc = ck.getClass(cname);
if (nc == null) {
throw new Exception("Can't declare function "+funcID+" for "+
"non-existent class '"+cname+"' at "+
getFilePos());
}
Type et = ck.functionType(funcID);
if (et == null) {
throw new Exception("Can't define undeclared object function "+funcID+" at "+
getFilePos());
}
// find & register all the derivative names by which this function
// could be called.
ListIterator li = nc.getDerClasses().listIterator();
while (li.hasNext()) {
DerItem dc = (DerItem) li.next();
NodeClass c = dc.nc;
ListIterator fi = NodeFormals.variationIterator(formals, ck);
while (fi.hasNext()) {
NodeFormals fv = (NodeFormals) fi.next();
String derFuncID = Checker.functionID(c.getName(), getName(), fv);
ck.setFuncDistance(derFuncID, new DerItem(this, dc.dist));
ck.addFunction(derFuncID, t, builtin);
}
}
} else {
// non-class function. register all variations of the formals.
ListIterator fi = NodeFormals.variationIterator(formals, ck);
while (fi.hasNext()) {
NodeFormals fv = (NodeFormals) fi.next();
String derFuncID = Checker.functionID(cname, getName(), fv);
ck.setFuncDistance(derFuncID, new DerItem(this, 0));
ck.addFunction(derFuncID, t, builtin);
}
}
// check the formals
if (formals != null)
formals.check(l, ck);
// check the statement block
if (stmts != null) {
// prepare stmts to be checked
stmts.setReturnType(t);
// make sure $this is accessible in a class method
// FIXME: not in static functions, once we have static functions
if (cname != null) {
stmts.addLocalVar("this", new Type(cname));
} else {
stmts.addLocalVar("this", Type.VOID); // prevent its use
}
// make sure $this is accessible in a class method
// that has a parent.
String pname = ck.getParentClassName(cname);
if (pname != null) {
stmts.addLocalVar("super", new Type(pname));
} else {
stmts.addLocalVar("super", Type.VOID); // prevent its use
}
if (formals != null)
formals.populateScope(stmts);
ck.setCurrentFunctionClass(cname); // for $.member lookups
ck.pushLocalBlock(stmts);
stmts.check(l, ck);
ck.popLocalBlock();
}
// remember the funcID -> local funcNum mappings for the backend
funcNames = ck.getFuncNames();
}
// called by NodeClass
public void registerFunction (Checker ck, String cname)
throws Exception
{
String funcID = Checker.functionID(cname, getName(), formals);
Type et = ck.functionType(funcID);
Type rt = getReturnType();
// check that function is either currently undefined or
// defined with the same type, otherwise complain
if (et == null || et.equals(rt)) {
ck.addFunction(funcID, rt, builtin); // Register
} else {
throw new Exception("Can't redefine function '"+getName()+"' with return "+
"type of '"+rt+"' at "+getFilePos()+" masking "+
"earlier definition of type '"+et+"'.");
}
}
public NodeFormals getFormals () {
return formals;
}
public String getName () {
return name.getIdent();
}
public Type getReturnType () {
return (rettype != null ? rettype.getType() : Type.VOID);
}
public void asS2 (Indenter o)
{
o.tabwrite("function " + totalName());
if (formals != null) {
o.write(" ");
formals.asS2(o);
}
if (rettype != null) {
o.write(" : ");
rettype.asS2(o);
}
if (stmts != null) {
o.write(" ");
stmts.asS2(o);
o.newline();
} else {
o.writeln(";");
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (classname == null) {
o.tabwrite("register_global_function(" +
bp.getLayerIDString() + "," +
bp.quoteString(name.getIdent() + (formals != null ? formals.toString() : "()")) + "," +
bp.quoteString(getReturnType().toString()));
if (docstring != null)
o.write(", " + bp.quoteString(docstring));
o.writeln(");");
}
if (builtin) return;
o.tabwrite("register_function(" + bp.getLayerIDString() +
", [");
// declare all the names by which this function would be called:
// its base name, then all derivative classes which aren't already
// used.
Iterator i = ck.getFuncIDsIter(this);
while (i.hasNext()) {
String funcID = (String) i.next();
o.write(bp.quoteString(funcID) + ", ");
}
o.writeln("], sub {");
o.tabIn();
// the first time register_function is run, it'll find the
// funcNames for this session and save those in a list and then
// return the sub which is a closure and will have fast access
// to that num -> num hash. (benchmarking showed two
// hashlookups on ints was faster than one on strings)
if (funcNames.size() > 0) {
o.tabwriteln("my @_l2g_func = ( undef, ");
o.tabIn();
ListIterator li = funcNames.listIterator();
while (li.hasNext()) {
String id = (String) li.next();
o.tabwriteln("get_func_num(" +
BackendPerl.quoteString(id) + "),");
}
o.tabOut();
o.tabwriteln(");");
}
// now, return the closure
o.tabwriteln("return sub {");
o.tabIn();
// setup function argument/ locals
o.tabwrite("my ($_ctx");
if (classname != null && ! isCtor) {
o.write(", $this");
}
if (formals != null) {
ListIterator li = formals.iterator();
while (li.hasNext()) {
NodeNamedType nt = (NodeNamedType) li.next();
o.write(", $"+nt.getName());
}
}
o.writeln(") = @_;");
// end function locals
stmts.asPerl(bp, o, false);
o.tabOut();
o.tabwriteln("};");
// end the outer sub
o.tabOut();
o.tabwriteln("});");
}
public String toString ()
{
return (className() + "...");
}
public boolean isBuiltin () {
return builtin;
}
//-----------------------------
private String className ()
{
if (classname != null)
return classname.getIdent();
return null;
}
private String totalName ()
{
StringBuffer sb = new StringBuffer(50);
String clas = className();
if (clas != null) {
sb.append(clas);
sb.append("::");
}
sb.append(name.getIdent());
return sb.toString();
}
};

View File

@@ -0,0 +1,169 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class NodeIfStmt extends Node
{
NodeExpr expr;
NodeStmtBlock thenblock;
NodeStmtBlock elseblock;
LinkedList elseifexprs;
LinkedList elseifblocks;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.IF))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeIfStmt n = new NodeIfStmt();
n.elseifblocks = new LinkedList();
n.elseifexprs = new LinkedList();
n.setStart(n.requireToken(toker, TokenKeyword.IF));
n.requireToken(toker, TokenPunct.LPAREN);
n.addNode(n.expr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.RPAREN);
n.addNode(n.thenblock = (NodeStmtBlock) NodeStmtBlock.parse(toker));
while (toker.peek().equals(TokenKeyword.ELSEIF)) {
n.eatToken(toker);
// get the expression.
n.requireToken(toker, TokenPunct.LPAREN);
Node expr = NodeExpr.parse(toker);
n.addNode(expr);
n.requireToken(toker, TokenPunct.RPAREN);
n.elseifexprs.add(expr);
// and the block
Node nie = NodeStmtBlock.parse(toker);
n.addNode(nie);
n.elseifblocks.add(nie);
}
if (toker.peek().equals(TokenKeyword.ELSE)) {
n.eatToken(toker);
n.addNode(n.elseblock = (NodeStmtBlock) NodeStmtBlock.parse(toker));
}
return n;
}
// returns true if and only if the 'then' stmtblock ends in a
// return statement, the 'else' stmtblock is non-null and ends
// in a return statement, and any elseif stmtblocks end in a return
// statement.
public boolean willReturn ()
{
// there must be an else block.
if (elseblock == null) return false;
// both the 'then' and 'else' blocks must return
if (! thenblock.willReturn()) return false;
if (! elseblock.willReturn()) return false;
// if there are elseif blocks, all those must return
ListIterator li = elseifblocks.listIterator();
while (li.hasNext()) {
NodeStmtBlock sb = (NodeStmtBlock) li.next();
if (! sb.willReturn()) return false;
}
// else, it does return.
return true;
}
public void check (Layer l, Checker ck) throws Exception
{
Type t = expr.getType(ck);
if (! t.equals(Type.BOOL) && ! t.equals(Type.INT)) {
throw new Exception("Non-boolean if test at "+getFilePos());
}
thenblock.check(l, ck);
ListIterator li;
li = elseifexprs.listIterator();
while (li.hasNext()) {
NodeExpr ne = (NodeExpr) li.next();
t = ne.getType(ck);
if (! t.equals(Type.BOOL) && ! t.equals(Type.INT))
throw new Exception("Non-boolean elseif test at "+ne.getFilePos());
}
li = elseifblocks.listIterator();
while (li.hasNext()) {
NodeStmtBlock sb = (NodeStmtBlock) li.next();
sb.check(l, ck);
}
if (elseblock != null)
elseblock.check(l, ck);
}
public void asS2 (Indenter o)
{
// if
o.tabwrite("if (");
expr.asS2(o);
o.write(") ");
thenblock.asS2(o);
// else-if
ListIterator li = elseifexprs.listIterator(0);
ListIterator lib = elseifblocks.listIterator(0);
while (li.hasNext()) {
NodeExpr expr = (NodeExpr) li.next();
NodeStmtBlock block = (NodeStmtBlock) lib.next();
o.write(" elseif (");
expr.asS2(o);
o.write(") ");
block.asS2(o);
}
// else
if (elseblock != null) {
o.write(" else ");
elseblock.asS2(o);
}
o.newline();
}
public void asPerl (BackendPerl bp, Indenter o)
{
// if
o.tabwrite("if (");
expr.asPerl(bp, o);
o.write(") ");
thenblock.asPerl(bp, o);
// else-if
ListIterator li = elseifexprs.listIterator(0);
ListIterator lib = elseifblocks.listIterator(0);
while (li.hasNext()) {
NodeExpr expr = (NodeExpr) li.next();
NodeStmtBlock block = (NodeStmtBlock) lib.next();
o.write(" elsif (");
expr.asPerl(bp, o);
o.write(") ");
block.asPerl(bp, o);
}
// else
if (elseblock != null) {
o.write(" else ");
elseblock.asPerl(bp, o);
}
o.newline();
}
};

View File

@@ -0,0 +1,70 @@
package danga.s2;
public class NodeIncExpr extends Node
{
Node expr;
TokenPunct op;
boolean bPre = false;
boolean bPost = false;
public static boolean canStart (Tokenizer toker) throws Exception
{
return (toker.peek().equals(TokenPunct.INC) ||
toker.peek().equals(TokenPunct.DEC) ||
NodeTerm.canStart(toker));
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeIncExpr n = new NodeIncExpr();
if (toker.peek().equals(TokenPunct.INC) ||
toker.peek().equals(TokenPunct.DEC)) {
n.bPre = true;
n.op = (TokenPunct) toker.peek();
n.setStart(n.eatToken(toker));
n.skipWhite(toker);
}
Node expr = NodeTerm.parse(toker);
if (toker.peek().equals(TokenPunct.INC) ||
toker.peek().equals(TokenPunct.DEC)) {
if (n.bPre) throw new Exception("Unexpected -- or ++");
n.bPost = true;
n.op = (TokenPunct) toker.peek();
n.eatToken(toker);
n.skipWhite(toker);
}
if (n.bPre || n.bPost) {
n.expr = expr;
return n;
}
return expr;
}
public Type getType (Checker ck) throws Exception
{
if (! expr.isLValue()) {
throw new Exception("Post/pre-increment must operate on lvalue at "+
expr.getFilePos());
}
return expr.getType(ck);
}
public void asS2 (Indenter o)
{
if (bPre) { o.write(op.getPunct()); }
expr.asS2(o);
if (bPost) { o.write(op.getPunct()); }
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (bPre) { o.write(op.getPunct()); }
expr.asPerl(bp, o);
if (bPost) { o.write(op.getPunct()); }
}
}

View File

@@ -0,0 +1,64 @@
package danga.s2;
import java.util.LinkedList;
public class NodeLayerInfo extends Node
{
String key;
String val;
public String getKey () { return key; }
public String getValue () { return val; }
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.LAYERINFO))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeLayerInfo n = new NodeLayerInfo();
NodeText nkey, nval;
n.requireToken(toker, TokenKeyword.LAYERINFO);
n.addNode(nkey = (NodeText) NodeText.parse(toker));
n.requireToken(toker, TokenPunct.ASSIGN);
n.addNode(nval = (NodeText) NodeText.parse(toker));
n.requireToken(toker, TokenPunct.SCOLON);
n.key = nkey.getText();
n.val = nval.getText();
return n;
}
public void asS2 (Indenter o)
{
o.tabwrite("layerinfo ");
o.write(Backend.quoteString(key));
o.write(" = ");
o.write(Backend.quoteString(val));
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwriteln("set_layer_info("+
bp.getLayerIDString() + "," +
bp.quoteString(key) + "," +
bp.quoteString(val) + ");");
}
public void check (Layer l, Checker ck) throws Exception
{
l.setLayerInfo(key, val);
}
};

View File

@@ -0,0 +1,60 @@
package danga.s2;
public class NodeLogAndExpr extends Node
{
Node lhs;
Node rhs;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeEqExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeLogAndExpr n = new NodeLogAndExpr();
n.lhs = NodeEqExpr.parse(toker);
n.addNode(n.lhs);
Token t = toker.peek();
if (t.equals(TokenKeyword.AND)) {
n.eatToken(toker);
} else {
return n.lhs;
}
n.rhs = NodeEqExpr.parse(toker);
n.addNode(n.rhs);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type lt = lhs.getType(ck);
Type rt = rhs.getType(ck);
if (! lt.equals(rt) || ! lt.isBoolable())
throw new Exception("The left and right side of the 'and' expression must "+
"both be of either type bool or int at "+getFilePos());
return lt;
}
public void asS2 (Indenter o) {
lhs.asS2(o);
if (rhs != null) {
o.write(" and ");
rhs.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o) {
lhs.asPerl(bp, o);
if (rhs != null) {
o.write(" && ");
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,61 @@
package danga.s2;
public class NodeLogOrExpr extends Node
{
Node lhs;
TokenKeyword op;
Node rhs;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeLogAndExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeLogOrExpr n = new NodeLogOrExpr();
n.lhs = NodeLogAndExpr.parse(toker);
n.addNode(n.lhs);
Token t = toker.peek();
if (t.equals(TokenKeyword.OR) || t.equals(TokenKeyword.XOR)) {
n.op = (TokenKeyword) t;
n.eatToken(toker);
} else {
return n.lhs;
}
n.rhs = NodeLogOrExpr.parse(toker);
n.addNode(n.rhs);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type lt = lhs.getType(ck);
Type rt = rhs.getType(ck);
if (! lt.equals(rt) || ! lt.isBoolable())
throw new Exception("The left and right side of the 'or' expression must "+
"both be of either type bool or int at "+getFilePos());
return lt;
}
public void asS2 (Indenter o) {
lhs.asS2(o);
if (rhs != null) {
o.write(" " + op.getIdent() + " ");
rhs.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o) {
lhs.asPerl(bp, o);
if (rhs != null) {
o.write(" || ");
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,48 @@
package danga.s2;
public class NodeNamedType extends Node
{
public Type type;
public NodeType typenode;
public String name;
public Type getType () {
return type;
}
public String getName () {
return name;
}
public NodeNamedType () {
}
public NodeNamedType (String name, Type type) {
this.name = name;
this.type = type;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeNamedType n = new NodeNamedType();
n.typenode = (NodeType) NodeType.parse(toker);
n.type = n.typenode.getType();
n.addNode(n.typenode);
n.name = n.getIdent(toker).getIdent();
return n;
}
public void asS2 (Indenter o)
{
typenode.asS2(o);
o.write(" " + name);
}
public String toString () // was asString
{
return type.toString() + " " + name;
}
};

View File

@@ -0,0 +1,66 @@
package danga.s2;
public class NodePrintStmt extends Node
{
NodeExpr expr;
boolean doNewline = false;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.PRINT) ||
toker.peek().equals(TokenKeyword.PRINTLN) ||
toker.peek() instanceof TokenStringLiteral)
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodePrintStmt n = new NodePrintStmt();
Token t = toker.peek();
if (t.equals(TokenKeyword.PRINT)) {
n.setStart(n.eatToken(toker));
}
if (t.equals(TokenKeyword.PRINTLN)) {
n.setStart(n.eatToken(toker));
n.doNewline = true;
}
n.addNode(n.expr = (NodeExpr) NodeExpr.parse(toker));
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
Type t = expr.getType(ck);
if (t.equals(Type.INT) || t.equals(Type.STRING)) {
return;
}
throw new Exception("Print statement must print an expression of type "
+"int or string, not "+t+" at "+expr.getFilePos());
}
public void asS2 (Indenter o)
{
if (doNewline)
o.tabwrite("println ");
else
o.tabwrite("print ");
expr.asS2(o);
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwrite("pout(");
expr.asPerl(bp, o);
if (doNewline) {
o.write(" . \"\\n\"");
}
o.writeln(");");
}
};

View File

@@ -0,0 +1,89 @@
package danga.s2;
public class NodeProduct extends Node
{
Node lhs;
TokenPunct op;
Node rhs;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeUnaryExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
Node lhs = NodeUnaryExpr.parse(toker);
while (toker.peek().equals(TokenPunct.MULT) ||
toker.peek().equals(TokenPunct.DIV) ||
toker.peek().equals(TokenPunct.MOD)) {
lhs = parseAnother(toker, lhs);
}
return lhs;
}
private static Node parseAnother (Tokenizer toker, Node lhs) throws Exception
{
NodeProduct n = new NodeProduct();
n.lhs = lhs;
n.addNode(n.lhs);
n.op = (TokenPunct) toker.peek();
n.eatToken(toker);
n.skipWhite(toker);
n.rhs = NodeUnaryExpr.parse(toker);
n.addNode(n.rhs);
n.skipWhite(toker);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type lt = lhs.getType(ck);
Type rt = rhs.getType(ck);
if (! rt.equals(Type.INT)) {
throw new Exception("Right hand side of " + op.getPunct() + " operator is not an integer at "+
rhs.getFilePos());
}
if (! lt.equals(Type.INT)) {
throw new Exception("Left hand side of " + op.getPunct() + " operator is not an integer at "+
lhs.getFilePos());
}
return Type.INT;
}
public void asS2 (Indenter o)
{
BackendS2.LParen(o);
lhs.asS2(o);
if (op != null) {
o.write(" " + op.getPunct() + " ");
rhs.asS2(o);
}
BackendS2.RParen(o);
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (op == TokenPunct.DIV)
o.write("int(");
lhs.asPerl(bp, o);
if (op != null) {
if (op == TokenPunct.MULT)
o.write(" * ");
else if (op == TokenPunct.DIV)
o.write(" / ");
else if (op == TokenPunct.MOD)
o.write(" % ");
rhs.asPerl(bp, o);
if (op == TokenPunct.DIV)
o.write(")");
}
}
}

View File

@@ -0,0 +1,189 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class NodeProperty extends Node
{
NodeNamedType nt;
LinkedList pairs;
boolean builtin = false, use = false, hide = false;
String uhName; // if use or hide, then this is property to use/hide
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.PROPERTY))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeProperty n = new NodeProperty();
n.pairs = new LinkedList();
n.setStart(n.requireToken(toker, TokenKeyword.PROPERTY));
if (toker.peek().equals(TokenKeyword.BUILTIN)) {
n.builtin = true;
n.eatToken(toker);
}
// parse the use/hide case
if (toker.peek() instanceof TokenIdent) {
String ident = ((TokenIdent) toker.peek()).getIdent();
if (ident.equals("use") || ident.equals("hide")) {
if (ident.equals("use")) n.use = true;
if (ident.equals("hide")) n.hide = true;
n.eatToken(toker);
Token t = toker.peek();
if (! (t instanceof TokenIdent)) {
throw new Exception("Expecting identifer after "+ident+" at "+t.getFilePos());
}
n.uhName = ((TokenIdent) toker.peek()).getIdent();
n.eatToken(toker);
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
}
n.addNode(n.nt = (NodeNamedType) NodeNamedType.parse(toker));
Token t = toker.peek();
if (t.equals(TokenPunct.SCOLON)) {
n.eatToken(toker);
return n;
}
n.requireToken(toker, TokenPunct.LBRACE);
while (NodePropertyPair.canStart(toker)) {
Node pair = NodePropertyPair.parse(toker);
n.tokenlist.add(pair);
n.pairs.add(pair);
}
n.requireToken(toker, TokenPunct.RBRACE);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
if (use) {
if (! l.getType().equals("layout")) {
throw new Exception("Can't declare property usage in non-layout layer at"
+ getFilePos());
}
if (ck.propertyType(uhName) == null) {
throw new Exception("Can't declare usage of non-existent property at"
+ getFilePos());
}
return;
}
if (hide) {
if (ck.propertyType(uhName) == null) {
throw new Exception("Can't hide non-existent property at"
+ getFilePos());
}
return;
}
String name = nt.getName();
Type type = nt.getType();
if (l.getType().equals("i18n")) {
// FIXME: as a special case, allow an i18n layer to
// to override the 'des' property of a property, so
// that stuff can be translated
return;
}
// only core and layout layers can define properties
if (! l.isCoreOrLayout()) {
throw new Exception("Only core and layout layers can define new properties.");
}
// make sure they aren't overriding a property from a lower layer
Type existing = ck.propertyType(name);
if (existing != null && ! type.equals(existing)) {
throw new Exception("Can't override property '" + name +
"' at " + getFilePos() + " of type "+existing+
" with new type "+type+".");
}
String basetype = type.baseType();
if (! Type.isPrimitive(basetype) && ck.getClass(basetype) == null) {
throw new Exception("Can't define a property of an unknown class "+
"at "+nt.getFilePos());
}
// all is well, so register this property with its type
ck.addProperty(name, type);
}
public void asS2 (Indenter o)
{
o.tabwrite("property ");
if (builtin) { o.write("builtin "); }
if (use || hide) {
if (use) o.write("use ");
if (hide) o.write("hide ");
o.write(uhName);
o.writeln(";");
return;
}
nt.asS2(o);
if (pairs.size() > 0) {
o.writeln(" {");
o.tabIn();
ListIterator li = pairs.listIterator(0);
while (li.hasNext()) {
NodePropertyPair pp = (NodePropertyPair) li.next();
pp.asS2(o);
}
o.tabOut();
o.writeln("}");
} else {
o.writeln(";");
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (use) {
o.tabwriteln("register_property_use("+
bp.getLayerIDString() + "," +
bp.quoteString(uhName) + ");");
return;
}
if (hide) {
o.tabwriteln("register_property_hide("+
bp.getLayerIDString() + "," +
bp.quoteString(uhName) + ");");
return;
}
o.tabwriteln("register_property("+
bp.getLayerIDString() + "," +
bp.quoteString(nt.getName()) + ",{");
o.tabIn();
o.tabwriteln("\"type\"=>" +
bp.quoteString(nt.getType().toString())+",");
ListIterator li = pairs.listIterator();
while (li.hasNext()) {
NodePropertyPair pp = (NodePropertyPair) li.next();
o.tabwriteln(bp.quoteString(pp.getKey()) + "=>" +
bp.quoteString(pp.getVal()) + ",");
}
o.tabOut();
o.writeln("});");
}
};

View File

@@ -0,0 +1,40 @@
package danga.s2;
public class NodePropertyPair extends Node
{
NodeText key;
NodeText val;
public String getKey () { return key.getText(); }
public String getVal () { return val.getText(); }
public static boolean canStart (Tokenizer toker) throws Exception
{
if (NodeText.canStart(toker))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodePropertyPair n = new NodePropertyPair();
n.addNode(n.key = (NodeText) NodeText.parse(toker));
n.requireToken(toker, TokenPunct.ASSIGN);
n.addNode(n.val = (NodeText) NodeText.parse(toker));
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void asS2 (Indenter o)
{
o.doTab();
key.asS2(o);
o.write(" = ");
val.asS2(o);
o.writeln(";");
}
};

View File

@@ -0,0 +1,77 @@
package danga.s2;
public class NodeRange extends Node
{
Node lhs;
Node rhs;
public NodeRange() {
}
public NodeRange(Node start, Node end) {
this.lhs = start;
this.rhs = end;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeLogOrExpr.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeRange n = new NodeRange();
n.lhs = NodeLogOrExpr.parse(toker);
n.addNode(n.lhs);
if (toker.peek().equals(TokenPunct.DOTDOT)) {
n.eatToken(toker);
n.skipWhite(toker);
} else {
return n.lhs;
}
n.rhs = NodeLogOrExpr.parse(toker);
n.addNode(n.rhs);
return n;
}
public Type getType (Checker ck) throws Exception
{
return getType(ck, null);
}
public Type getType (Checker ck, Type wanted) throws Exception
{
Type lt = lhs.getType(ck, wanted);
Type rt = rhs.getType(ck, wanted);
if (! lt.equals(Type.INT)) {
throw new Exception("Left operand of '..' range operator is not int at "+lhs.getFilePos());
}
if (! rt.equals(Type.INT)) {
throw new Exception("Right operand of '..' range operator is not int at "+rhs.getFilePos());
}
Type ret = new Type("int");
ret.makeArrayOf();
return ret;
}
public void asS2 (Indenter o)
{
lhs.asS2(o);
o.write(" .. ");
rhs.asS2(o);
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.write("[");
lhs.asPerl(bp, o);
o.write(" .. ");
rhs.asPerl(bp, o);
o.write("]");
}
}

View File

@@ -0,0 +1,93 @@
package danga.s2;
public class NodeRelExpr extends Node
{
Node lhs;
TokenPunct op;
Node rhs;
private Type myType; // for backend later
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeSum.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeRelExpr n = new NodeRelExpr();
n.lhs = NodeSum.parse(toker);
n.addNode(n.lhs);
Token t = toker.peek();
if (t.equals(TokenPunct.LT) || t.equals(TokenPunct.LTE) ||
t.equals(TokenPunct.GT) || t.equals(TokenPunct.GTE)) {
n.op = (TokenPunct) t;
n.eatToken(toker);
n.skipWhite(toker);
} else {
return n.lhs;
}
n.rhs = NodeSum.parse(toker);
n.skipWhite(toker);
return n;
}
public Type getType (Checker ck) throws Exception
{
Type lt = lhs.getType(ck);
Type rt = rhs.getType(ck);
if (! lt.equals(rt))
throw new Exception("The types of the left and right hand side of "+
"comparision test expression don't match at "+getFilePos());
if (lt.equals(Type.STRING) || lt.equals(Type.INT)) {
myType = lt;
return Type.BOOL;
}
throw new Exception ("Only string and int types can be compared at "+
getFilePos());
}
public void asS2 (Indenter o)
{
lhs.asS2(o);
if (op != null) {
o.write(" " + op.getPunct() + " ");
rhs.asS2(o);
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
lhs.asPerl(bp, o);
if (op != null) {
if (op.equals(TokenPunct.LT)) {
if (myType.equals(Type.STRING))
o.write(" lt ");
else
o.write(" < ");
} else if (op.equals(TokenPunct.LTE)) {
if (myType.equals(Type.STRING))
o.write(" le ");
else
o.write(" <= ");
} else if (op.equals(TokenPunct.GT)) {
if (myType.equals(Type.STRING))
o.write(" gt ");
else
o.write(" > ");
} else if (op.equals(TokenPunct.GTE)) {
if (myType.equals(Type.STRING))
o.write(" ge ");
else
o.write(" >= ");
}
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,63 @@
package danga.s2;
public class NodeReturnStmt extends Node
{
NodeExpr expr;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.RETURN))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeReturnStmt n = new NodeReturnStmt();
n.setStart(n.requireToken(toker, TokenKeyword.RETURN));
// optional return expression
if (NodeExpr.canStart(toker)) {
n.addNode(n.expr = (NodeExpr) NodeExpr.parse(toker));
}
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
Type exptype = ck.getReturnType();
Type rettype = expr != null ? expr.getType(ck) : Type.VOID;
if (! ck.typeIsa(rettype, exptype)) {
throw new Exception("Return type of "+rettype+" at "+
getFilePos()+" doesn't match expected type of "+
exptype+" for this function.");
}
}
public void asS2 (Indenter o)
{
o.tabwrite("return");
if (expr != null) {
o.write(" ");
expr.asS2(o);
}
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwrite("return");
if (expr != null) {
o.write(" ");
expr.asPerl(bp, o);
}
o.writeln(";");
}
};

View File

@@ -0,0 +1,90 @@
package danga.s2;
public class NodeSet extends Node
{
String key;
NodeExpr value;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.SET))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeText nkey;
NodeSet ns = new NodeSet();
ns.setStart(ns.requireToken(toker, TokenKeyword.SET));
nkey = (NodeText) NodeText.parse(toker);
ns.addNode(nkey);
ns.requireToken(toker, TokenPunct.ASSIGN);
ns.value = (NodeExpr) NodeExpr.parse(toker);
ns.addNode(ns.value);
ns.requireToken(toker, TokenPunct.SCOLON);
ns.key = nkey.getText();
return ns;
}
public void asS2 (Indenter o)
{
o.tabwrite("set ");
o.write(Backend.quoteString(key));
o.write(" = ");
value.asS2(o);
o.writeln(";");
}
public void check (Layer l, Checker ck) throws Exception
{
Type ltype = ck.propertyType(key);
ck.setInFunction(false);
// check to see that the thing we're setting exists
if (ltype == null) {
throw new Exception("Can't set non-existent property '" + key + "' at " +
getFilePos());
}
Type rtype = value.getType(ck, ltype);
if (! ltype.equals(rtype)) {
throw new Exception("Property value is of wrong type at "+getFilePos());
}
// simple case... assigning a primitive
if (ltype.isPrimitive()) {
// TODO: check that value.isLiteral()
// TODO: check value's type matches
return;
}
Type base = new Type(ltype.baseType());
if (base.isPrimitive()) {
return;
} else if (ck.getClass(ltype.baseType()) == null) {
throw new Exception("Can't set property of unknown type at "+
getFilePos());
}
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.tabwrite("register_set("+
bp.getLayerIDString() + "," +
bp.quoteString(key) + ",");
value.asPerl(bp, o);
o.writeln(");");
return;
}
};

View File

@@ -0,0 +1,48 @@
package danga.s2;
public class NodeStmt extends Node
{
public static boolean canStart (Tokenizer toker) throws Exception
{
if (NodePrintStmt.canStart(toker) ||
NodeIfStmt.canStart(toker) ||
NodeReturnStmt.canStart(toker) ||
NodeDeleteStmt.canStart(toker) ||
NodeForeachStmt.canStart(toker) ||
NodeVarDeclStmt.canStart(toker) ||
NodeExprStmt.canStart(toker) ||
false)
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
if (NodePrintStmt.canStart(toker))
return NodePrintStmt.parse(toker);
if (NodeIfStmt.canStart(toker))
return NodeIfStmt.parse(toker);
if (NodeReturnStmt.canStart(toker))
return NodeReturnStmt.parse(toker);
if (NodeDeleteStmt.canStart(toker))
return NodeDeleteStmt.parse(toker);
if (NodeForeachStmt.canStart(toker))
return NodeForeachStmt.parse(toker);
if (NodeVarDeclStmt.canStart(toker))
return NodeVarDeclStmt.parse(toker);
// important that this is last:
//(otherwise idents would be seen as function calls)
if (NodeExprStmt.canStart(toker))
return NodeExprStmt.parse(toker);
throw new Exception("don't know how to parse this type of statement");
}
};

View File

@@ -0,0 +1,152 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Hashtable;
import java.util.NoSuchElementException;
public class NodeStmtBlock extends Node
{
protected LinkedList stmtlist = new LinkedList ();
protected Type returnType;
protected Hashtable localvars = new Hashtable (); // String -> Type
public static Node parse (Tokenizer toker) throws Exception
{
NodeStmtBlock ns = new NodeStmtBlock();
ns.setStart(ns.requireToken(toker, TokenPunct.LBRACE));
boolean loop = true;
boolean closed = false;
do {
ns.skipWhite(toker);
Token p = toker.peek();
if (p == null) {
loop = false;
} else if (p.equals(TokenPunct.RBRACE)) {
ns.eatToken(toker);
loop = false;
closed = true;
}
else if (NodeStmt.canStart(toker)) {
Node s = NodeStmt.parse(toker);
ns.stmtlist.add(s);
ns.addNode(s);
}
else {
throw new Exception("Unexpected token at " + toker.locationString() +
" while parsing statement block: " + p.toString());
}
}
while (loop);
if (! closed)
throw new Exception("Didn't find closing brace in statement block");
return ns;
}
public void addLocalVar (String v, Type t) {
localvars.put(v, t);
}
public Type getLocalVar (String v) {
return (Type) localvars.get(v);
}
public void setReturnType (Type t) {
returnType = t;
}
public boolean willReturn ()
{
Node ns;
// find the last statement in the block, if one exists
try {
ns = (Node) stmtlist.getLast();
} catch (NoSuchElementException e) {
return false;
}
if (ns instanceof NodeReturnStmt) {
// a return statement obviously returns
return true;
} else if (ns instanceof NodeIfStmt) {
// and if statement at the end of a function returns
// if all paths return, so ask the ifstatement
NodeIfStmt ni = (NodeIfStmt) ns;
return ni.willReturn();
} else {
// all other types of statements don't return
return false;
}
}
public void check (Layer l, Checker ck) throws Exception
{
ListIterator li = stmtlist.listIterator();
// set the return type for any returnstmts that need it.
// NOTE: the returnType is non-null if and only if it's
// attached to a function.
if (returnType != null) {
ck.setReturnType(returnType);
}
while (li.hasNext()) {
Node ns = (Node) li.next();
ns.check(l, ck);
if (! li.hasNext() &&
returnType != null &&
! returnType.equals(Type.VOID) &&
! willReturn()) {
throw new Exception("Statement block at "+getFilePos()+
" isn't guaranteed to return type "+
returnType);
}
}
}
public void asS2 (Indenter o) {
o.writeln("{");
o.tabIn();
ListIterator li = stmtlist.listIterator();
while (li.hasNext()) {
Node ns = (Node) li.next();
ns.asS2(o);
}
o.tabOut();
o.tabwrite("}");
}
public void asPerl (BackendPerl bp, Indenter o)
{
asPerl(bp, o, true);
}
public void asPerl (BackendPerl bp, Indenter o, boolean doCurlies)
{
if (doCurlies) {
o.writeln("{");
o.tabIn();
}
ListIterator li = stmtlist.listIterator();
while (li.hasNext()) {
Node n = (Node) li.next();
n.asPerl(bp, o);
}
if (doCurlies) {
o.tabOut();
o.tabwrite("}");
}
}
};

119
wcmtools/s2/danga/s2/NodeSum.java Executable file
View File

@@ -0,0 +1,119 @@
package danga.s2;
public class NodeSum extends Node
{
Node lhs;
TokenPunct op;
Node rhs;
// use this for the backend to decide which add operator to use
private Type myType;
public NodeSum () {
}
public NodeSum (Node lhs, TokenPunct op, Node rhs)
{
this.lhs = lhs;
this.op = op;
this.rhs = rhs;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeProduct.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
Node lhs = NodeProduct.parse(toker);
lhs.skipWhite(toker);
while(toker.peek().equals(TokenPunct.PLUS) ||
toker.peek().equals(TokenPunct.MINUS)) {
lhs = parseAnother(toker, lhs);
}
return lhs;
}
private static Node parseAnother (Tokenizer toker, Node lhs) throws Exception
{
NodeSum n = new NodeSum();
n.lhs = lhs;
n.addNode(n.lhs);
n.op = (TokenPunct) toker.peek();
n.eatToken(toker);
n.skipWhite(toker);
n.rhs = NodeProduct.parse(toker);
n.addNode(n.rhs);
n.skipWhite(toker);
return n;
}
public Type getType (Checker ck) throws Exception
{
return getType(ck, null);
}
public Type getType (Checker ck, Type wanted) throws Exception
{
Type lt = lhs.getType(ck, wanted);
Type rt = rhs.getType(ck, wanted);
if (! (lt.equals(Type.INT) || lt.equals(Type.STRING))) {
if (lhs.makeAsString(ck))
lt = Type.STRING;
else
throw new Exception("Left hand side of + operator is "+lt+", not a string or "+
"integer at "+lhs.getFilePos());
}
if (! (rt.equals(Type.INT) || rt.equals(Type.STRING))) {
if (rhs.makeAsString(ck))
rt = Type.STRING;
else
throw new Exception("Right hand side of + operator is "+rt+", not a string or "+
"integer at "+rhs.getFilePos());
}
// can't subtract strings
if (op == TokenPunct.MINUS && (lt.equals(Type.STRING) ||
rt.equals(Type.STRING))) {
throw new Exception("Can't subtract strings at "+rhs.getFilePos());
}
// all summations involving a string on either side are promoted
// to a concatenation
if (lt.equals(Type.STRING) || rt.equals(Type.STRING)) {
return (myType = Type.STRING);
}
return (myType = Type.INT);
}
public void asS2 (Indenter o)
{
BackendS2.LParen(o);
lhs.asS2(o);
if (op != null) {
o.write(" " + op.getPunct() + " ");
rhs.asS2(o);
}
BackendS2.RParen(o);
}
public void asPerl (BackendPerl bp, Indenter o)
{
lhs.asPerl(bp, o);
if (op != null) {
if (myType == Type.STRING)
o.write(" . ");
else if (op == TokenPunct.PLUS)
o.write(" + ");
else if (op == TokenPunct.MINUS)
o.write(" - ");
rhs.asPerl(bp, o);
}
}
}

View File

@@ -0,0 +1,717 @@
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;
}
}
}

View File

@@ -0,0 +1,62 @@
package danga.s2;
public class NodeText extends Node
{
String text;
public String getText () {
return text;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
Token t = toker.peek();
if (t instanceof TokenIdent ||
t instanceof TokenIntegerLiteral ||
t instanceof TokenStringLiteral)
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeText nt = new NodeText();
nt.skipWhite(toker);
Token t = toker.peek();
if (t instanceof TokenIdent ||
t instanceof TokenIntegerLiteral ||
t instanceof TokenStringLiteral) {
if (t instanceof TokenIdent) {
TokenIdent ti = (TokenIdent) t;
nt.text = ti.getIdent();
ti.setType(TokenIdent.STRING);
}
if (t instanceof TokenIntegerLiteral) {
int iv = ((TokenIntegerLiteral)t).val;
nt.text = (new Integer(iv)).toString();
}
if (t instanceof TokenStringLiteral) {
TokenStringLiteral ts = (TokenStringLiteral) t;
//FIXME: check for unclosed side.
nt.text = ts.text;
}
nt.eatToken(toker);
} else {
throw new Exception("Expecting text (integer, string, or identifer)");
}
return nt;
}
public void asS2 (Indenter o)
{
o.write(Backend.quoteString(text));
}
};

View File

@@ -0,0 +1,45 @@
package danga.s2;
public class NodeType extends Node
{
private Type type;
public Type getType () { return type; }
public static Node parse (Tokenizer toker) throws Exception
{
NodeType n = new NodeType();
TokenIdent base = (TokenIdent) n.getIdent(toker, true, false);
base.setType(TokenIdent.TYPE);
n.type = new Type(base.getIdent());
while (toker.peek().equals(TokenPunct.LBRACK) ||
toker.peek().equals(TokenPunct.LBRACE)) {
Token t = toker.peek();
n.eatToken(toker, false);
if (t.equals(TokenPunct.LBRACK)) {
n.requireToken(toker, TokenPunct.RBRACK, false);
n.type.makeArrayOf();
}
if (t.equals(TokenPunct.LBRACE)) {
n.requireToken(toker, TokenPunct.RBRACE, false);
n.type.makeHashOf();
}
}
// If the type was a simple type, we have to remove whitespace,
// since we explictly said not to above.
n.skipWhite(toker);
return n;
}
public void asS2 (Indenter o)
{
o.write(type.toString());
}
};

View File

@@ -0,0 +1,71 @@
package danga.s2;
public class NodeUnaryExpr extends Node
{
boolean bNot = false;
boolean bNegative = false;
Node expr;
public static boolean canStart (Tokenizer toker) throws Exception
{
return (toker.peek().equals(TokenPunct.MINUS) ||
toker.peek().equals(TokenKeyword.NOT) ||
NodeIncExpr.canStart(toker));
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeUnaryExpr n = new NodeUnaryExpr();
if (toker.peek().equals(TokenPunct.MINUS)) {
n.bNegative = true;
n.eatToken(toker);
n.skipWhite(toker);
} else if (toker.peek().equals(TokenKeyword.NOT)) {
n.bNot = true;
n.eatToken(toker);
n.skipWhite(toker);
}
Node expr = NodeIncExpr.parse(toker);
if (n.bNegative || n.bNot) {
n.expr = expr;
n.addNode(n.expr);
return n;
}
return expr;
}
public Type getType (Checker ck) throws Exception
{
Type t = expr.getType(ck);
if (bNegative) {
if (! t.equals(Type.INT))
throw new Exception("Can't use unary minus on non-integer. Type = "+t);
return Type.INT;
}
if (bNot) {
if (! t.equals(Type.BOOL))
throw new Exception("Can't use NOT operator on non-boolean. Type = "+t);
return Type.BOOL;
}
return null;
}
public void asPerl (BackendPerl bp, Indenter o)
{
if (bNot) o.write("! ");
if (bNegative) o.write("-");
expr.asPerl(bp, o);
}
public void asS2 (Indenter o)
{
if (bNot) o.write("not ");
if (bNegative) o.write("-");
expr.asS2(o);
}
}

View File

@@ -0,0 +1,31 @@
package danga.s2;
public class NodeUnnecessary extends Node
{
public static Node parse (Tokenizer toker) throws Exception
{
Node n = new NodeUnnecessary();
n.skipWhite(toker);
return n;
}
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().isNecessary() == false)
return true;
return false;
}
public void asS2 (Indenter o) {
// do nothing when making the canonical S2
}
public void asPerl (BackendPerl bp, Indenter o) {
// do nothing when doing the perl output
}
public void check (Layer l, Checker ck) throws Exception {
// nothing can be wrong with whitespace and comments
}
};

View File

@@ -0,0 +1,55 @@
package danga.s2;
public class NodeVarDecl extends Node
{
NodeNamedType nt;
public static boolean canStart (Tokenizer toker) throws Exception
{
if (toker.peek().equals(TokenKeyword.VAR))
return true;
return false;
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeVarDecl n = new NodeVarDecl();
n.setStart(n.requireToken(toker, TokenKeyword.VAR));
n.addNode(n.nt = (NodeNamedType) NodeNamedType.parse(toker));
return n;
}
public Type getType () {
return nt.getType();
}
public String getName () {
return nt.getName();
}
public void populateScope (NodeStmtBlock nb) throws Exception
{
String name = nt.getName();
Type et = nb.getLocalVar(name);
if (et == null) {
nb.addLocalVar(name, nt.getType());
return;
}
throw new Exception("Can't mask local variable '"+name+"' at "+getFilePos());
}
public void asS2 (Indenter o) {
// Note: no tabbing, as this may be in a foreach. nodes using
// VarDecl nodes must do their own tabbing (NodeClass, NodeVarDeclStmt)
o.write("var ");
nt.asS2(o);
}
public void asPerl (BackendPerl bp, Indenter o) {
// Note: no tabbing, as this may be in a foreach. nodes using
// VarDecl nodes must do their own tabbing (NodeClass, NodeVarDeclStmt)
o.write("my $" + nt.getName());
}
}

View File

@@ -0,0 +1,91 @@
package danga.s2;
public class NodeVarDeclStmt extends Node
{
NodeVarDecl nvd;
NodeExpr expr;
public static boolean canStart (Tokenizer toker) throws Exception
{
return NodeVarDecl.canStart(toker);
}
public static Node parse (Tokenizer toker) throws Exception
{
NodeVarDeclStmt n = new NodeVarDeclStmt();
n.addNode(n.nvd = (NodeVarDecl) NodeVarDecl.parse(toker));
if (toker.peek().equals(TokenPunct.ASSIGN)) {
n.eatToken(toker);
n.expr = (NodeExpr) NodeExpr.parse(toker);
n.addNode(n.expr);
}
n.requireToken(toker, TokenPunct.SCOLON);
return n;
}
public void check (Layer l, Checker ck) throws Exception
{
nvd.populateScope(ck.getLocalScope());
// check that the variable type is a known class
Type t = nvd.getType();
String bt = t.baseType();
if (! Type.isPrimitive(bt) &&
ck.getClass(bt) == null) {
throw new Exception("Unknown type or class '"+bt+"' at "+
nvd.getFilePos());
}
if (expr != null) {
Type et = expr.getType(ck, t);
if (! ck.typeIsa(et, t)) {
throw new Exception("Can't initialize variable '"+nvd.getName()+"' "+
"of type "+t+" with expression of type "+
et+" at "+expr.getFilePos());
}
}
// can't be named $_ctx (conflicts with perl backend)
String vname = nvd.getName();
if (vname.equals("_ctx")) {
throw new Exception("Reserved variable name '_ctx' in use at "+
getFilePos());
}
}
public void asS2 (Indenter o) {
o.doTab();
nvd.asS2(o);
if (expr != null) {
o.write(" = ");
expr.asS2(o);
}
o.writeln(";");
}
public void asPerl (BackendPerl bp, Indenter o) {
o.doTab();
nvd.asPerl(bp, o);
if (expr != null) {
o.write(" = ");
expr.asPerl(bp, o);
} else {
Type t = nvd.getType();
if (t.equals(Type.STRING)) {
o.write(" = \"\"");
} else if (t.equals(Type.INT) || t.equals(Type.BOOL)) {
o.write(" = 0");
}
}
//else {
// o.write(" = { '_type' => " + bp.quoteString(t.toString()) + "}");
//}
o.writeln(";");
}
}

View File

@@ -0,0 +1,356 @@
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'}");
}
}
};

View File

@@ -0,0 +1,22 @@
package danga.s2;
/* if I knew java.io.* more, I'd probably not write this. */
public abstract class Output {
public abstract void write (String s);
public void write (int i) {
write(new Integer(i).toString());
}
public void writeln (int i) {
write(new Integer(i).toString());
newline();
}
public void writeln (String s) {
write(s);
newline();
}
public abstract void newline ();
public void flush () { }
}

View File

@@ -0,0 +1,38 @@
package danga.s2;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;
import java.io.Writer;
public class OutputConsole extends Output {
BufferedWriter out;
public OutputConsole () {
try {
out = new BufferedWriter(new OutputStreamWriter(System.out, "UTF-8"),
4096);
} catch (Exception e) {
System.err.println("ERROR: Java installation doesn't support UTF-8?");
}
}
public void write (String s) {
try {
out.write(s);
} catch (Exception e) {
System.err.println("Error: "+e.toString());
}
}
public void newline () {
write("\n");
}
public void flush () {
try { out.flush(); }
catch (Exception e) {
System.err.println("UTF-8 output flush failed");
}
}
}

View File

@@ -0,0 +1,26 @@
package danga.s2;
/* if I knew java.io.* more, I'd probably not write this. */
public class OutputStringBuffer extends Output {
StringBuffer sb;
public OutputStringBuffer () {
sb = new StringBuffer();
}
public void write (String s) {
if (s == null) {
System.err.println("\n write s==null");
}
sb.append(s);
}
public void newline () {
sb.append('\n');
}
public void writeTo (Indenter i) {
i.write(sb.toString());
}
}

View File

@@ -0,0 +1,99 @@
package danga.s2;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
public class Scanner
{
public int line;
public int col;
BufferedReader is;
boolean havePeeked;
char peekedChar;
boolean fakeNewline = false;
boolean bogusInput = false;
public Scanner (InputStream is)
{
try {
this.is = new BufferedReader(new InputStreamReader(is, "UTF-8"), 4096);
} catch (Exception e) {
System.err.println("Scanner Error: "+e);
}
line = 1;
col = 1;
havePeeked = false;
}
public boolean getBogusFlag() {
return bogusInput;
}
public char peek ()
{
if (havePeeked) {
return peekedChar;
}
havePeeked = true;
try {
peekedChar = (char) is.read();
if (peekedChar == (char) 0) peekedChar = (char) -1;
} catch (Exception e) {
bogusInput = true;
peekedChar = (char) -1;
}
return peekedChar;
}
// get a char, or -1 if the end of the stream is hit.
public char getChar ()
{
char ch = peek();
havePeeked = false;
if (ch == '\n') {
if (fakeNewline) {
fakeNewline = false;
} else {
line++;
col = 0;
}
}
if (ch == '\t')
col += 4; // stupid assumption.
else
col++;
return ch;
}
public void forceNextChar (char ch)
{
if (ch == '\n') fakeNewline = true;
havePeeked = true;
peekedChar = ch;
}
public String locationString ()
{
return ("line " + line + ", column " + col + ".");
}
// get a char that isn't an end of file.
public char getRealChar () throws Exception
{
char ch = getChar();
if (ch == -1) {
throw new Exception("Unexpected end of file!");
}
return ch;
}
}

24
wcmtools/s2/danga/s2/Token.java Executable file
View File

@@ -0,0 +1,24 @@
package danga.s2;
public abstract class Token {
public FilePos pos;
public FilePos getFilePos () {
return pos;
}
public boolean isNecessary() {
return true;
}
public abstract String toString();
public abstract void asHTML (Output o);
public void asS2 (Indenter o) {
o.write("##Token::asS2##");
}
public void asPerl (BackendPerl bp, Indenter o) {
o.write("##Token::asPerl##");
}
}

View File

@@ -0,0 +1,35 @@
package danga.s2;
class TokenComment extends Token {
String com;
public boolean isNecessary() { return false; }
public TokenComment (String com) {
this.com = com;
}
public String getComment() {
return com;
}
public String toString() {
return ("[TokenComment]");
}
public static Token scan (Tokenizer t)
{
StringBuffer tbuf = new StringBuffer(80);
while ((t.peekChar() != '\n')) {
tbuf.append(t.getChar());
}
return new TokenComment(tbuf.toString());
}
public void asHTML (Output o)
{
o.write("<span class=\"c\">" + com + "</span>");
}
}

View File

@@ -0,0 +1,82 @@
package danga.s2;
class TokenIdent extends Token {
public final static int DEFAULT = 0;
public final static int TYPE = 1;
public final static int STRING = 2;
int type;
String ident;
// dummy constructor for subclass
public TokenIdent () {
this.ident = null;
}
public TokenIdent (String ident) {
this.ident = ident;
}
public String getIdent() {
return ident;
}
public String toString() {
return ("[TokenIdent] = " + ident);
}
public void setType (int type) {
this.type = type;
}
public static boolean canStart (Tokenizer t)
{
char nextchar = t.peekChar();
if ((nextchar >= 'a' && nextchar <= 'z') ||
(nextchar >= 'A' && nextchar <= 'Z') ||
(nextchar == '_'))
return true;
return false;
}
public static Token scan (Tokenizer t)
{
StringBuffer tbuf = new StringBuffer(15);
while ((t.peekChar() >= 'a' && t.peekChar() <= 'z') ||
(t.peekChar() >= 'A' && t.peekChar() <= 'Z') ||
(t.peekChar() >= '0' && t.peekChar() <= '9') ||
(t.peekChar() == '_'))
{
tbuf.append(t.getChar());
}
String token = tbuf.toString();
Token kwtok = TokenKeyword.tokenFromString(token);
Token ret = (kwtok != null) ? kwtok : new TokenIdent(token);
return ret;
}
public void asHTML (Output o)
{
String c = BackendHTML.IdentColor;
if (type == TYPE) c = BackendHTML.TypeColor;
else if (type == STRING) c = BackendHTML.StringColor;
// FIXME: TODO: Don't hardcode internal types, intelligently recognise
// places where types and class references occur and
// make them class="t"
if (ident.equals("int") || ident.equals("void") ||
ident.equals("string") || ident.equals("bool")) {
o.write("<span class=\"t\">" + ident + "</span>");
} else {
o.write("<span class=\"i\">" + ident + "</span>");
}
}
}

View File

@@ -0,0 +1,46 @@
package danga.s2;
class TokenIntegerLiteral extends Token {
int val;
public TokenIntegerLiteral (int val) {
this.val = val;
}
public int getInteger() {
return val;
}
public void asS2 (Indenter o) {
o.write((new Integer(val)).toString());
}
public void asPerl (BackendPerl bp, Indenter o) {
asS2(o); // same as S2 (just an integer)
}
public String toString() {
StringBuffer ret = new StringBuffer("[TokenIntegerLiteral] = ");
ret.append((new Integer(val)).toString());
return ret.toString();
}
public static Token scan (Tokenizer t)
{
StringBuffer tbuf = new StringBuffer(15);
while (t.peekChar() >= '0' && t.peekChar() <= '9') {
tbuf.append(t.getChar());
}
return new TokenIntegerLiteral((new Integer(tbuf.toString())).intValue());
}
public void asHTML (Output o)
{
o.write("<span class=\"n\">" +
val + "</span>");
}
}

View File

@@ -0,0 +1,70 @@
package danga.s2;
class TokenKeyword extends TokenIdent {
public final static TokenKeyword CLASS = new TokenKeyword("class");
public final static TokenKeyword ELSE = new TokenKeyword("else");
public final static TokenKeyword ELSEIF = new TokenKeyword("elseif");
public final static TokenKeyword FUNCTION = new TokenKeyword("function");
public final static TokenKeyword IF = new TokenKeyword("if");
public final static TokenKeyword BUILTIN = new TokenKeyword("builtin");
public final static TokenKeyword PROPERTY = new TokenKeyword("property");
public final static TokenKeyword SET = new TokenKeyword("set");
public final static TokenKeyword STATIC = new TokenKeyword("static");
public final static TokenKeyword VAR = new TokenKeyword("var");
public final static TokenKeyword WHILE = new TokenKeyword("while");
public final static TokenKeyword FOREACH = new TokenKeyword("foreach");
public final static TokenKeyword PRINT = new TokenKeyword("print");
public final static TokenKeyword PRINTLN = new TokenKeyword("println");
public final static TokenKeyword NOT = new TokenKeyword("not");
public final static TokenKeyword AND = new TokenKeyword("and");
public final static TokenKeyword OR = new TokenKeyword("or");
public final static TokenKeyword XOR = new TokenKeyword("xor");
public final static TokenKeyword LAYERINFO = new TokenKeyword("layerinfo");
public final static TokenKeyword EXTENDS = new TokenKeyword("extends");
public final static TokenKeyword RETURN = new TokenKeyword("return");
public final static TokenKeyword DELETE = new TokenKeyword("delete");
public final static TokenKeyword DEFINED = new TokenKeyword("defined");
public final static TokenKeyword NEW = new TokenKeyword("new");
public final static TokenKeyword TRUE = new TokenKeyword("true");
public final static TokenKeyword FALSE = new TokenKeyword("false");
public final static TokenKeyword REVERSE = new TokenKeyword("reverse");
public final static TokenKeyword SIZE = new TokenKeyword("size");
public final static TokenKeyword ISNULL = new TokenKeyword("isnull");
public final static TokenKeyword NULL = new TokenKeyword("null");
public final static TokenKeyword READONLY = new TokenKeyword("readonly");
static TokenKeyword[] keywords = {
CLASS, ELSE, ELSEIF, FUNCTION, IF, BUILTIN, PROPERTY, SET,
STATIC, VAR, WHILE, FOREACH, PRINT, PRINTLN, NEW, TRUE, FALSE,
NOT, AND, OR, XOR, LAYERINFO, EXTENDS, RETURN, DELETE, DEFINED,
REVERSE, SIZE, ISNULL, NULL, READONLY
};
public TokenKeyword () {
this.ident = null;
}
public TokenKeyword (String ident) {
this.ident = ident;
}
public static TokenKeyword tokenFromString (String ident) {
// FIXME: this is O(n). do binary search or use hash
for (int i=0; i<keywords.length; i++) {
if (keywords[i].ident.equals(ident))
return keywords[i];
}
return null;
}
public String toString() {
return ("[TokenKeyword] = " + ident);
}
public void asHTML (Output o)
{
o.write("<span class=\"k\">" + ident + "</span>");
}
}

View File

@@ -0,0 +1,247 @@
package danga.s2;
class TokenPunct extends Token {
String punct;
public TokenPunct (String punct) {
this.punct = punct;
}
public String getPunct() {
return punct;
}
public String toString() {
return ("[TokenPunct] = " + punct);
}
public static final TokenPunct LTE = new TokenPunct("<=");
public static final TokenPunct LT = new TokenPunct("<");
public static final TokenPunct GTE = new TokenPunct(">=");
public static final TokenPunct GT = new TokenPunct(">");
public static final TokenPunct EQ = new TokenPunct("==");
public static final TokenPunct NE = new TokenPunct("!=");
public static final TokenPunct ASSIGN = new TokenPunct("=");
public static final TokenPunct INC = new TokenPunct("++");
public static final TokenPunct PLUS = new TokenPunct("+");
public static final TokenPunct DEC = new TokenPunct("--");
public static final TokenPunct MINUS = new TokenPunct("-");
public static final TokenPunct DEREF = new TokenPunct("->");
public static final TokenPunct SCOLON = new TokenPunct(";");
public static final TokenPunct COLON = new TokenPunct(":");
public static final TokenPunct DCOLON = new TokenPunct("::");
public static final TokenPunct LOGAND = new TokenPunct("&&");
public static final TokenPunct BITAND = new TokenPunct("&");
public static final TokenPunct LOGOR = new TokenPunct("||");
public static final TokenPunct BITOR = new TokenPunct("|");
public static final TokenPunct MULT = new TokenPunct("*");
public static final TokenPunct DIV = new TokenPunct("/");
public static final TokenPunct MOD = new TokenPunct("%");
public static final TokenPunct NOT = new TokenPunct("!");
public static final TokenPunct DOT = new TokenPunct(".");
public static final TokenPunct DOTDOT = new TokenPunct("..");
public static final TokenPunct LBRACE = new TokenPunct("{");
public static final TokenPunct RBRACE = new TokenPunct("}");
public static final TokenPunct LBRACK = new TokenPunct("[");
public static final TokenPunct RBRACK = new TokenPunct("]");
public static final TokenPunct LPAREN = new TokenPunct("(");
public static final TokenPunct RPAREN = new TokenPunct(")");
public static final TokenPunct COMMA = new TokenPunct(",");
public static final TokenPunct QMARK = new TokenPunct("?");
public static final TokenPunct DOLLAR = new TokenPunct("$");
public static final TokenPunct HASSOC = new TokenPunct("=>");
public static Token scan (Tokenizer t)
{
if (t.peekChar() == '$') {
t.getChar();
return TokenPunct.DOLLAR;
}
if (t.peekChar() == '<') {
t.getChar();
if (t.peekChar() == '=') {
t.getChar();
return TokenPunct.LTE;
} else {
return TokenPunct.LT;
}
}
if (t.peekChar() == '>') {
t.getChar();
if (t.peekChar() == '=') {
t.getChar();
return TokenPunct.GTE;
} else {
return TokenPunct.GT;
}
}
if (t.peekChar() == '=') {
t.getChar();
if (t.peekChar() == '=') {
t.getChar();
return TokenPunct.EQ;
} else if (t.peekChar() == '>') {
t.getChar();
return TokenPunct.HASSOC;
} else {
return TokenPunct.ASSIGN;
}
}
if (t.peekChar() == '+') {
t.getChar();
if (t.peekChar() == '+') {
t.getChar();
return TokenPunct.INC;
} else {
return TokenPunct.PLUS;
}
}
if (t.peekChar() == '-') {
t.getChar();
if (t.peekChar() == '-') {
t.getChar();
return TokenPunct.DEC;
} else if (t.peekChar() == '>') {
t.getChar();
return TokenPunct.DEREF;
} else {
return TokenPunct.MINUS;
}
}
if (t.peekChar() == ';') {
t.getChar();
return TokenPunct.SCOLON;
}
if (t.peekChar() == ':') {
t.getChar();
if (t.peekChar() == ':') {
t.getChar();
return TokenPunct.DCOLON;
} else {
return TokenPunct.COLON;
}
}
if (t.peekChar() == '&') {
t.getChar();
if (t.peekChar() == '&') {
t.getChar();
return TokenPunct.LOGAND;
} else {
return TokenPunct.BITAND;
}
}
if (t.peekChar() == '|') {
t.getChar();
if (t.peekChar() == '|') {
t.getChar();
return TokenPunct.LOGOR;
} else {
return TokenPunct.BITOR;
}
}
if (t.peekChar() == '*') {
t.getChar();
return TokenPunct.MULT;
}
if (t.peekChar() == '/') {
t.getChar();
return TokenPunct.DIV;
}
if (t.peekChar() == '%') {
t.getChar();
return TokenPunct.MOD;
}
if (t.peekChar() == '!') {
t.getChar();
if (t.peekChar() == '=') {
t.getChar();
return TokenPunct.NE;
} else {
return TokenPunct.NOT;
}
}
if (t.peekChar() == '{') {
t.getChar();
return TokenPunct.LBRACE;
}
if (t.peekChar() == '}') {
t.getChar();
return TokenPunct.RBRACE;
}
if (t.peekChar() == '[') {
t.getChar();
return TokenPunct.LBRACK;
}
if (t.peekChar() == ']') {
t.getChar();
return TokenPunct.RBRACK;
}
if (t.peekChar() == '(') {
t.getChar();
return TokenPunct.LPAREN;
}
if (t.peekChar() == ')') {
t.getChar();
return TokenPunct.RPAREN;
}
if (t.peekChar() == '.') {
t.getChar();
if (t.peekChar() == '.') {
t.getChar();
return TokenPunct.DOTDOT;
}
return TokenPunct.DOT;
}
if (t.peekChar() == ',') {
t.getChar();
return TokenPunct.COMMA;
}
if (t.peekChar() == '?') {
t.getChar();
return TokenPunct.QMARK;
}
return null;
}
public void asHTML (Output o)
{
if (punct.equals("[") || punct.equals("]") ||
punct.equals("(") || punct.equals(")") ||
punct.equals("{") || punct.equals("}")) {
o.write("<span class=\"b\">" + punct + "</span>");
} else {
o.write("<span class=\"p\">" + punct + "</span>");
}
}
public void asS2 (Output o)
{
o.write(punct);
}
}

View File

@@ -0,0 +1,159 @@
package danga.s2;
class TokenStringLiteral extends Token {
int quotesLeft;
int quotesRight;
String text;
String source;
public int getQuotesLeft () { return quotesLeft; }
public int getQuotesRight () { return quotesRight; }
public void setQuotesLeft (int c) { quotesLeft = c; }
public void setQuotesRight (int c) { quotesRight = c; }
public Object clone () {
return new TokenStringLiteral(text, source, quotesLeft, quotesRight);
}
public TokenStringLiteral (String text) {
this(text, text, 1, 1);
}
public TokenStringLiteral (String text, int quotesLeft, int quotesRight) {
this(text, text, quotesLeft, quotesRight);
}
public TokenStringLiteral (String text, String source, int quotesLeft, int quotesRight) {
this.text = text;
this.source = source;
this.quotesLeft = quotesLeft;
this.quotesRight = quotesRight;
}
public String getString() {
return text;
}
public String toString() {
StringBuffer ret = new StringBuffer("[TokenStringLiteral] = ");
if (quotesLeft == 0) { ret.append("("); }
else if (quotesLeft == 1) { ret.append("<"); }
else if (quotesLeft == 3) { ret.append("<<"); }
ret.append(text);
if (quotesRight == 0) { ret.append(")"); }
else if (quotesRight == 1) { ret.append(">"); }
else if (quotesRight == 3) { ret.append(">>"); }
return ret.toString();
}
public void asHTML (Output o)
{
StringBuffer ret = new StringBuffer();
ret.append(makeQuotes(quotesLeft));
ret.append(source);
ret.append(makeQuotes(quotesRight));
o.write("<span class=\"s\">" + BackendHTML.quoteHTML(ret.toString()) + "</span>");
}
public static Token scan (Tokenizer t) throws Exception
{
boolean inTriple = false;
boolean continued = false;
FilePos pos = t.getPos();
if (t.inString == 0) {
// see if this is a triple quoted string,
// like python. if so, don't need to escape quotes
t.getRealChar(); // 1
if (t.peekChar() == '"') {
t.getChar(); // 2
if (t.peekChar() == '"') {
t.getRealChar(); // 3
inTriple = true;
} else {
t.inString = 0;
return new TokenStringLiteral("", 1, 1);
}
}
} else if (t.inString == 3) {
continued = true;
inTriple = true;
} else if (t.inString == 1) {
continued = true;
}
StringBuffer tbuf = new StringBuffer(inTriple ? 500 : 80); // escaped
StringBuffer sbuf = new StringBuffer(inTriple ? 500 : 80); // source
while (true) {
char peekchar = t.peekChar();
if (peekchar == (char)-1) {
throw new Exception("Run-away string. Check for unbalanced quote types on string literals.");
} else if (peekchar == '"') {
if (! inTriple) {
t.getChar();
t.inString = 0;
return new TokenStringLiteral(tbuf.toString(), sbuf.toString(), continued ? 0 : 1, 1);
} else {
t.getChar(); // 1
if (t.peekChar() == '"') {
t.getChar(); // 2
if (t.peekChar() == '"') {
t.getChar(); // 3
t.inString = 0;
return new TokenStringLiteral(tbuf.toString(), sbuf.toString(), continued ? 0 : 3, 3);
} else {
tbuf.append('"'); tbuf.append('"');
sbuf.append('"'); sbuf.append('"');
}
} else {
tbuf.append('"');
sbuf.append('"');
}
}
} else {
if (t.peekChar() == '$') {
t.inString = inTriple ? 3 : 1;
return new TokenStringLiteral(tbuf.toString(), sbuf.toString(),
continued ? 0 : (inTriple ? 3 : 1),
0);
}
if (t.peekChar() == '\\') {
sbuf.append(t.getRealChar()); // skip the backslash. next thing will be literal.
sbuf.append(t.peekChar());
if (t.peekChar() == 'n') {
t.forceNextChar('\n');
}
tbuf.append(t.getRealChar());
} else {
char c = t.getRealChar();
tbuf.append(c);
sbuf.append(c);
}
}
}
}
public void asS2 (Indenter o)
{
o.write(makeQuotes(quotesLeft));
o.write(Backend.quoteStringInner(text));
o.write(makeQuotes(quotesRight));
}
public void asPerl (BackendPerl bp, Indenter o)
{
o.write(bp.quoteString(text));
}
private String makeQuotes(int i) {
if (i == 0) return "";
if (i == 1) return "\"";
if (i == 3) return "\"\"\"";
return "XXX";
}
}

View File

@@ -0,0 +1,47 @@
package danga.s2;
import java.util.StringTokenizer;
class TokenWhitespace extends Token {
String ws;
public boolean isNecessary() { return false; }
public TokenWhitespace (String ws) {
this.ws = ws;
}
public String getWhitespace() {
return ws;
}
public String toString() {
return ("[TokenWhitespace]");
}
public static Token scan (Tokenizer t)
{
StringBuffer tbuf = new StringBuffer(200);
while ((t.peekChar() == ' ' || t.peekChar() == '\t' ||
t.peekChar() == '\n' || t.peekChar() == '\r')) {
tbuf.append(t.getChar());
}
return new TokenWhitespace(tbuf.toString());
}
public void asHTML (Output o) {
StringTokenizer st = new StringTokenizer(ws, "\n", true);
while (st.hasMoreTokens()) {
String s = st.nextToken();
if (s.equals("\n") && BackendHTML.addBreaks) {
o.write("<br />\n");
} else {
o.write(s);
}
}
}
}

View File

@@ -0,0 +1,161 @@
package danga.s2;
import java.io.InputStream;
public class Tokenizer
{
Token peekedToken;
Tokenizer masterTokenizer;
Scanner sc;
// these are public for the Tokens to access when scanning: (no 'friend' classes in Java. :/)
public int inString; // can be 0, 1, or 3. (string types of none, normal, triple quote)
boolean varToker = false;
public Tokenizer (InputStream is)
{
if (is != null) sc = new Scanner(is);
inString = 0;
peekedToken = null;
masterTokenizer = this;
}
public Tokenizer getVarTokenizer() throws Exception
{
Tokenizer vt = new Tokenizer(null); // null unimportant--- setting scanner later
vt.inString = 0; // note: we probably _are_ in a string.
vt.varToker = true;
// clone everything else:
vt.masterTokenizer = masterTokenizer;
vt.sc = sc;
// but don't clone this...
if (peekedToken != null) {
throw new Exception("Request to instantiate sub-tokenizer failed because "+
"master tokenizer has a peeked token loaded already.");
}
return vt;
}
public void release () throws Exception
{
if (peekedToken != null) {
throw new Exception("Sub-tokenizer had a peeked token when releasing.");
}
}
public Token peek () throws Exception
{
if (peekedToken == null) {
peekedToken = getToken();
}
return peekedToken;
}
public Token getToken () throws Exception
{
if (peekedToken != null) {
Token t = peekedToken;
peekedToken = null;
return t;
}
char nextChar = sc.peek();
if (nextChar == (char) -1) {
if (sc.getBogusFlag())
throw new Exception("Malformed source encoding. (should be UTF-8)");
return null;
}
FilePos pos = getPos();
Token nxtoken = makeToken();
nxtoken.pos = pos;
return nxtoken;
}
public FilePos getPos () {
return new FilePos(sc.line, sc.col);
}
private Token makeToken () throws Exception
{
char nextChar = sc.peek();
// this has to be before the string literal parsing.
if (nextChar == '$') {
return TokenPunct.scan(this);
}
if (inString != 0) {
return TokenStringLiteral.scan(this);
}
if (nextChar == ' ' || nextChar == '\t' ||
nextChar == '\n' || nextChar == '\r') {
return TokenWhitespace.scan(this);
}
if (TokenIdent.canStart(this)) {
return TokenIdent.scan(this);
}
if (nextChar >= '0' && nextChar <= '9') {
return TokenIntegerLiteral.scan(this);
}
if (nextChar == '<' || nextChar == '>' ||
nextChar == '=' || nextChar == '!' ||
nextChar == ';' || nextChar == ':' ||
nextChar == '+' || nextChar == '-' ||
nextChar == '*' || nextChar == '/' ||
nextChar == '&' || nextChar == '|' ||
nextChar == '{' || nextChar == '}' ||
nextChar == '[' || nextChar == ']' ||
nextChar == '(' || nextChar == ')' ||
nextChar == '.' || nextChar == ',' ||
nextChar == '?' || nextChar == '%' ||
false) {
return TokenPunct.scan(this);
}
if (nextChar == '#') {
return TokenComment.scan(this);
}
if (nextChar == '"') {
return TokenStringLiteral.scan(this);
}
throw new Exception("Parse error! Unknown character '" + nextChar +
"' (" + (new Integer((int)nextChar)).toString() + ") encountered at " +
locationString());
}
public String locationString () {
return sc.locationString();
}
public char peekChar () {
return sc.peek();
}
public char getChar () {
return sc.getChar();
}
public char getRealChar () throws Exception {
return sc.getRealChar();
}
public void forceNextChar (char ch) {
sc.forceNextChar(ch);
}
}

152
wcmtools/s2/danga/s2/Type.java Executable file
View File

@@ -0,0 +1,152 @@
package danga.s2;
import java.util.LinkedList;
import java.util.ListIterator;
public class Type implements Cloneable
{
public final static Type VOID = new Type("void");
public final static Type STRING = new Type("string");
public final static Type INT = new Type("int");
public final static Type BOOL = new Type("bool");
protected LinkedList typeMods; // stores "[]" and "{}" information
protected String baseType;
protected boolean readOnly = false;
public Object clone () {
Type nt = new Type (baseType);
// GCJBUG: this doesn't work in gcj
// nt.typeMods = (LinkedList) typeMods.clone();
// GCJBUG: this works:
nt.typeMods = new LinkedList();
for (ListIterator li = typeMods.listIterator(); li.hasNext(); ) {
nt.typeMods.add(li.next());
}
nt.readOnly = readOnly;
return nt;
}
public Type (String baseType) {
this.baseType = baseType;
typeMods = new LinkedList();
}
// return true if the type is an INT or BOOL (something
// that can be interpretted in a boolean context)
public boolean isBoolable ()
{
return (equals(BOOL) || equals(INT));
}
// return a vector of all the sub-types this could be:
// if this is a A[][], would return A[][], B[][], C[][]
public ListIterator subTypesIter (Checker ck)
{
LinkedList l = new LinkedList();
NodeClass nc = ck.getClass(baseType);
if (nc == null) {
// no sub-classes. just return our type.
l.add(this);
return l.listIterator();
}
ListIterator di = nc.getDerClasses().listIterator();
while (di.hasNext()) {
// add a copy of this type to the list, but with
// the derivative class type.
DerItem der = (DerItem) di.next();
String c = der.nc.getName();
Type newt = (Type) clone();
newt.baseType = c;
l.add(newt);
}
return l.listIterator();
}
public boolean equals (Object o) {
if (! (o instanceof Type)) return false;
Type ot = (Type) o;
if (ot.baseType.equals(baseType) &&
ot.typeMods.equals(typeMods))
return true;
return false;
}
public static boolean sameMods (Type a, Type b)
{
if (a.typeMods.equals(b.typeMods))
return true;
return false;
}
public void makeArrayOf () {
typeMods.addLast("[]");
}
public void makeHashOf () {
typeMods.addLast("{}");
}
public void removeMod () {
try {
typeMods.removeLast();
} catch (Exception e) { }
}
// return true if it's not a hashof or arrayof
public boolean isSimple () {
return (typeMods.size() == 0);
}
private boolean isThing (String s) {
try {
Object o = typeMods.removeLast();
typeMods.addLast(o);
return o.equals(s);
} catch (Exception e) {
return false;
}
}
public boolean isHashOf () { return isThing("{}"); }
public boolean isArrayOf () { return isThing("[]"); }
public String baseType ()
{
return baseType;
}
public String toString ()
{
StringBuffer sb = new StringBuffer(baseType);
ListIterator li = typeMods.listIterator(0);
while (li.hasNext()) {
String s = (String) li.next();
sb.append(s);
}
return sb.toString();
}
public static boolean isPrimitive (String bt)
{
Type t = new Type(bt);
return (t.equals(STRING) ||
t.equals(INT) ||
t.equals(BOOL));
}
public boolean isPrimitive () {
return (this.equals(STRING) ||
this.equals(INT) ||
this.equals(BOOL));
}
public boolean isReadOnly () { return readOnly; }
public void setReadOnly (boolean b) { readOnly = b; }
}

View File

@@ -0,0 +1,151 @@
package danga.s2;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.File;
import java.util.Hashtable;
class s2compile
{
public static String topLayerName;
public static String topLayerType;
public static void main (String[] args) throws Exception
{
if (args.length < 3 || args.length % 2 != 1) { usage(); return; }
Hashtable hargs = new Hashtable();
int i = 0;
while (args[i].startsWith("-") && i != args.length-1) {
int offset = args[i].startsWith("--") ? 2 : 1;
String key = args[i].substring(offset, args[i].length());
String val = args[i+1];
i += 2;
hargs.put(key, val);
}
if (i != args.length - 1) { usage(); return; }
String filename = args[i];
String format = (String) hargs.get("output");
if (format == null) {
System.err.println("No output format specified");
return;
}
// Tokens output format requires no validation.
if (format.equals("tokens")) {
Tokenizer toker = new Tokenizer(getInputStream(filename));
try {
Token tok;
while ((tok = toker.getToken()) != null) {
System.out.println(tok.toString());
}
} catch (Exception e) {
System.err.println(e.toString());
return;
}
System.out.println("end.");
return;
}
String layertype = null;
Checker ck = null;
Layer layerMain;
// TODO: respect cmdline option to pre-load serialized checker to
// avoid having to make one by reparsing source of core[+layout]
if (format.equals("html") || format.equals("s2")) {
ck = null;
} else {
ck = new Checker();
layertype = (String) hargs.get("layertype");
if (layertype == null) {
System.err.println("Unspecified layertype.");
return;
} else if (layertype.equals("core")) {
// nothing.
}
else if (layertype.equals("i18nc") || layertype.equals("layout")) {
makeLayer((String) hargs.get("core"), "core", ck);
}
else if (layertype.equals("theme") || layertype.equals("i18n") ||
layertype.equals("user")) {
makeLayer((String) hargs.get("core"), "core", ck);
makeLayer((String) hargs.get("layout"), "layout", ck);
}
else {
System.err.println("Invalid layertype.");
return;
}
}
layerMain = makeLayer(filename, layertype, ck);
topLayerName = layerMain.getLayerInfo("name");
topLayerType = layerMain.type;
Output o = new OutputConsole();
Backend be = null;
if (format.equals("html"))
be = new BackendHTML(layerMain);
if (format.equals("s2"))
be = new BackendS2(layerMain);
if (format.equals("perl")) {
int layerid = 0;
try {
layerid = Integer.parseInt((String) hargs.get("layerid"));
} catch (Exception e) {
System.err.println("Unspecified -layerid <n>");
return;
}
be = new BackendPerl(layerMain, layerid);
}
if (be == null) {
System.err.println("No backend found for '" + format + "'");
return;
}
be.output(o);
o.flush();
return;
}
private static int usage () {
System.err.println("Usage: ");
System.err.println(" s2compile [opts]* <file>\n");
System.err.println("Options:");
System.err.println(" --output <format> One of: perl, html, s2, tokens");
System.err.println(" --layerid <int> For perl output format only");
System.err.println(" --layertype <type> One of: core, i18nc, layout, theme, i18n, user");
System.err.println(" --core <filename> Core S2 file, if layertype after core");
System.err.println(" --layout <filename> Layout S2 file, if compiling layer after layout");
System.err.println("\nAny file args can be '-' to read from STDIN, ending with ^D");
return 1;
}
public static InputStream getInputStream (String filename) throws Exception {
if (filename.equals("-"))
return System.in;
return new FileInputStream(new File(filename));
}
public static Layer makeLayer (String filename, String type, Checker ck) throws Exception
{
if (filename == null) {
throw new Exception("Undefined filename for "+type+" layer.");
}
Tokenizer toker = new Tokenizer(getInputStream(filename));
Layer s2l = new Layer(toker, type);
// now check the layer, since it must have parsed fine (otherwise
// the Layer constructor would have thrown an exception
if (ck != null)
ck.checkLayer(s2l);
return s2l;
}
}