Project: Program and Statement Parse
Objectives
- Familiarity with writing a recursive-descent parser (for the BL language).
- Familiarity with writing layered secondary methods (
parseforProgramandparseandparseBlockforStatement). - Familiarity with developing and using specification-based test plans.
The Problem
The problem is to complete and carefully test an implementation of a recursive-descent parser for the BL language. This is a fundamental piece of the BL compiler. The parser must follow the context-free grammar (CFG) for the BL language.
As an example of the expected behavior of your parser and the kind of
error messages it should generate, a program is available that parses a
BL program and pretty prints it if it is syntactically valid, otherwise
it prints an error message. To run the program open a terminal window
and type the command /class/software/bin/bw-parser < input.bl where
input.bl is the name of a BL file in the current directory.
Setup
Only one member of the team should follow the steps to set up an Eclipse project for this assignment. The project should then be shared with the rest of the team by using the Subversion version control system as learned in the Version Control With Subversion lab.
To get started, import the project for this assignment, BLParser, from
the BLParser.zip file available at this link. If you
don't remember how to do this, see these Setup
instructions
from an earlier project.
Method
- Start by implementing
Program1Parse1. Carefully review theProgram1Parse1.javaskeleton in thesrcfolder. In particular, pay attention to the public instance methodparse(corresponding to the program non-terminal symbol in the grammar and whose contract is available here) and to the private static methodparseInstruction(corresponding to the instruction non-terminal symbol in the grammar and whose contract is provided in the file itself). Implement the two methods using the grammar as a guide (as discussed in class and in Recursive-Descent Parsing). There are several points to keep in mind:-
The non-terminal symbols program and instruction are not defined recursively in terms of themselves or of each other; therefore, recursion is not called for here.
-
Both program and instruction's rewrite rules refer to the block non-terminal, which is processed by the
Statementsecondary methodparseBlock(see here for the contract). You will need to use this method in implementingProgram1Parse1and, sinceProgram1Parse1extendsProgram1, which in turn usesStatement1, you will callStatement1's version ofparseBlockand not the one you will implement as part of this project. The advantage of this is that you will be able to develop, test, and debug the two parts of the parser independently. -
parsemust replace the distinguished parameterthis. -
Your implementation must check for syntax errors as implied by each method's contract. To perform each check use the
Reporter.assertElseFatalError(boolean, String)utility method, which, if the given boolean is false, generates an error that terminates the program printing the givenStringas a message, and if the boolean is true, simply returns without doing anything else. This means that as soon as the parser finds an error, it quits after reporting the error. It does not attempt to continue parsing to catch other possible errors. You will also find useful static methods (e.g.,isConditionandisIdentifier) in theTokenizerutility class. -
In addition to being responsible for catching any syntax errors in the input BL source program (i.e., determining whether the source program is in the language generated by the context-free grammar for the BL language), your parser is also responsible for checking the following conditions (that cannot be expressed in a context-free grammar) and if they are not satisfied, an error message should be produced and the parser should terminate (again using
Reporter.assertElseFatalError(boolean, String)):- The identifier at the end of the program must be the same as the identifier at the beginning of the program.
- The identifier at the end of each new instruction definition must be the same as the identifier at the beginning of the definition.
- The name of each new user-defined instruction must be unique, i.e., there cannot be two user-defined instructions with the same name.
- The name of each new user-defined instruction must not be the
name of one of the primitive instructions, i.e.,
move,turnleft,turnright,infect, orskip.
The parser is not responsible for making sure that no undefined instruction is called in the program nor that no recursion is present in the program. These conditions can be tested much more easily in the code generation phase, so that's where they will be checked.
-
- In the
testfolder, carefully review the JUnit test fixturesProgramTestandProgram1Parse1Test. They provide the set-up to systematically test theProgramparsemethod you implemented.ProgramTestprovides example test cases for two distinct situations: (1) testing that the parser is correctly parsing valid input and (2) testing that the parser catches errors in invalid input. The second example shows a new feature of JUnit and that is the ability to write test cases that succeed only if the method under test actually generates a specific kind of error (in this case, something calledRuntimeException). So if the method under test fails to generate the appropriate error, the test case fails and you will know that your parser is not catching an error that it should. You should create as many test inputs and corresponding test cases to build your confidence that your parser can handle both situations. An additional way to test your implementation is to use themainmethod included inProgram1Parse1.javabut this is not a replacement for a systematically designed JUnit test fixture. - Next you need to implement
Statement1Parse1. Carefully review theStatement1Parse1.javaskeleton in thesrcfolder. In particular, pay attention to the public instance methodsparseBlockandparse(corresponding to the block and statement non-terminal symbols in the grammar and whose contracts are available here) and to the private static methodsparseIf,parseWhile, andparseCall(corresponding to the if, while, and call non-terminal symbols in the grammar and whose contracts are provided in the file itself). Implement the five methods using the grammar as a guide. There are several points to keep in mind:parseBlockparses a (maximally long) sequence of statements from the input and constructs a BLOCKStatement.parse, instead, parses a single statement from the input and constructs an IF or IF_ELSE or WHILE or CALLStatement.- This part of the project does involve (indirect) recursion because block is defined in terms of statement, which is defined (through if and while's rules) in terms of block.
- Both
parseBlockandparsemust replace the distinguished parameterthis. - Again your implementation must check for syntax errors as
implied by each method's contract using
Reporter.assertElseFatalError(boolean, String). The parser should quit as soon as the first error is found.
- Testing the
Statementpart of the parser is done the same way as theProgrampart. In thetestfolder, carefully review the JUnit test fixturesStatementTestandStatement1Parse1Test. They provide the set-up to systematically test theStatementparsemethod you implemented.StatementTestprovides example test cases for two distinct situations: (1) testing that the parser is correctly parsing valid input and (2) testing that the parser catches errors in invalid input. In this case, you have two methods to test (parseandparseBlock). You should create as many test inputs and corresponding test cases to build your confidence that your parser can handle both situations. An additional way to test your implementation is to use themainmethod included inStatement1Parse1.javabut this is not a replacement for a systematically designed JUnit test fixture. - When you and your teammate(s) are done with the project, decide who
is going to submit your solution. That team member should select the
Eclipse project
BLParser(not just some of the files, but the whole project) containing the complete group submission, create a zip archive of it, and submit the zip archive to the Carmen dropbox for this project, as described in Submitting a Project. Note that you will only be allowed one submission per group, that is, your group can submit as many times as you want, but only the last submission will be on Carmen and will be graded. Under no circumstance will teammates be allowed to submit separate solutions. Make sure that you and your partner agree on what should be submitted.
Additional Activities
Here are some possible additional activities related to this project. Any extra work is strictly optional, for your own benefit, and will not directly affect your grade.
- Test your
ProgramandStatementparsers together with your implementations ofProgramandStatementkernels. Here is one way to do it. It might be useful to draw some component diagrams showing the various dependencies (implements, extends, or just uses) so that you understand what is going on.- From the previous project, copy your
Program2andStatement2components into the current project. - In the current project, make copies of your
Program1Parse1andStatement1Parse1components and name themProgram2Parse1andStatement2Parse1, respectively. - Edit the
Program2Parse1component so that it extendsProgram2(instead ofProgram1). - Edit the
Statement2Parse1component so that it extendsStatement2(instead ofStatement1). - Edit the
Program2component so that it usesStatement2Parse1(instead ofStatement1). - Test the
parsemethod inProgram2Parse1. It will use your implementations of both parsers and both kernels.
- From the previous project, copy your