Wherein the grammar is revealed to be even more valuable.
Recall that the expander supplies the initial bindings for the program.
Recall that a brag parser produces a parse tree where each node starts with the name of a production rule, followed by the matched elements.
The expander cooperates with the parser by exporting bindings that correspond to those production rules.
In this way, the parse tree becomes part of our program. For instance, this expression:
(with-syntax ([PT parse-tree])
#'(module winner taco-victory
(display (apply string PT)))
Might receive a parse-tree like this:
#'(taco-program (taco-leaf (not-a-taco) (taco)))
And return this composite syntax object (where the pattern variable PT in the syntax template is replaced with the value of parse-tree):
#'(module winner taco-victory
(display (apply string (taco-program (taco-leaf (not-a-taco) (taco))))))
The name of the production rule becomes the identifier of a new function (or macro) provided by the expander.
For each leaf of the parse tree, the matched elements of the production rule become the input to that identifier.
Thus, the grammar not only generates our parser, but gives us a specification for the expander.
The expander needs to export bindings for every identifier that appears in the parse tree. These bindings can be macros or ordinary functions.
Make a language called taco-victory that takes the same input as tacopocalypse and converts it back into a Racket program. Use whatever combination of lexer, parser, and expander makes life easiest.
Example:
#lang taco-victory
##$%#$%#$#$#$$##$%#$%#$#$#$$##$%#$#$#$%#$$##$#$#$%#$%%$#%#$%#$#$%%$##$#$%%#$%%$##$#$%%#$%%$#%%%%#$%%$##$#$#$#$#$%#$$#%%%#$%%%$#%%%%#$%%$##$%#$#$%%%$##$#$%%#$%%$##$#$%#$#$%%$##$%#$#$#$%#$$##$%#$%#$#$#$$##$#$#$%#$%#$$#%%#$%#$%#$$##$#$#$#$#$%#$$#%#$#$#$%%#$$##$#$#$#$#$%#$$##$#$#$%#$%#$$##$%#$%#$%#$$##$#$#$#$#$%#$$##$%#$#$%%#$$##$#$#$#$#$%#$$##$#$#$%#$%#$$#%#$%%#$%#$$##$#$#$#$#$%#$$##$#$#$%%%%$#%#$#$%#$%#$$#%#$#$%#$%#$$#%#$#$%#$%#$$
Result:
"hello world"
(+ 1 (* 2 (- x)))
Hint: You need four rules in your grammar, and four corresponding functions in your expander. Code to get you started is below. Note one wrinkle: this time, parse calls tokenize repeatedly to get each token (this approach was mentioned in quantum-taco). So this tokenize should return one token at a time, rather than all of them.
#lang br/quicklang
(require brag/support "grammar.rkt")
(provide taco-program taco-leaf
taco not-a-taco
show
#%module-begin)
(module+ reader
(provide read-syntax))
(define (tokenize-1 ip)
;; return one token (not all tokens)
;; ···
)
(define (taco-program . pieces)
;; ···
)
(define (taco-leaf . pieces)
;; ···
)
(define (taco)
;; ···
)
(define (not-a-taco)
;; ···
)
(define (show pt)
(display (apply string pt)))
(define (read-syntax src ip)
(define token-thunk (λ () (tokenize-1 ip)))
(define parse-tree (parse src token-thunk))
(strip-bindings
(with-syntax ([PT parse-tree])
#'(module winner taco-victory
(show PT)))))
#lang brag
taco-program : ;; ···
taco-leaf : ;; ···
taco : ;; ···
not-a-taco : ;; ···
Hint: (apply string PT) will only succeed if the value represented by PT evaluates to a list of characters.
Hint: work on "main.rkt" in isolation until this REPL input:
(apply-port-proc tokenize-1 "\n#$#$#$%#$%%\n")
Produces:
'("#$" "#$" "#$" "%" "#$" "%" "%")
Hint: work on "grammar.rkt" in isolation until this REPL input:
(parse-to-datum '("#$" "#$" "#$" "%" "#$" "%" "%"))
Produces:
'(taco-program (taco-leaf (not-a-taco) (not-a-taco) (not-a-taco) (taco) (not-a-taco) (taco) (taco)))
Hint: work on "main.rkt" in isolation until this REPL input:
(taco-program (taco-leaf (not-a-taco) (not-a-taco) (not-a-taco) (taco) (not-a-taco) (taco) (taco)))
Produces:
'(#\h)
It’s all downhill from here. For some definition of “all”.