package edu.neu.ccs.demeterf.demfgen; import edu.neu.ccs.demeterf.lib.*; import edu.neu.ccs.demeterf.demfgen.classes.*; import edu.neu.ccs.demeterf.demfgen.dgp.DGPFunc; import edu.neu.ccs.demeterf.demfgen.pcdgp.PCDGPFunc; import edu.neu.ccs.demeterf.demfgen.traversals.Travs; import edu.neu.ccs.demeterf.dispatch.*; import edu.neu.ccs.demeterf.*; import edu.neu.ccs.demeterf.demfgen.ClassGen.ArityPair; import edu.neu.ccs.demeterf.demfgen.ClassHier.InhrtPair; import java.io.*; import edu.neu.ccs.demeterf.util.Util; import edu.neu.ccs.demeterf.util.CLI; /** DemFGen Main program/class */ public class DemFGenMain{ /** Simple (shorter) Print to Std-Error */ static void p(String s){ if(!Diff.optionSet(Diff.quiet))System.err.print(s); } /** Create a String of spaces with the given width */ static String gap(int len){ if(len <= 0)return ""; return " "+gap(len-1); } /** Quit/Exit due to some error (print before calling) */ public static void abort(){ System.exit(1); } /** Print the DemFGen Header for the given language (e.g., "Java" or "C#") */ public static void header(String lang){ p("\n \u25e4------------------------------\u25e5"+ "\n | DemeterF "+lang+gap(12-lang.length())+"|"+ "\n | "+Diff.buildDate+" |"+ "\n \u25e3------------------------------\u25e2\n\n"); } static void timing(String what, long start){ //System.out.println(pad(what+": ",30)+(System.currentTimeMillis()-start)); } /** Main entry point for DemFGen. Parses the CD and BEH files, manages arguments, * and generates classes. All the real stuff is implemented below, and in separate * classes in the "demeterf.demfgen.*" classes. */ public static void main(String args[], String lang) throws Exception { long time = System.currentTimeMillis(); List[] all = CLI.splitArgs(args); List opts = all[CLI.OPTS]; Diff.storeOptions(opts); String nonOpt[] = all[CLI.ARGS].toArray(new String[all[CLI.ARGS].length()]); DemFGenMain.header(lang); if(Diff.optionSet(Diff.help))usage(true,""); if(nonOpt.length != 3)usage(false,"Not enough arguments"); List unknown = CLI.invalidOptions(opts, Diff.validOptions); if(!unknown.isEmpty())usage(false,"Unknown Option(s): "+unknown.toString(", ", "")); String cdFile = nonOpt[0], behFile = nonOpt[1], outDir = nonOpt[2]; // Trim annoying final separator if(outDir.lastIndexOf(File.separatorChar) == outDir.length()-1) outDir = outDir.substring(0,outDir.length()-1); final String dir = outDir; try{ long starter = System.currentTimeMillis(); final List CDs = resolveCDFile(cdFile); final List BEHs = resolveBEHFile(behFile); final PackageDef basepkg = CDs.top().getPkg(); timing("ParseIn",starter); boolean parser = !Diff.optionSet(Diff.noparse); Util.addBuiltIns(DoGen.class/*,Ident.class*/); if(!basepkg.hasPkg()) p("\n !! Warning: No Root Package Specified\n"+ " Parser/DGP classes may not be accessible\n\n"); List arities = CDs.fold(new List.Fold>(){ public List fold(CDFile f, List r){ return TypeCheck.defsToList(f.getTypes()).append(r); }}, ClassGen.Primitives); TypeCheck tc = new TypeCheck(); Travs.TheFactory.makeTypeCheckTrav(tc).traverse(CDs, new TypeCheck.Env(arities)); if(Diff.optionSet(Diff.showcd)) p("\n ** Full Class Dictionary:\n "+Print.PrintM(CDs).replace("\n","\n ")); if(Diff.optionSet(Diff.graph)){ p("\n ** Generating DOT Graph...\n"); GraphGen.genGraph(CDs, dir); System.exit(0); }else{ List inhrt = DemFGenMain.subtypes(CDs); String dgpMeths = ""; int dgp = OptStart.index(opts, Diff.dgp+":"); if(dgp >= 0){ p(" ** Loading DGP Functions...\n ["); Type.addPath("edu.neu.ccs.demeterf.demfgen.dgp."); List funcs = loadDGP(CLI.separateOption("dgp",opts), new DGPLoad(),"DGP"); Type.removePath("edu.neu.ccs.demeterf.demfgen.dgp."); p("]\n ** Generating DGP Functions...\n ["); dgpMeths = funcs.foldl(new List.Fold(){ public String fold(DGPFunc f, String s){ return (f.method(basepkg.name())+s); } },""); starter = System.currentTimeMillis(); doDGPGen(funcs,CDs,BEHs,dir,inhrt); timing("DGPFuncs",starter); Type.removePath("edu.neu.ccs.demeterf.demfgen.dgp."); p("]\n"); } if(!Diff.optionSet(Diff.nogen)){ List funcs = List.create(); if(OptStart.index(opts, Diff.pcdgp+":") >= 0){ p(" ** Loading PCDGP Functions...\n ["); Type.addPath("edu.neu.ccs.demeterf.demfgen.pcdgp."); funcs = loadDGP(CLI.separateOption("pcdgp",opts),new PCDGPLoad(),"PCDGP"); Type.removePath("edu.neu.ccs.demeterf.demfgen.pcdgp."); p("]\n"); } p(" ** Generating Classes...\n"); starter = System.currentTimeMillis(); ClassGen.genClasses(CDs, BEHs, dir, dgpMeths, opts, inhrt, funcs, basepkg); timing("ClassGen",starter); } if(parser){ p(" ** Generating Parser...\n"); ParseGen.genParser(CDs, dir, inhrt, basepkg); } if(!Diff.optionSet(Diff.nogen)){ int lib = OptStart.index(opts, Diff.lib+":"); List pkgdirs = CDs.foldr(new List.Fold>(){ public List fold(CDFile cd, List r){ return (cd.getTypes().anyGen())? r.push(DemFGenMain.pkgdir(dir, cd.getPkg(), true)):r; } }, List.create()); Make.make(dir, basepkg, parser && !Diff.optionSet(Diff.noparsecc), opts, lib, pkgdirs); } p(" ** Finished ["+seconds(System.currentTimeMillis()-time)+" sec.]\n"); timing("Total",time); System.exit(0); } }catch(RE re){ error(re,"Runtime"); }catch(RTParseException pe){ error(pe,"Parse"); }catch(java.io.FileNotFoundException fe){ error(fe,"File"); }catch(RTFileNotFound fe){ error(fe,"File"); }catch(TE te){ error(te,"Type"); } System.exit(1); } /** Pad the given string out to the give size (on the right) */ public static String pad(String s, int i){ if(s.length() >= i)return s; return pad(s+" ",i); } /** Return the number of Seconds from the given Milliseconds */ public static double seconds(long mil){ return ((int)(mil/10.0))/100.0; } /** Does the given argument start with the given option prefix (usually "--")? */ static class OptStart extends List.Pred{ String start; OptStart(String s){ start = s; } public boolean huh(String s){ return s.startsWith(start); } static int index(List l, String start){ return l.index(new OptStart(start)); } } /** Print an Error Message (default) */ static void error(Throwable t, String type){ p("\n\n !! "+type+" Error:\n"+t.getMessage()+"\n\n"); //t.printStackTrace(); } /**************************************************** *** Some of these methods are quite useful... *** ****************************************************/ /** Recursively resolve any included CD files, starting with the given file name. */ public static List resolveCDFile(String file) throws FileNotFoundException{ try{ return resolveCDFile(new FileInputStream(file), file); }catch(ParseException pe){ throw new RTParseException(" ** CD File: \""+file+"\"\n"+pe.getMessage()); }catch(TokenMgrError te){ throw new RTParseException(" ** CD File: \""+file+"\"\n"+te.getMessage()); } } /** Recursively resolve any included CD files from the given InputStream, starting * with the given file name. The name is kept around for ParseError feedback * to the user. */ public static List resolveCDFile(InputStream in, String name) throws ParseException{ CDFile main = CDFile.parse(in); List CDs = IncludeCDs.resolveCDs(main.getIncl(),name); ImportList imports = IncludeCDs.allImports(CDs.push(main)); return CDs.push(main.updateImports(imports)); } /** Recursively resolve any included BEH files, starting with the given file name. */ public static List resolveBEHFile(String file) throws FileNotFoundException{ try{ return resolveBEHFile(new FileInputStream(file), file); }catch(ParseException pe){ throw new RTParseException(" ** BEH File: \""+file+"\"\n"+pe.getMessage()); }catch(TokenMgrError te){ throw new RTParseException(" ** BEH File: \""+file+"\"\n"+te.getMessage()); } } /** Recursively resolve any included BEH files from the given InputStream, starting * with the given file name. The name is kept around for ParseError feedback * to the user. */ public static List resolveBEHFile(InputStream in, String name) throws ParseException{ BehFile beh = BehFile.parse(in); return beh.getBehs().toList().append(IncludeCDs.resolveBEHs(beh.getIncl(),name)); } /** Print the full usage information for DemFGen */ static void usage(boolean help,String bad){ p("\n"+ (help?"":" !! "+bad+"\n\n")+ " ** Usage: DemFGen [Options] \n"+ (!help?" ** Use --help for a full description\n": " The order/placement of options doesn\'t matter, but the relative\n"+ " order of the manditory ones must be as shown.\n\n"+ " CD-File contains the CD to be generated,\n"+ " BEH-File contains the behavior to be used with classes,\n"+ " Output-Dir is the directory to place generated files\n\n"+ " Options can be: \n"+ //12345678901234567890123456789012345678901234567890123456789012345678901234567890 " --help : Print this usage information.\n"+ " --build : "+Diff.d.makeInfo+"\n"+ " --lib:FILE : Implies --build, Create a library named FILE from the\n"+ " compiled/generated code (JAR for Java, DLL for C#)\n"+ " --windows : Use Windows based command launches and CSC to compile\n"+ " C# instead of GMCS. System PATH and CLASSPATH\n"+ " variables need to be set correctly. Works with both\n"+ " '--build' and '--lib'\n"+ " --noshell : Use Java to run JavaCC, instead of trying to run commands\n"+ " through BASH or Windows CMD\n"+ " --nogen : Don\'t generate the classes... just DGP and Parser\n"+ " --noparse : Don\'t generate the parser files or methods\n"+ " --noequals : Don\'t generate the equals() methods\n"+ " --mutable : Generate 'mutable' fields... not final/readonly\n"+ " --graph : Just print a Dot file to StdOut\n"+ " --show : Print the final deep parsed CD(s)\n"+ " --dgp:C1:...:Cn : A Colon seperated List of Data-generic function\n"+ " classes to be run/generated. Each class must be a\n"+ " subclass of ...demeterf.demfgen.dgp.DGPFunc\n"+ " and accessible in the current classpath.\n"+ " * Predefined classes are:\n"+ " - Objects to Strings, Each one can be used as a \"toString\" method\n"+ " by using the _ToString version\n"+ " Print, PrintToString : in complete CD syntax\n"+ " PrintHeap, PrintHeapToString : Uses very little Java stack\n"+ " Same method Names as above\n"+ " PrintIter : Use iteration when possible\n"+ " Display, DisplayToString : nested field/type information\n"+ " ToStr, ToString : simple nested constructors\n"+ " ToXML : simple XML output\n"+ " - Useful methods\n"+ " HashCode : add deep hashcode methods\n"+ " - Static Code Generation:\n"+ " StaticTrav : Create Static Traversal Class/Methods\n"+ " use --nocontrol to eliminate Traversal control overhead\n"+ " StaticTravCtx : Static Traversal with a context/argument\n"+ " StaticTP : Create a static TP Class for the CD\n"+ " StaticTU : Create a static TU Class for the CD\n"+ " * See the demfgen.dgp package source for more details.\n\n"+ " --pcdgp:C1:...:Cn : A Colon seperated List of Per-Class Data-generic function\n"+ " classes to be run on each TypeDef. Classes must be a\n"+ " subclass of ...demeterf.demfgen.pcdgp.PCDGPFunc\n"+ " and accessible in the current classpath.\n"+ " * Predefined classes are:\n"+ " - Getters : Add getters for all fields. For 'field' the getter is\n"+ " created as 'getField()'.\n"+ " - Updaters : Adds an Update method for each field that returns an\n"+ " updated instance; all other fields are the same, but\n"+ " the selected field is replaced.\n"+ " - Creator : Addes a static Creator method that mimics the constructor.\n"+ " - Setters : Add setters for all fields. ** The '--mutable' option\n"+ " must be used in order for this to work. Otherwise, try\n"+ " using the Updaters instead.\n"+ " * See the demfgen.pcdgp package source for more details.\n")+ "\n"); System.exit(1); } /** Load/Instantiation Exception */ static class LoadException extends RuntimeException{ public LoadException(String s){ super(s); } public LoadException(Throwable e){ super(e); } } /** Load a specific instance of the given class, and check/cast if needed */ static abstract class Loader extends List.Map{ public X map(String n){ try{Class c = Type.classForName(n); return instance(c); }catch(Exception e){throw new RuntimeException(e); } } abstract X instance(Class c) throws Exception; } static class DGPLoad extends Loader{ DGPFunc instance(Class c) throws Exception{ if(DGPFunc.class.isAssignableFrom(c)){ p(""+c.getSimpleName()+", "); return (DGPFunc)(c.newInstance()); } throw new LoadException("Unknown DGPFunc"); } } static class PCDGPLoad extends Loader{ PCDGPFunc instance(Class c) throws Exception{ if(PCDGPFunc.class.isAssignableFrom(c)){ p(""+c.getSimpleName()+", "); java.lang.reflect.Constructor constr = c.getConstructor(new Class[]{List.class}); return (PCDGPFunc)(constr.newInstance(new Object[]{List.create()})); } throw new LoadException("Unknown PCDGPFunc"); } } /** Lookup the given DGP function classes */ static List loadDGP(String names[], final Loader load, final String typ){ return List.create(names).reverse().map(new List.Map(){ public X map(String name){ try{ return load.map(name); }catch(TypeSearchException e) { p("\n !! DGP Error: Couldn\'t Find function class: \'"+name+"\'\n\n"); } catch(LoadException e) { p("\n !! DGP Error: Class \'"+name+"\' isn\'t a subclass of "+typ+"Func\n\n"); }catch(Exception e) { p("\n !! DGP Error: Couldn\'t create function: \'"+name+"\'\n\n"); } abort(); return null; } }); } /** When traversing just return a list of TypeDefs */ static class JustTypes extends TU>{ public List combine(){ return List.create(); } public List fold(List a, List b){ return a.append(b); } List combine(TypeDef t){ return combine().push(t); } } /** Get just the TypeDefs from a List of DemFGenMains */ public static List justTypes(List CD){ List types = new JustTypes().traverse(CD); return types; } /** Flatten the classes so that fields are represented in all concrete * classes (i.e., push common fields down to subtypes) */ public static List flatten(List CD, final List inhrt){ List types = new Traversal(new ID(){ TypeDef combine(TypeDef td){ return td; } ClassDef combine(ClassDef cd, DoGen g, ident n, TypeDefParams ps, PESubtypeList sts, FieldList fs, Impl i){ return new ClassDef(g,n,ps,sts, fs.append(ClassHier.superFieldsAndSyntax(ps.toList(), inhrt, ""+n) .foldr(new List.Fold(){ public FieldList fold(FieldOrSyntax f, FieldList r){ return r.push(f); } }, new FieldEmpty())), i); } Empty combine(Empty e){ return e; } List combine(List l, TypeDef f, List r){ return r.push(f); } },Control.bypass(ClassDef.class,IntfcDef.class)).traverse(justTypes(CD)); return types; } /** Remove all the syntax froma given CD */ public static List removeSyntax(List types){ return Factory.newTraversal(new TP(){ FieldList combine(FieldList l, Syntax s, FieldList r){ return r; } },Control.builtins(Syntax.class)).traverseList_TypeDef_(types); } /** DGP generation function class */ public static class DGPGen extends FC{ final List types; final List BEHs; final String dir, imports, suff; final PackageDef pkg; public DGPGen(List CD, List Bs, String d, List inhrt){ types = flatten(CD, inhrt); BEHs = Bs; imports = ""+IncludeCDs.allImports(CD); pkg = CD.top().getPkg(); suff = CD.top().getPkg().hasPkg()?Diff.d.classEnd:""; dir = DemFGenMain.pkgdir(d, pkg, true); } public String combine(List e, String f, String r){ return f+r; } public String combine(Empty e){ return ""; } public String combine(DGPFunc f){ long starter = System.currentTimeMillis(); String name = f.fileName(); String extra = behBody(BEHs, name); DGPFunc.Trav trav = f.traversalObj(extra); String ret = trav.traverseOption_List_TypeDef__(Option.some(types)); // Removed to support inlined DGP traversals // DFGTrav trav = Factory.newTraversal(f.functionObj(extra), f.control()); // String ret = trav.traverseOption_List_TypeDef__(Option.some(types)); // Make sure the package dir exists if needed Util.writeFile(name, "."+f.fileSuffix(), ret+suff+"\n", dir, f.header(Preamble.header,pkg,imports)); timing(" DGP-"+name+": ",starter); return (""+name+", "); } } /** Calculate the directory for the package based on command line args */ public static String pkgdir(String dir, PackageDef pkg, boolean create){ if(pkg.hasPkg() && !Diff.optionSet(Diff.flatdirs)){ String d = dir+File.separator+pkg.dir(); // Make sure it exists... if(create)Util.createWritableDir(d); return d; } return dir; } /** Run each of the DGP function classes and allow them to create files */ static void doDGPGen(List funcs, List CDs, List BEHs, String dir, List inhrt){ DGPGen gen = new DGPGen(CDs,BEHs,dir,inhrt); String names = Travs.TheFactory.makeDGPGenTrav(gen).traverse(funcs); p(names); } /** Find BEH attached to a giv en Name */ static class FindBeh extends List.Pred{ String name; FindBeh(String n){ name = n; } public boolean huh(BehDef b){ return name.equals(""+b.getName()); } } /** Find the BEH body for the given class/interface */ public static String behBody(List behs, String c){ List match = behs.filter(new FindBeh(c)); return match.fold(new List.Fold(){ public String fold(BehDef b, String r){ return b.getBody().getText()+r; } },""); } /** Instantiate a type definition with the given environment */ private static TypeDef instantiate(TypeDef td, List env){ return new Traversal(new Subst()).traverse(td,env); } /** Type instantiation Function Class */ public static List instantiate(List fs, List bnds, List uses){ List env = bnds.zip(new List.Zip(){ public StrLTrip.StrPair zip(String b, String u){ return new StrLTrip.StrPair(b,u); } }, uses); return new Traversal(new Subst()).>traverse(fs,env); } /** Instantiate a generic type definition. Basically replace all occurences of the * TypeParameters using the environment implied by the given TypeUse */ public static TypeDef instantiate(TypeUse tu, List aT){ TypeDef def = aT.find(new FindType(""+tu.getName())); String dpar[] = def.typeParams().toArray(), upar[] = tu.getTparams().toArray(); if(dpar.length != upar.length){ throw new RuntimeException("Wrong number of Type parameters for "+ def.name()+def.typeParams().print()+"\n - Given "+upar.length+" in "+tu.print()+"!"+ "\n Use: "+tu.print()+ "\n Def: "+def.print()+"\n"); } List env = makeEnv(dpar,upar,0); return instantiate(def,env); } /** Predicate to search for a type definition by String name */ static class FindType extends List.Pred{ String name; FindType(String n){ name = n; } public boolean huh(TypeDef t){ return name.equals(""+t.name()); } public String toString(){ return "typedef["+name+"]"; } } /** Find a type by Ident name */ static class FIdent extends List.Pred{ String name; FIdent(ident look){ name = ""+look; } public boolean huh(StrLTrip.StrPair t){ return name.equals(t.n);} } /** Substitution class, replaces TypeUses and DefParams using the given * Environment context */ public static class Subst extends StaticTP{ String lookup(ident name, List env){ FIdent find = new FIdent(name); if(env.contains(find)) return env.find(find).b; return name.toString(); } TypeUse combine(TypeUse t, ident name, EmptyUseParams ps, List env){ return TypeUse.makeType(lookup(name,env)); } NameDef combine(NameDef d, ident i, Bound b, List env){ return new NameDef(new ident(ClassGen.unlocal(lookup(i,env))), b); } } /** Create an environment from definition variables and actual uses */ public static List makeEnv(String[] d, String[] u, int i){ if(i == d.length)return List.create(); return makeEnv(d,u,i+1).push(new StrLTrip.StrPair(d[i],u[i])); } /** Return a list of all the suptypes from the given CDFiles... */ public static List subtypes(List CD){ return CD.fold(new List.Fold>(){ public List fold(CDFile f, List r){ return CollectInherit.inheritPairs(f.getTypes()).append(r); } }, List.create()); } }