365 lines
9.4 KiB
Java
Executable File
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();
|
|
}
|
|
|
|
};
|