;; The first three lines of this file were inserted by DrScheme. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "reader.ss" "plai" "lang") ; The job of a parser is to match patterns (i.e., the right ; sides of the BNF rules) and convert them into expressions. ; Macro-expressive patterns are those that can simply be ; re-written into other patterns. We've seen some examples, ; including: LET can be rewritten as an APP and a LAMBDA, ; and AND and OR can be rewritten using IF. Both of those ; arose while writing our own interpreters. ; ; We've also seen how Scheme's DEFINE-SYNTAX-RULE lets us ; define our own patterns for Scheme's existing interpreter. ; DEFINE-SYNTAX-RULE limits us to patterns of the form: ; ; (pattern-name ... ) ; ; The underlying parser is able to recognize more sophisticated ; patterns, however. A first example is including extra terminals ; inside a syntax to make it more readable. ; ; DEFINE-SYNTAX, used in conjunction with SYNTAX-RULES, is ; a more general pattern matcher. For example, we can make a ; more natural looking version of LET: (define-syntax with (syntax-rules (=) ; e.g., 1 + 2 [(with = ...) (let ([ ]) (begin ...))])) (with x = 4 (print x) (newline) (set! x (+ x 1)) x) ; We can even parse syntax with multiple forms. For example, there ; are two versions of DEFINE in Scheme, and the parser is able to ; distinguish them by looking at more than the first symbol after ; the open paren. ; ; The example below uses SYNTAX-RULES to rewrite non-nested infix ; expressions for writing "normal" mathematical notation in Scheme: (define-syntax infix (syntax-rules () ; e.g., 1 + 2 [(infix ) ( )] ; e.g., not true, sqrt 3 [(infix ) ( )] ; e.g., 7 [(infix ) ])) ; Examples (infix 1 + 2) (infix 7 > 4) (infix not false) (infix 4) ; Define some familiar math operators: (define == equal?) (define ^ expt) (define (|| a b) (or a b)) (infix 7 == 7) (infix 2 ^ 10) (infix sqrt 144) (infix true || false) ; We might want to make more complicated syntax patterns. For example, ; the above does not let us put parentheses inside the infix operation: ; ; (infix (3 + 4) * 5) ; ; That's because the internal parentheses begin a new expression. If ; we want to stay in infix syntax we have to keep "turning it back on": (infix (infix 3 + 4) * 5) ; It *is* possible in Scheme to make it so that infix looks inside of the ; child expressions. In fact, it is possible to make even something like ; the following evaluate correctly. Note that I've turned semi-colons into ; full colons, since semi-colon is the Scheme comment character: ; ; (Java ; int x = 3: ; println(x + 4): ; while (x < 10) { ; ++x: ; } ; ) ; ; See the SYNTAX documentation for a discussion of all of the capabilities. ; ; Scheme's macro system is Turing Equivalent. That means that we can ; implement any computable function using it. This ensures that ; the macro system is powerful enough for any kind of macro we want, ; but it introduces a new problem: with that much complexity, Scheme ; can't check to make sure that the *parser* halts! A simple ; example of this is just: (define-syntax-rule (infinite-loop) (infinite-loop)) ;(define (loop) (loop)) (infinite-loop) ;(loop) ; Including the next line causes the program to hang in the parser: ; ; (infinite-loop) ; Overloading in languages like Java and C++ is similar to this ; pattern matching notion. If you define two constructors with ; different numbers of arguments in Java, the compiler decides ; which one to invoke *at compile time*, by matching the pattern: ; ; class Cow { ; public Cow(boolean isBrown); ; public Cow(boolean isBrown, float weight); ; } ; ; Cow c = new Cow(true); // Calls the first constructor ; Cow d = new Cow(true, 400); // Calls the second constructor ; ; It might be nice to program in a Scheme-like language where ; PROCEDURES were defined using this pattern-matching idea, not just ; macros. It would be especially nice in a functional language, since ; our semantics of applying rules to reduce expressions to values ; would exactly match the programming style. For example, imagine we ; could write something like: ; ; (define-procedure factorial ; [(factorial 0) 1] ; base case ; [(factorial exp) (* (factorial (- exp 1)) exp)]) ; ; or ; ; (define-procedure - ; [(- a b) (sub a b)] ; 2-argument subtraction ; [(-a) (sub 0 a)]) ; 1-argument subtraction ; ; We can't do that in Scheme (without some major macro hacking!), ; but there are languages based on that pattern matching idea. We'll ; be looking at one soon, called ML.