package driver; import static trees.MatchSpec.or; import static trees.MatchSpec.rule; import static trees.MatchSpec.str; import static util.ArrayUtils.arr; import static util.ListUtils.list; import static util.ListUtils.removeLast; import java.util.ArrayList; import java.util.Collection; import java.util.List; import files.Package; import files.RelativeSourcePath; import files.Require; import grammar.java._D_Requires; import parser.Match; import parser.Matcher; import trees.MatchSpec; import util.Result; /** * See section of the section titled "The require Statement: Importing Macros * and Specifying Dependencies" in my thesis for a comprehensive description of * the require statement. * * For the grammar of the require statement, see * {@link _D_Requires#requireDeclaration}. */ public class RequiresParser { /***************************************************************************** * {@see {@link SourceParseManager#parseRequires(List, List)}} * * This function will advance the matcher past the file's package, import and * require statements, or throws a runtime error if it encounters a parse * error on that segment. */ void parseRequires(Matcher matcher, Result<String> pkg, List<Require> requires, List<String> imports) { if (matcher.matches(Context.get().grammar().rule("fullPrelude"))) { Match prelude = matcher.match(); pkg.set(prelude.first(rule("qualifiedIdentifier")).string()); parseRequires(prelude, requires, imports); } else { throw new Error( "Parsing error while reading package/import/require statements:\n\n" + matcher.errors().report(matcher.source())); } } /****************************************************************************/ private void parseRequires(Match prelude, List<Require> requires, List<String> imports) { for (Match m : prelude.all(rule("importDeclaration"))) { if (m.has(rule("requireDeclaration"))) { parseRequire(requires, m); } else /* import */ { imports.add(m.string()); } /* In the case of an "import", we could check if the .class file exists * here, but it seems complicated to do in practice (need to write own * code to traverse directories, .jar and .zip files). */ } } /****************************************************************************/ private void parseRequire(List<Require> out, Match match) { final boolean isMacro = match.has(rule("macro")); final boolean isStatic = match.has(rule("_static")); final int nbColons = match.all(rule("colon")).length; List<String> chain1 = new ArrayList<>(); List<String> chain2 = new ArrayList<>(); MatchSpec idOrStar = or(rule("identifier"), str("*")); MatchSpec semiOrColon = or(rule("colon"), rule("semi")); Match[] chain1m = match.allBeforeFirst(idOrStar, semiOrColon); Match[] chain2m = match.allBetween(idOrStar, arr(rule("colon")), arr(rule("semi"))); for (Match m : chain1m) { chain1.add(m.string()); } for (Match m : chain2m) { chain2.add(m.string()); } String fileStem = removeLast(chain1); if (nbColons == 0 && fileStem.equals("*")) // Package-Wide Imports { out.addAll(processPackageRequire(chain1, isMacro)); return; } else if (nbColons == 0 || nbColons == 2) // Syntactic Sugars { chain2.add(0, fileStem); } if (!isMacro && !chain2.isEmpty() && chain2.get(0).equals("*")) { throw new Error("Can't have file-wide imports for regular imports."); } RelativeSourcePath relative = new RelativeSourcePath( new Package(chain1), fileStem + '.' + (isMacro ? SourceFile.MACRO_EXT : "java")); String member = isMacro || isStatic ? removeLast(chain2) : null; out.add(new Require(relative, chain2, member)); } /***************************************************************************** * When a package-wide require is encountered, we use the * {@link SourceRepository} to ensure the {@link SourceFile} for each source * file in the package is loaded (either regular source files, or macro source * files). * * Note we do create {@link SourceFile} for files depended upon via * package-wide requires, but not for other kind of requires. The reason is to * preserve the separation of concerns as much as possible. Since package-wide * require require a file-system scan to know the files depended upon, we might * as well ensure they are loaded while we're at it. */ private Collection<Require> processPackageRequire( List<String> pkgCompo, boolean isMacro) { Package pkg = new Package(pkgCompo); String ext = isMacro ? SourceFile.MACRO_EXT : "java"; Collection<SourceFile> files = Context.get().repo.findAll(pkg, ext); Collection<Require> out = new ArrayList<>(files.size()); for (SourceFile file : files) { List<String> classes = isMacro ? null : list("*"); String member = isMacro ? "*" : null; out.add(new Require(file.path(), classes, member)); } return out; } }