| Deadline | Friday, November 17, 11:59pm |
| Name on Marmoset | Q10 |
| To Submit |
treeprint.cc OR treeprint.rkt
|
The goal of this question is to construct and print the parse tree corresponding to a CFG derivation. The upcoming projects make heavy use of trees, so a function for printing out a clean and easily readable representation of a tree will be helpful for debugging. This question will also give you some practice working with trees in code.
The input consists of a context-free grammar followed by a derivation, encoded as CFG and DERIVATION components.
There are two output formats allowed for this question. Marmoset will accept both and there is no difference in terms of marks or tests.
The Unicode Format uses Unicode characters for cleaner-looking output. If the software you are using doesn't display these Unicode characters properly, you can instead use the ASCII Format, which only uses ASCII characters. In either case, the output is a representation of the parse tree for the derivation, as illustrated below.
Input & Output
Sample Input
Output (Unicode Format)
Output (ASCII Format)
.CFG
expr expr MINUS term
expr term
term factor
factor ID
factor LPAREN expr RPAREN
.DERIVATION
expr expr MINUS term
expr term
term factor
factor ID
term factor
factor LPAREN expr RPAREN
expr expr MINUS term
expr term
term factor
factor ID
term factor
factor ID
expr
├─expr
│ ╰─term
│ ╰─factor
│ ╰─ID
├─MINUS
╰─term
╰─factor
├─LPAREN
├─expr
│ ├─expr
│ │ ╰─term
│ │ ╰─factor
│ │ ╰─ID
│ ├─MINUS
│ ╰─term
│ ╰─factor
│ ╰─ID
╰─RPAREN
expr
|-expr
| '-term
| '-factor
| '-ID
|-MINUS
'-term
'-factor
|-LPAREN
|-expr
| |-expr
| | '-term
| | '-factor
| | '-ID
| |-MINUS
| '-term
| '-factor
| '-ID
'-RPAREN
A reference implementation called
cs241.treeprint is available to help you generate more examples of output and check the correctness of your output.
cs241.treeprint < input.cfg will produce the expected output for input.cfg in Unicode Format.
cs241.treeprint --ascii < input.cfg
will produce the expected output for input.cfg in ASCII Format.
We suggest solving the problem with two recursive functions: One to read the input and build the tree, and one to print the tree. It is also possible to solve the problem using a single recursive function that directly prints the tree as it reads input, but using two separate functions like this will give you practice with both building a tree data structure and working with a tree data structure. You will also be able to use the printing function with any tree you create in the course for debugging!
The tree-building algorithm requires you to know which symbols are terminals or nonterminals.
This is the only information you need from the CFG.
Thus, you should read the CFG and construct a set containing all the nonterminals.
You can then check whether a symbol is a terminal or a nonterminal by checking whether it is in this set.
Read one line to skip the
You will need to create your own data structure to store the tree. An example in C++ might look like:
(Allowing the Node constructor to read input from any input stream, and allowing the print function to write to any output
stream, will make it more flexible if you want to use it for debugging purposes later.)
In Racket, you could just create a
Make sure that you have consumed the entire CFG component from standard input, as well as the
To build the tree, use the following recursive algorithm:
To print the tree, use the following recursive algorithm. This algorithm takes a parameter called the "indentation prefix", which is printed before printing each child node of the tree. The initial call to the algorithm should pass an empty string as the indentation prefix.
The algorithm refers to the following two-character strings. Note that the Bar string is different between Unicode and ASCII, even though it looks similar, so make sure to copy the appropriate string directly into your code.Setup: Reading the CFG
.CFG header of the CFG component.
Then, read the contents of the CFG component line by line. Each line is a single CFG rule.
.DERIVATION, you've reached the end of the CFG component and
you can move on to building the tree.Setup: Defining a Tree Data Structure
struct Node {
// data stored at the node
std::string data;
// vector of pointers to child subtrees; could also use smart pointers
std::vector<Node*> children;
// constructor for a leaf node
Node(std::string data) : data(data) {}
Node(std::istream &in = std::cin) {
// constructor that builds a tree as it reads input
}
// explicit destructor; could also use smart pointers
~Node() {
for( auto &c : children ) { delete c; }
}
void print(std::string prefix, std::ostream &out = std::cout) {
// prints a representation of the tree, with each line indented by the given prefix
}
};
tree
struct with fields for the data and a list of children, and write a function that reads
standard input and builds a tree struct, and another function that prints a tree struct.
(struct node (data children) #:transparent)
(define (build-tree [in (current-input-port)]) ...)
(define (print-tree tree prefix [out (current-output-port)]) ...)
Building the Tree
.DERIVATION header line.
That is, the next line you read from standard input should be the first rule in the DERIVATION component.
Be careful about nodes where the right-hand side of the rule is
.EMPTY,
which means there are no symbols on the right-hand side.
These should be leaf nodes (no children) rather than having one child called called .EMPTY.
Printing the Tree
Name
Unicode Format
ASCII Format
Joint
"├─"
"|-"
Bar
"│ "
"| "
Corner
"╰─"
"'-"
Spacer
" "
" "