private void run(String[] programArgs, String expectedOutput, String expectedError) throws IOException, ConfigurationException, InterruptedException { // Should be initialized at this point by call to Main.run() Configuration config = Configuration.getConfiguration(); Path fullExecutable = config.getSystemImport().resolve(executable); List<String> programCommand = new ArrayList<String>(); programCommand.add(fullExecutable.toAbsolutePath().toString()); for (String arg : programArgs) programCommand.add(arg); Process program = new ProcessBuilder(programCommand).start(); // regular output BufferedReader reader = new BufferedReader(new InputStreamReader(program.getInputStream())); BufferedReader errorReader = new BufferedReader(new InputStreamReader(program.getErrorStream())); StringBuilder builder = new StringBuilder(); String line; do { line = reader.readLine(); if (line != null) builder.append(line).append('\n'); } while (line != null); String output = builder.toString(); assertEquals(expectedOutput, output); // error output builder = new StringBuilder(); do { line = errorReader.readLine(); if (line != null) builder.append(line).append('\n'); } while (line != null); String error = builder.toString(); assertEquals(expectedError, error); program.waitFor(); // keeps program from being deleted while running }
/* * Adds a type or a whole package to the current list of imports. */ public boolean addImport(String name) { String separator = File.separator; // Adds some platform independence. if (separator.equals("\\")) // Hack for Windows to deal with backslash escaping. separator = "\\\\"; String path = name.replaceAll(":", separator); /* Which import paths are used depends on whether the requested package * is in the standard library or not. */ List<Path> importPaths; if (path.startsWith("shadow")) { importPaths = new ArrayList<Path>(); importPaths.add(config.getSystemImport()); } else importPaths = config.getImports(); boolean success = false; if (importPaths != null && importPaths.size() > 0) { for (Path importPath : importPaths) { // If an import path is relative, resolving it against the // current source file will make it absolute. // If it's absolute, no change will happen. importPath = currentFile.toPath().getParent().resolve(importPath); /* No @, must be a whole package import. * Add everything in the directory. */ if (!path.contains("@")) { File fullPath = new File(importPath.toFile(), path); if (fullPath.isDirectory()) { File[] matchingShadow = fullPath.listFiles( new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".shadow"); } }); File[] matchingMeta = fullPath.listFiles( new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".meta"); } }); try { for (File file : matchingShadow) importList.add(stripExtension(file.getCanonicalPath())); for (File file : matchingMeta) { String canonicalPath = stripExtension(file.getCanonicalPath()); if (!importList.contains(canonicalPath)) importList.add(canonicalPath); } success = true; } catch (IOException e) { } } } /* Single file import. */ else { File shadowVersion; File metaVersion; String fixedPath; if (path.startsWith("default")) { fixedPath = path.replaceFirst("default@", ""); shadowVersion = new File(currentFile.getParent(), fixedPath + ".shadow"); metaVersion = new File(currentFile.getParent(), fixedPath + ".meta"); } else { fixedPath = path.replaceAll("@", separator); shadowVersion = new File(importPath.toFile(), fixedPath + ".shadow"); metaVersion = new File(importPath.toFile(), fixedPath + ".meta"); } try { if (shadowVersion.exists()) { importList.add(stripExtension(shadowVersion.getCanonicalPath())); success = true; } else if (metaVersion.exists()) { importList.add(stripExtension(metaVersion.getCanonicalPath())); success = true; } } catch (IOException e) { } } if (success) return true; } } else addError(Error.INVALID_IMPORT, "No import paths specified, cannot import " + name); return false; }
/* * Does actual collection of types based on a list of files. */ private void collectTypes( List<File> files, boolean hasMain, Set<String> mustRecompile, Map<String, TreeSet<String>> dependencies) throws ParseException, ShadowException, TypeCheckException, IOException, ConfigurationException { // Create and fill the initial set of files to be checked. TreeSet<String> uncheckedFiles = new TreeSet<String>(); String main = null; // May or may not be null, based on hasMain. if (files.isEmpty()) { throw new ConfigurationException("No files provided for typechecking"); } else if (hasMain) { // Assume the main file is the first and only file. main = stripExtension(files.get(0).getCanonicalPath()); uncheckedFiles.add(main); } else { for (File file : files) { String path = stripExtension(file.getCanonicalPath()); uncheckedFiles.add(path); } } FilenameFilter filter = new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".shadow"); } }; /* Add standard imports. */ File standard = new File(config.getSystemImport().toFile(), "shadow" + File.separator + "standard"); if (!standard.exists()) throw new ConfigurationException( "Invalid path to shadow:standard: " + standard.getCanonicalPath()); TreeSet<String> standardDependencies = new TreeSet<String>(); File[] imports = standard.listFiles(filter); for (File file : imports) { String name = stripExtension(file.getCanonicalPath()); uncheckedFiles.add(name); standardDependencies.add(name); } /* Add io imports (necessary for console programs). */ File io = new File(config.getSystemImport().toFile(), "shadow" + File.separator + "io"); if (!io.exists()) throw new ConfigurationException("Invalid path to shadow:io: " + io.getCanonicalPath()); imports = standard.listFiles(filter); for (File file : imports) { String name = stripExtension(file.getCanonicalPath()); uncheckedFiles.add(name); standardDependencies.add(name); } /* As long as there are unchecked files, remove one and process it. */ while (!uncheckedFiles.isEmpty()) { String canonical = uncheckedFiles.first(); uncheckedFiles.remove(canonical); File canonicalFile = new File(canonical + ".shadow"); // Depending on the circumstances, the compiler may choose to either // compile/recompile source files, or rely on existing binaries/IR. if (canonicalFile.exists()) { File meta = new File(canonical + ".meta"); File llvm = new File(canonical + ".ll"); // If source compilation was not requested and the binaries exist // that are newer than the source, use those binaries. if (!useSourceFiles && !mustRecompile.contains(canonical) && meta.exists() && meta.lastModified() >= canonicalFile.lastModified() && llvm.exists() && llvm.lastModified() >= meta.lastModified()) canonicalFile = meta; else mustRecompile.add(canonical); } else if (!useSourceFiles) canonicalFile = new File(canonical + ".meta"); ShadowParser parser = new ShadowFileParser(canonicalFile); currentFile = canonicalFile; Node node = parser.CompilationUnit(); // Make another collector to walk the current file. TypeCollector collector = new TypeCollector(new Package(), useSourceFiles); // Keeping a current files gives us a file whose directory we can check against. collector.currentFile = currentFile; ASTWalker walker = new ASTWalker(collector); walker.walk(node); if (canonical.equals(main)) mainType = node.getType(); fileTable.put(canonical, node); /* Copy types from other collector into our package tree. */ for (Type type : collector.packageTree) { try { packageTree.addQualifiedPackage(type.getPackage().toString()).addType(type); if (mainType != null && type.getPackage() == packageTree && mainType.getPackage() != packageTree) { // Imported class has default package but the main type doesn't. // The only classes without a package that will be imported will be // in the same directory as the main type. // Implication: classes in the same directory have different packages. String message = "Type " + type + " belongs to the default package, but types defined in the same directory belong to other packages"; addWarning(Error.MISMATCHED_PACKAGE, message); } } catch (PackageException e) { addError(Error.INVALID_PACKAGE, e.getMessage()); } } // Copy errors for the other collector into our error list. if (collector.errorList.size() > 0) errorList.addAll(collector.errorList); // Copy warnings. if (collector.warningList.size() > 0) warningList.addAll(collector.warningList); /* Track the dependencies for this file (if dependencies are being used). * If any of its dependencies need to be recompiled, this file will need * to be recompiled. */ TreeSet<String> dependencySet = null; if (dependencies != null) { dependencySet = new TreeSet<String>(standardDependencies); dependencies.put(canonical, dependencySet); } for (String _import : collector.importList) { if (!fileTable.containsKey(_import)) uncheckedFiles.add(_import); if (dependencySet != null) dependencySet.add(_import); } /* Add files in the directory after imports. */ File[] directoryFiles = canonicalFile.getParentFile().listFiles(filter); for (File file : directoryFiles) { String name = stripExtension(file.getCanonicalPath()); if (!fileTable.containsKey(name)) uncheckedFiles.add(name); if (dependencySet != null) dependencySet.add(name); } /* Copy file table from other collector into our table. */ Map<Type, Node> otherNodeTable = collector.typeTable; for (Type type : otherNodeTable.keySet()) { if (!typeTable.containsKey(type)) { Node otherNode = otherNodeTable.get(type); typeTable.put(type, otherNode); } } } }