ljr/wcmtools/s2/danga/s2/NodeFunction.java

365 lines
9.4 KiB
Java
Executable File

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();
}
};