2 A Simple Model
Goals |
— |
— |
— |
To understand Racket’s approach to language extension and construction, we need to have a model of the front-end of a language implementation.
;; A LAM is one of: Its Interpretation is: |
;; -- VAR -- a variable ref |
;; -- NUM -- a literal constant |
;; -- (lambda (VAR) LAM) -- a 1-argument function |
;; -- (+ LAM LAM) -- an addition expression |
;; -- (LAM LAM) -- a function application |
;; |
;; NUM is a (Racket) number |
;; |
;; VAR is a sequence of keyboard characters (not a Racket number) |
These first five lines represent the core language of LAM, as in, "understood by the compiler, which assigns meaning."
;; -- (let-syntax (VAR META) LAM) -- a macro definition |
;; -- (VAR LAM ...) -- a macro use IF VAR is declared |
;; |
;; META is a language for writing down functions that construct LAMs |
Let’s study lexing, parsing, and macro expansion in this context.
Let’s start with examples in the plain language state as strings:
(define program-as-text "((lambda (x) (+ x 10)) 42)") (define macro-as-text (string-append "((lambda (y)" " (let-syntax (plus10" " (lambda (stx)" " (match stx" " [`(plus10 ,x) `(+ ,x 10)])))" " (plus10 y)))" " 42)")) (define another-example (string-append "((lambda (fun)" " (fun " " (let-syntax (syn (lambda (stx)" " 23))" " (syn (fun 1) 20))))" " (lambda (x) (+ x 1)))"))
(struct Syntax (e {stuff #:auto}) #:transparent #:auto-value 'stuff)
; A SyntaxTree is one of: ; – (Syntax Number) ; – (Syntax Symbol) ; – (Syntax (list SyntaxTree ... SyntaxTree))
; S-expression -> SyntaxTree (define (to-Syntax stx) (cond [(cons? stx) (Syntax (map to-Syntax stx))] [else (Syntax stx)]))
; String -> S-exp ; read string (of characters) and transform into an S-expression (define (lex str) ; also known as READER ... read ... to-Syntax ...)
(define (parse s table) ; also known as EXPANDER (match (Syntax-e s) [(? symbol?) s] [(? number?) s] [`(,(Syntax lambda _) ,(Syntax `(,(Syntax (? symbol? parameter) _)) _) ,body) (Syntax (list 'lambda parameter (parse body table)))] [`(,(Syntax '+ _) ,lop ,rop) (Syntax (list '+ (parse lop table) (parse rop table)))] ... *** ... [`(,fun ,arg) (Syntax (list 'app (parse fun table) (parse arg table)))] [_ (error 'parse "~s is not grammatical" s)]))
[`(,(Syntax 'let-syntax _) ,(Syntax `(,(Syntax (? symbol? name) _) ,rhs) _) ,body) (define transformer (make-transformer (from-Syntax rhs))) (parse body (extend-table name transformer table))] [`(,(Syntax (? (compose (in? table)) m) _) ,more ...) (define transformer (retrieve m table)) (parse (transformer s) table)]
The make-tranformer function turns the rhs META expression into a function on SyntaxTree.
With extend-table, parse remembers the association between name and this function for just the body of let-syntax. The last match clause of parse shows how the macro is used when a particular macro shape is discovered.
And yes, you can test this on the above strings.
Resources
macro.rkt: provides functions to state and run simple macros
table.rkt: provides functions for adding VAR0keyed information to, and retrieving it from, a table