diff --git a/.gitignore b/.gitignore index e49c27d..ee3a58b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.meta/ bin/ +dist/ +doc/junit/ src/pp/iloc/sample/ -.meta/ \ No newline at end of file diff --git a/.project b/.project index 00615c7..f7205cc 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - Vertalerbouw + boppi diff --git a/README.md b/README.md new file mode 100644 index 0000000..cacf5c4 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Prerequisites +Boppi requires JDK 1.8 or higher and [Ant](https://ant.apache.org/). Both must be available in the environment. + + +# Installation +Run `ant build` to perform a basic build of the project: it initialises output directories, generates _ANTLR_ files and compiles all java files. To skip _ANTLR_ file generation, run `ant do-init` and `ant do-build` separately. + +Run `ant build-all` to do the above and generate _javadoc_ documentation, run _JUnit_ tests and produce a runnable _JAR_ file. + +To see all possible targets, run `ant -verbose -projecthelp`. + + +# Command line use +After building a _JAR_ file, a command `boppi` becomes available in the `dist/` folder. This command can be used to compile and run files or perform an interactive session. See `boppi --help` and `boppi interactive --help` for more information. + + +# Directory structure +- `bin` contains compiled java code and required text files (after `ant do-build`) +- `dist` contains a runnable JAR, script files and a copy of the libraries required to run the JAR (after `ant do-build-jar`) +- `doc` contains a report of the project and attached example files + - `doc/javadoc` contains _javadoc_ documentation of the project (after `ant do-javadoc`) + - `doc/junit` contains a report of _JUnit_ tests (after `ant do-junit` and `ant do-junit-report`) +- `lib` contains Java libraries required for the project, excluding those in the JDK 1.8 +- `src` + - `src/pp/iloc` contains java code for a slightly modified ILOC virtual machine + - `src/pp/s1184725/boppi` contains java code for the _Boppi_ language +- `util` contains [Pygments](http://pygments.org/) lexers for both ILOC and Boppi and scripts to run the Boppi command line interface (used for the JAR build). diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..3af1109 --- /dev/null +++ b/build.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pp/s1184725/boppi/util/CommandLineInterface.java b/src/pp/s1184725/boppi/util/CommandLineInterface.java new file mode 100644 index 0000000..17f3da4 --- /dev/null +++ b/src/pp/s1184725/boppi/util/CommandLineInterface.java @@ -0,0 +1,191 @@ +package pp.s1184725.boppi.util; + +import java.io.*; +import java.nio.file.*; +import java.util.*; +import java.util.function.Predicate; +import java.util.logging.*; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.apache.commons.cli.*; + +import pp.iloc.Assembler; +import pp.iloc.model.Program; +import pp.iloc.parse.FormatException; +import pp.s1184725.boppi.*; + +/** + * Command line interface for compiling and running Boppi programs. + * + * @author Frank Wibbelink + */ +public class CommandLineInterface { + + private static CommandLine cmd; + private static Level desiredLogLevel; + + /** + * Runs the command line. + * + * @param args + * CLI arguments + */ + public static void main(String[] args) { + Options options = new Options(); + options.addOption("o", "output", true, "output file for ILOC code"); + options.addOption("d", "directory", true, "output directory for ILOC code"); + options.addOption("w", "warnings", true, "set the warning level ('all', 'error' or 'none')"); + options.addOption("r", "run", false, "run an ILOC program"); + options.addOption("h", "help", false, "display this message"); + + if (args.length >= 1 && args[0].equalsIgnoreCase("interactive")) { + InteractivePrompt.main(Arrays.copyOfRange(args, 1, args.length)); + return; + } + + try { + cmd = new DefaultParser().parse(options, args); + } catch (ParseException e) { + System.err.println(e.getLocalizedMessage()); + System.err.println("Use 'boppi -h' for help."); + return; + } + + if (cmd.hasOption("h") || args.length == 0) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("boppi [interactive] [OPTION]... FILE...", + "To start an interactive prompt, have 'interactive' as the first parameter. " + + "This mode has its own help message.", + options, "By default output files will be written to the same path as the " + + "input files but with the .iloc extension."); + return; + } + + if (cmd.hasOption("o") && cmd.getArgList().size() > 1) { + System.err.println("Cannot compile multiple files to one file."); + return; + } + + switch (cmd.getOptionValue("w", "error")) { + case "all": + desiredLogLevel = Level.ALL; + break; + case "none": + desiredLogLevel = Level.OFF; + break; + default: + desiredLogLevel = Level.SEVERE; + } + + if (cmd.hasOption("r")) { + for (String fileName : cmd.getArgList()) + runFile(Paths.get(fileName)); + } else { + for (String fileName : cmd.getArgList()) { + Path infile = Paths.get(fileName); + Path outfile; + + if (cmd.hasOption("o")) + outfile = Paths.get(cmd.getOptionValue("o")); + else if (cmd.hasOption("d")) + outfile = replaceExtension(Paths.get(cmd.getOptionValue("d")).resolve(infile.getFileName()), + "iloc"); + else + outfile = replaceExtension(infile, "iloc"); + + compileFile(infile, outfile); + } + } + + } + + /** + * Loads the given file as ILOC assembly and runs it with the input tied to + * {@link System#in} and the output tied to {@link System#out}. If the VM + * halts with an interrupt code, the whole program is terminated and will + * return it as the exit code. + * + * @param path + * the ILOC file to load + */ + private static void runFile(Path path) { + try { + Logger logger = Logger.getAnonymousLogger(); + logger.setLevel(desiredLogLevel); + + Program prog = Assembler.instance().assemble(path.toFile()); + + ToolChain.execute(prog, logger, System.in, System.out); + + if (ToolChain.machine.getInterrupt() != 0) + System.exit(ToolChain.machine.getInterrupt()); + // System.err.println(String.format("Program exited with status: + // %8x", ToolChain.machine.getInterrupt())); + } catch (FormatException | IOException e) { + e.printStackTrace(); + } + } + + /** + * Tries to compile a Boppi file by parsing it, typechecking it and + * generating code. The process is broken off if at any stage a + * {@link Level#SEVERE} error is encountered. If the code successfully + * compiles, it is written to the given output location. + * + * @param infile + * the Boppi file to read from + * @param outfile + * the ILOC file to write to + */ + private static void compileFile(Path infile, Path outfile) { + Logger logger = Logger.getAnonymousLogger(); + List logList = ToolChain.makeListLog(logger); + logger.setLevel(Level.ALL); + + ParseTree ast = ToolChain.getParser(ToolChain.getLexer(ToolChain.getCharStream(infile), logger), logger) + .program(); + + if (logList.stream().noneMatch(severityAtLeast(Level.SEVERE))) { + Annotations an = ToolChain.getAnnotations(ast, logger); + + if (logList.stream().noneMatch(severityAtLeast(Level.SEVERE))) { + Program iloc = ToolChain.getILOC(ast, logger, an); + + if (logList.stream().noneMatch(severityAtLeast(Level.SEVERE))) { + try { + Files.write(outfile, iloc.prettyPrint().getBytes("utf-8"), StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } catch (IOException e) { + System.err.println("Cannot write to " + outfile); + } + } + } + } + + logList.stream().filter(severityAtLeast(desiredLogLevel)).map(LogRecord::getMessage) + .forEach(System.err::println); + } + + private static Predicate severityAtLeast(Level level) { + return (logRecord) -> logRecord.getLevel().intValue() >= level.intValue(); + } + + private static Path replaceExtension(Path path, String newExt) { + Path folder = path.getParent(); + String filename = path.getFileName().toString(); + + return folder == null ? Paths.get(replaceExtension(filename, newExt)) + : folder.resolve(replaceExtension(filename, newExt)); + } + + private static String replaceExtension(String filename, String newExt) { + int index = filename.lastIndexOf('.'); + if (index >= 0) + filename = filename.substring(0, index); + + return filename + "." + newExt; + } + +} diff --git a/src/pp/s1184725/boppi/util/InteractivePrompt.java b/src/pp/s1184725/boppi/util/InteractivePrompt.java index 946573a..bbb47ba 100644 --- a/src/pp/s1184725/boppi/util/InteractivePrompt.java +++ b/src/pp/s1184725/boppi/util/InteractivePrompt.java @@ -27,20 +27,20 @@ public class InteractivePrompt { public static void main(String[] args) { Options options = new Options(); options.addOption("s", "stateless", false, "run stateless (every command will run in a fresh environment)"); - options.addOption("n", "empty-line", false, "keep reading a single command until the user enters an empty line"); + options.addOption("n", "empty-line", false, + "keep reading a single command until the user enters an empty line"); options.addOption("h", "help", false, "display this message"); options.addOption("p", "print-program", false, "prints the program before executing it"); options.addOption("D", "debug", false, "runs a program in debug mode"); options.addOption("k", "keep", false, "appends every new command to previous input (implies --stateless)"); - + try { cmd = new DefaultParser().parse(options, args); - } - catch (ParseException e) { + } catch (ParseException e) { System.err.println(e.getLocalizedMessage()); return; } - + if (cmd.hasOption("h")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("boppi", "", options, "Exit a program by typing 'exit' on a line."); @@ -48,49 +48,49 @@ public class InteractivePrompt { } sc = new Scanner(System.in); - + if (cmd.hasOption("n")) sc.useDelimiter("\r?\n\r?\n"); else sc.useDelimiter("\r?\n"); - + if (cmd.hasOption("s") || cmd.hasOption("k")) stateless(); else - System.err.println("Stateful interactive mode is not supported."); - + System.err.println("Stateful interactive mode is not yet supported. Please run with --stateless."); + sc.close(); } - + private static void stateless() { Logger logger = Logger.getAnonymousLogger(); String saved = ""; while (sc.hasNext()) { String command = sc.next().trim(); - - if(command.equals("")) + + if (command.equals("")) continue; - + if (command.equals("exit")) break; - - String code = saved+command; - + + String code = saved + command; + if (cmd.hasOption("k")) - saved = code+";\n"; - + saved = code + ";\n"; + Program program = ToolChain.compile(ToolChain.getCharStream(code), logger); - + if (cmd.hasOption("p")) { for (String line : program.prettyPrint().split("\n")) - System.out.println("--- "+line); + System.out.println("--- " + line); } - + ByteArrayOutputStream out = new ByteArrayOutputStream(); ToolChain.execute(program, logger, System.in, out); - + for (String str : out.toString().split("\r\n|\r")) - System.out.println("<<< "+str); + System.out.println("<<< " + str); } } diff --git a/util/boppi.bat b/util/boppi.bat new file mode 100644 index 0000000..6c8c8d5 --- /dev/null +++ b/util/boppi.bat @@ -0,0 +1 @@ +@java -jar boppi.jar %* diff --git a/util/boppi.sh b/util/boppi.sh new file mode 100644 index 0000000..40249af --- /dev/null +++ b/util/boppi.sh @@ -0,0 +1 @@ +java -jar boppi.jar "$@" diff --git a/util/javadoc.xml b/util/javadoc.xml deleted file mode 100644 index d4d7ba3..0000000 --- a/util/javadoc.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - -