diff --git a/README.md b/README.md index 0032b54..2278f80 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ LL(1) Parser Generator that automatically generate a parser class written in Jav satisfies the context-free grammar (CFG) written as a specification file. The parse engine is based on LL(1) parsing technique, from top to bottom. -LL1-Parser-Gen will become a part of the decaf project for the undergraduate course Principles of -Compilation, Tsinghua University. +LL1-Parser-Gen will become a part of the decaf project for the undergraduate course _Principles of +Compilation_, Tsinghua University. ### Build @@ -32,19 +32,15 @@ and you will find the target jar at `target/scala-2.12/LL1-Parser-Gen-assembly-1 ### Usage -After preparing your -[specification file](https://github.com/paulzfm/LL1-Parser-Gen/wiki/1.-Specification-File), -type - ``` java -jar pg.jar [-strict] ``` -to generate the target parser `` from your ``. Open the option `-strict` to -run in [strict mode](https://github.com/paulzfm/LL1-Parser-Gen/wiki/2.-Strict-Mode). +Generate the target parser `` from your ``. The definition of specification +file is [here](https://github.com/paulzfm/LL1-Parser-Gen/wiki/1.-Specification-File). Open the +option `-strict` to run in [strict mode](https://github.com/paulzfm/LL1-Parser-Gen/wiki/2.-Strict-Mode). -We strongly recommend you to read our [wiki](https://github.com/paulzfm/LL1-Parser-Gen/wiki) before -using the tool. +We strongly recommend you to read our [wiki](https://github.com/paulzfm/LL1-Parser-Gen/wiki) first. ### Demo Projects @@ -53,5 +49,6 @@ demo projects [arith](https://github.com/paulzfm/LL1-Parser-Gen/tree/master/demo implements a simple calculator, and [decaf](https://github.com/paulzfm/LL1-Parser-Gen/tree/master/demos/decaf), a Java-like language parser. -To build these projects, under the demo project root directory, type `ant` to build (make sure you have -installed the `ant` tool). The target jar file will be generated at `result/` folder. +To build these projects, first run `sbt assembly` to build LL1-Parser-Gen as a jar. Then under each +demo project root directory, type `ant` to build (make sure you have installed the `ant` tool). +The target jar file will be generated at `result/` folder. diff --git a/demos/arith/build.xml b/demos/arith/build.xml index 8e19741..99c8734 100755 --- a/demos/arith/build.xml +++ b/demos/arith/build.xml @@ -6,7 +6,7 @@ - + diff --git a/demos/arith/src/arith/Lexer.java b/demos/arith/src/arith/Lexer.java index d0cf5d2..1f3898c 100644 --- a/demos/arith/src/arith/Lexer.java +++ b/demos/arith/src/arith/Lexer.java @@ -1,4 +1,4 @@ -/* The following code was generated by JFlex 1.4.1 on 8/5/17 10:19 PM */ +/* The following code was generated by JFlex 1.4.1 on 8/8/17 2:15 PM */ package arith; @@ -9,7 +9,7 @@ /** * This class is a scanner generated by * JFlex 1.4.1 - * on 8/5/17 10:19 PM from the specification file + * on 8/8/17 2:15 PM from the specification file * src/arith/Lexer.l */ public class Lexer extends BaseLexer { diff --git a/demos/arith/src/arith/Parser.java b/demos/arith/src/arith/Parser.java index ce72ed2..4d2bd16 100644 --- a/demos/arith/src/arith/Parser.java +++ b/demos/arith/src/arith/Parser.java @@ -1,8 +1,11 @@ /* This is auto-generated Parser source by LL1-Parser-Gen. - * Generated at: Sat Aug 05 22:19:22 CST 2017 + * Specification file: /Users/paul/Workspace/LL1-Parser-Gen/demos/arith/src/arith/Parser.spec + * Options: unstrict mode + * Generated at: Tue Aug 08 14:15:03 CST 2017 * Please do NOT modify it! * * Project repository: https://github.com/paulzfm/LL1-Parser-Gen + * Version: 1.0 * Author: Zhu Fengmin (Paul) */ @@ -15,16 +18,16 @@ public class Parser extends BaseParser { - public static final int eof = -1; - public static final int eos = 0; - public int lookahead = -1; + private static final int eof = -1; + private static final int eos = 0; + private int lookahead = -1; public SemValue val = new SemValue(); /* tokens */ public static final int NUM = 257; //# line 25 /* search token name */ - String[] tokens = { + private String[] tokens = { "NUM", }; @@ -47,7 +50,7 @@ public SemValue parse() throws Exception { return result; } - public SemValue matchToken(int expected) throws Exception { + private SemValue matchToken(int expected) throws Exception { SemValue self = val; if (lookahead == expected) { lookahead = lex(); diff --git a/demos/decaf/build.xml b/demos/decaf/build.xml index 3228661..aabe588 100755 --- a/demos/decaf/build.xml +++ b/demos/decaf/build.xml @@ -6,7 +6,7 @@ - + diff --git a/demos/decaf/src/decaf/BaseLexer.java b/demos/decaf/src/decaf/BaseLexer.java index f03a82f..70de5dc 100755 --- a/demos/decaf/src/decaf/BaseLexer.java +++ b/demos/decaf/src/decaf/BaseLexer.java @@ -78,10 +78,4 @@ protected int identifier(String name) { return Parser.IDENTIFIER; } - public void diagnose() throws IOException { - while (yylex() != 0) { - System.out.println(parser.lookahead); - } - } - } diff --git a/demos/decaf/src/decaf/Lexer.java b/demos/decaf/src/decaf/Lexer.java index 894e622..4ff428a 100644 --- a/demos/decaf/src/decaf/Lexer.java +++ b/demos/decaf/src/decaf/Lexer.java @@ -1,4 +1,4 @@ -/* The following code was generated by JFlex 1.4.1 on 8/5/17 10:31 PM */ +/* The following code was generated by JFlex 1.4.1 on 8/8/17 2:14 PM */ package decaf; @@ -10,7 +10,7 @@ /** * This class is a scanner generated by * JFlex 1.4.1 - * on 8/5/17 10:31 PM from the specification file + * on 8/8/17 2:14 PM from the specification file * src/decaf/Lexer.l */ public class Lexer extends BaseLexer { diff --git a/demos/decaf/src/decaf/Parser.java b/demos/decaf/src/decaf/Parser.java index a60d5ff..263e295 100644 --- a/demos/decaf/src/decaf/Parser.java +++ b/demos/decaf/src/decaf/Parser.java @@ -1,8 +1,11 @@ /* This is auto-generated Parser source by LL1-Parser-Gen. - * Generated at: Sat Aug 05 22:31:08 CST 2017 + * Specification file: /Users/paul/Workspace/LL1-Parser-Gen/demos/decaf/src/decaf/Parser.spec + * Options: unstrict mode + * Generated at: Tue Aug 08 14:14:52 CST 2017 * Please do NOT modify it! * * Project repository: https://github.com/paulzfm/LL1-Parser-Gen + * Version: 1.0 * Author: Zhu Fengmin (Paul) */ @@ -15,9 +18,9 @@ public class Parser extends BaseParser { - public static final int eof = -1; - public static final int eos = 0; - public int lookahead = -1; + private static final int eof = -1; + private static final int eos = 0; + private int lookahead = -1; public SemValue val = new SemValue(); /* tokens */ @@ -51,7 +54,7 @@ public class Parser extends BaseParser public static final int NOT_EQUAL = 284; //# line 19 /* search token name */ - String[] tokens = { + private String[] tokens = { "VOID", "BOOL", "INT", "STRING", "CLASS", "NULL", "EXTENDS", "THIS", "WHILE", "FOR", "IF", "ELSE", "RETURN", "BREAK", "NEW", @@ -79,7 +82,7 @@ public SemValue parse() throws Exception { return result; } - public SemValue matchToken(int expected) throws Exception { + private SemValue matchToken(int expected) throws Exception { SemValue self = val; if (lookahead == expected) { lookahead = lex(); diff --git a/demos/tools/pg.jar b/demos/tools/pg.jar deleted file mode 100644 index d73f43e..0000000 Binary files a/demos/tools/pg.jar and /dev/null differ diff --git a/src/main/scala/Generator.scala b/src/main/scala/Generator.scala index 5a57b85..b06cd9f 100644 --- a/src/main/scala/Generator.scala +++ b/src/main/scala/Generator.scala @@ -11,10 +11,11 @@ import scala.util.{Failure, Success, Try} * Parsers will be constructed according to the Predictive Set. * * @param spec specification illustrating the CFG. + * @param file specification file name. * @param strictMode option to decide whether assuming the CFG is strictlly LL(1) grammar, * default value closed. */ -class Generator(spec: Spec, strictMode: Boolean = false) { +class Generator(spec: Spec, file: String = "", strictMode: Boolean = false) { /** * Map non-terminals to their corresponding rules. */ @@ -261,7 +262,8 @@ class Generator(spec: Spec, strictMode: Boolean = false) { } NonTerminalParser(spec.sem, nt, cases) } - new JavaCodeFile(spec.pkg, spec.imports, spec.cls, spec.sem, spec.start, spec.tokens, parsers) + new JavaCodeFile(spec.pkg, spec.imports, spec.cls, spec.sem, spec.start, spec.tokens, parsers, + file, if (strictMode) "strict mode" else "unstrict mode") } /** diff --git a/src/main/scala/IndentWriter.scala b/src/main/scala/IndentWriter.scala index 82b75cb..31b72ed 100644 --- a/src/main/scala/IndentWriter.scala +++ b/src/main/scala/IndentWriter.scala @@ -83,10 +83,9 @@ class IndentWriter(val indent: Int = 4, val endOfLine: String = "\n") { /** * Output buffer to file. * - * @param path output file path. + * @param file output file. */ - def outputToFile(path: String): Unit = { - val file = new File(path) + def outputToFile(file: File): Unit = { val bw = new BufferedWriter(new FileWriter(file)) bw.write(buffer) bw.close() diff --git a/src/main/scala/JavaCodeFile.scala b/src/main/scala/JavaCodeFile.scala index 0dee876..ed02969 100644 --- a/src/main/scala/JavaCodeFile.scala +++ b/src/main/scala/JavaCodeFile.scala @@ -30,11 +30,16 @@ import Utils._ * @param start starting symbol of CFG, used as the parser entry. * @param tokens tokens (or terminals), will be obtained from the lexer. * @param parsers all non-terminal parsers. + * @param specFile input specification file name. + * @param options custom options. */ class JavaCodeFile(val pkg: Package, val imports: Imports, val cls: Class, val semValue: SemValue, val start: NonTerminal, val tokens: List[Token], - val parsers: List[NonTerminalParser]) extends Printable { + val parsers: List[NonTerminalParser], + val specFile: String, val options: String) extends Printable { + val version: String = "1.0" + override def printTo(writer: IndentWriter): Unit = { // Print info. printInfoTo(writer) @@ -52,9 +57,9 @@ class JavaCodeFile(val pkg: Package, val imports: Imports, val cls: Class, writer.incIndent() // Print yy variables. - writer.writeLn("public static final int eof = -1;") - writer.writeLn("public static final int eos = 0;") - writer.writeLn("public int lookahead = -1;") + writer.writeLn("private static final int eof = -1;") + writer.writeLn("private static final int eos = 0;") + writer.writeLn("private int lookahead = -1;") writer.writeLn(s"public $semValue val = new $semValue();") writer.writeLn() @@ -67,7 +72,7 @@ class JavaCodeFile(val pkg: Package, val imports: Imports, val cls: Class, } writer.writeLn() writer.writeLn("/* search token name */") - writer.writeLn("String[] tokens = {") + writer.writeLn("private String[] tokens = {") writer.incIndent() identTokens.grouped(5).foreach { grp => @@ -103,10 +108,13 @@ class JavaCodeFile(val pkg: Package, val imports: Imports, val cls: Class, private def printInfoTo(writer: IndentWriter): Unit = { writer.writeLn("/* This is auto-generated Parser source by LL1-Parser-Gen.") + writer.writeLn(s" * Specification file: $specFile") + writer.writeLn(s" * Options: $options") writer.writeLn(s" * Generated at: ${Calendar.getInstance.getTime}") writer.writeLn(" * Please do NOT modify it!") writer.writeLn(" *") writer.writeLn(" * Project repository: https://github.com/paulzfm/LL1-Parser-Gen") + writer.writeLn(s" * Version: $version") writer.writeLn(" * Author: Zhu Fengmin (Paul)") writer.writeLn(" */") } @@ -147,7 +155,7 @@ class JavaCodeFile(val pkg: Package, val imports: Imports, val cls: Class, } private def printFuncMatchTokenTo(writer: IndentWriter): Unit = { - writer.writeLn(s"public $semValue matchToken(int expected) throws Exception {") + writer.writeLn(s"private $semValue matchToken(int expected) throws Exception {") writer.incIndent() writer.writeLn(s"$semValue self = val;") writer.writeLn("if (lookahead == expected) {") diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 4a6705f..46bacb2 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,3 +1,5 @@ +import java.io.File + import scala.util.{Failure, Success, Try} /** @@ -6,8 +8,8 @@ import scala.util.{Failure, Success, Try} object Main { def main(args: Array[String]): Unit = { // Parse command line option and arguments. - var specFile = "" - var outputFile = "" + var specFileName = "" + var parserFileName = "" var strictMode = false if (args.length == 0) { @@ -15,16 +17,16 @@ object Main { System.exit(1) } else if (args(0) == "-strict") { if (args.length >= 3) { - specFile = args(1) - outputFile = args(2) + specFileName = args(1) + parserFileName = args(2) strictMode = true } else { Console.err.println(s"Usage: java -jar pg.jar [-strict] ") System.exit(1) } } else if (args.length == 2) { - specFile = args(0) - outputFile = args(1) + specFileName = args(0) + parserFileName = args(1) } else if (args.length == 3) { Console.err.println(s"Invalid option: ${args(0)}") System.exit(1) @@ -34,13 +36,16 @@ object Main { } // Read specification file. - val source = scala.io.Source.fromFile(specFile) + val inputFile = new File(specFileName) + val source = scala.io.Source.fromFile(inputFile) val lines = try source.mkString finally source.close() + val outputFile = new File(parserFileName) + // Parse and generate. def parseAndGenerate(source: String): Try[Unit] = for { spec <- Parsers.parse(source) - gen = new Generator(spec, strictMode) + gen = new Generator(spec, inputFile.getAbsolutePath, strictMode) code <- gen.generate } yield { val writer = new IndentWriter @@ -50,7 +55,8 @@ object Main { parseAndGenerate(lines) match { case Success(_) => - println(s"""Parser is successfully generated and written to "$outputFile".""") + println("Parser is successfully generated and written to \"" + + outputFile.getAbsolutePath + "\"") case Failure(ex) => Console.err.println(ex.getMessage) System.exit(1)