(define (parse-factor lst)
(if (not (and (pair? lst) (string? (car lst))))
(error "invalid syntax")
(let ([first (car lst)]
[rest (cdr lst)])
(if (and (pair? rest) (eq? (car rest) '+))
(let* ([result (parse-factor (cdr rest))]
[text (plus first (car result))])
(cons text (cdr result)))
lst))))
I next turned our attention to writing parse-term. Recall that we are working
with this grammar:
; grammar is:
; <term> ::= <factor> | <factor> * <term>
; <factor> ::= <string> | <string> + <factor>
In the case of factors, they were composed of simple strings and factors, so we
generated one result from a call on parse-factor. Here we're going to want two
results because have a combination of factors and terms. It's clear that a
term always begins with a factor, so we began by calling that function to
compute a result. And as with parse-factor, it can be helpful to give names to
the different parts of this result.
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
...
I asked whether we need error checking code like we had for parse-factor and
somebody said not for those cases because we start by calling parse-factor. So
we can assume we have a nonempty list that starts with a string. It can be
helpful to copy the approach we used in writing parse-factor because many of
these parsing functions will have a similar structure. In that case, we tested
to see if the second value in the result was a plus. Here we check for a times
operator:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
...
For the "then" part of the if/else, we know that we should be using our second
rule from the grammar because we have a factor followed by * which should then
be followed by a term. So the next step is to call parse-term on the result
list, skipping the string and *:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
(let* ([result2 (parse-term (cdr rest))]
...
We should now have a variable called first that has the first string we should
use (possibly obtained by collapsing several tokens into one by the call on
parse-factor) and a list called result2 that begins with the second string we
should use (the result of collapsing the tokens that followed). So we are in a
position to apply the * operator to figure out what string to include in our
overall result:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
(let* ([result2 (parse-term (cdr rest))]
[text (times first (car result2))])
As with parse-factor, we had to change this to a let* because we need the value
of result2 to compute the value of text. At this point we're almost done. We
have computed the string that should go at the front of our overall result, so
as with parse-factor, we can just put the pieces together. We also need to
include an "else" part for the case where we have no * operator to process:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
(let* ([result2 (parse-term (cdr rest))]
[text (times first (car result2))])
(cons text (cdr result2)))
result1)))
I introduced a bug by originally having "lst" instead of "result1" as the final
value to return. That didn't work because we want to return a list that has
the result of collapsing the initial factor even if there are no times
operators to process.This version behaved as expected:
> (parse-term test1)
'("(a, (b, (c, d)))")
> (parse-term test2)
'("[a-[b-[c-d]]]")
> (parse-term test3)
'("[(a, b)-[c-[(d, e)-[f-(g, h)]]]]")
I saved this version of the grammar in a file called
grammar1.rkt.In this version of the grammar, both operators evaluate right-to-left. I said that I wanted to change the * operator to evaluate left-to-right. We were able to make this change by switching the order of the nonterminals in the second rule:
; <term> ::= <factor> | <term> * <factor>
This produces a different kind of parse tree than we had before. For
the expression "a * b * c" we get:
<term>
/ | \
/ | \
/ | \
<term> | <factor>
/ | \ | |
/ | \ | |
<term> | <factor> | <string>
| | | | |
| | | | |
<factor> | | | |
| | | | |
<string> | <string> | |
| | | | |
a * b * c
Notice how "a * b" is grouped more closely together than the other *
and c.In this form, we can't simply translate this into function calls because parse-term would potentially call parse-term without reducing the input, which would lead to infinite recursion. To make this work, we have to somehow make the list shorter. In general we are given something like this:
<factor> * <factor> * <factor> ... * <factor>
Our challenge is to somehow reduce this to a shorter sequence of factors so
that we can use recursion to collapse the rest. If the operator evaluates
left-to-right, then we want to collapse the initial pair of factors:
<factor> * <factor> * <factor> ... * <factor>
| |
+-collapse-+
The version we have currently calls parse-factor followed by
a call on parse-term. The trick is to change the second call to a call on
parse-factor:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
(let* ([result2 (parse-factor (cdr rest))]
[text (times first (car result2))])
(cons text (cdr result2)))
result1)))
This isn't quite right because it processes a single * operator, replacing it
with a new string. But there might be more * operators to process. So we need
to include a call on parse-term with the overall result we have computed:
(define (parse-term lst)
(let* ([result1 (parse-factor lst)]
[first (car result1)]
[rest (cdr result1)])
(if (and (pair? rest) (eq? (car rest) '*))
(let* ([result2 (parse-factor (cdr rest))]
[text (times first (car result2))])
(parse-term (cons text (cdr result2))))
result1)))
This version behaved as expected:
> (parse-term test1)
'("(a, (b, (c, d)))")
> (parse-term test2)
'("[[[a-b]-c]-d]")
> (parse-term test3)
'("[[[[(a, b)-c]-(d, e)]-f]-(g, h)]")
I said that I would store this version of the parsing functions in a
file called grammar2.rkt.I spent the remainder of the lecture describing the grammar we will be using for the homework. It comes from Python and the assignment involves implementing a mini version of the Python interpreter known as Idle. That information is all in the assignment writeup, so I won't repeat it here.