; meta-lambda.scm - A simple parser generator ; Copyright (C) 2005 Jonathan Kraut ; This library is free software; you can redistribute it and/or ; modify it under the terms of the GNU Lesser General Public ; License as published by the Free Software Foundation; either ; version 2.1 of the License, or (at your option) any later version. ; This library is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ; Lesser General Public License for more details. ; You should have received a copy of the GNU Lesser General Public ; License along with this library; if not, write to the Free Software ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ; Contact: ; Jonathan Kraut ; 4130 43 ST #C2 ; Sunnyside, NY 11104 ; jak76@columbia.edu ; see file COPYING in the top of Sassy's distribution directory ; module meta-lambda ; export-syntax meta-lambda case-meta-lambda memoize ; Meta-lambda ; Another Henry Baker-inspired hack. see: ; http://home.pipeline.com/~hbaker1/Prag-Parse.html ; See after the code for documentation (define-syntax meta-expand (syntax-rules (or and lambda begin quote unquote unquote-splicing __ ? + * ?* else) ((_ p i r (quote a)) (and (not (null? i)) (pair? i) (equal? 'a (car i)) (begin (set! i (cdr i)) #t))) ((_ p i r (unquote a)) (and (not (null? i)) (pair? i) (equal? a (car i)) (begin (set! i (cdr i)) #t))) ((_ p i r (unquote-splicing a)) (begin (set! i (list i)) (meta-expand p i r a))) ((_ p i r (or a ...)) (let ((ti i) (tp p)) (or (or (meta-expand p i r a) (begin (set! i ti) (set-cdr! tp '()) (set! p tp) #f)) ...))) ((_ p i r (and a ...)) (and (meta-expand p i r a) ...)) ((_ p i r (lambda a b ...)) (and (null? i) (apply (lambda a b ...) (cdr r)))) ((_ p i r (begin a b ...)) (and (null? i) (begin a b ...))) ((_ p i r (else a)) (let ((tmp (a i))) (set! i '()) tmp)) ((_ p i r (+ a)) (let* ((nr (list #t)) (np nr)) (and (meta-expand np i nr a) (do () ((not (meta-expand np i nr a)) (set! nr (list (cdr nr))) (set-cdr! p nr) (set! p nr) #t))))) ((_ p i r (* a)) (let* ((nr (list #t)) (np nr)) (do () ((not (meta-expand np i nr a)) (set! nr (list (cdr nr))) (set-cdr! p nr) (set! p nr) #t)))) ((_ p i r (?* a)) (or (meta-expand p i r a) #t)) ((_ p i r ()) (null? i)) ((_ p i r (a)) (and (not (null? i)) (pair? i) (cond (((meta-lambda a) (car i)) => (lambda (res) (if (not (eq? #t res)) (begin (set! res (list res)) (set-cdr! p res) (set! p res))) (set! i (cdr i)) #t)) (else #f)))) ((_ p i r __) (if (or (pair? i) (null? i)) (begin (set-cdr! p i) (set! p i) (set! i '()) '__tail) #f)) ((_ p i r ?) (and (not (null? i)) (pair? i) (let ((t (list (car i)))) (set-cdr! p t) (set! p t) (set! i (cdr i)) #t))) ((_ p i r x) (let-syntax ((test (syntax-rules () ((test x w l) w) ((test y w l) l)))) (test __fubar__ (and (not (null? i)) (pair? i) (cond ((x (car i)) => (lambda (res) (let ((tmp (if (eq? res #t) (list (car i)) (list res)))) (set-cdr! p tmp) (set! p tmp) (set! i (cdr i)) #t))) (else #f))) (and (not (null? i)) (pair? i) (equal? x (car i)) (begin (set! i (cdr i)) #t))))))) (define-syntax meta-lambda (syntax-rules () ((meta-lambda grammar) (lambda (i) (let* ((r (list #t)) (p r)) (cond ((meta-expand p i r grammar) => (lambda (res) (if (null? i) (if (eq? res #t) (cond ((null? (cdr r)) #t) ((null? (cddr r)) (cadr r)) (else (cdr r))) (if (eq? res '__tail) (cdr r) res)) #f))) (else #f))))))) ; var-arity meta-lambda (define-syntax meta-lambda-dot (syntax-rules () ((_ x y ...) (lambda args (let ((tmp (meta-lambda x y ...))) (tmp args)))))) ; Something useful to wrap meta-lambda in to hurry things along. ; Of course only use this when not using side-effects. (define-syntax memoize (syntax-rules () ((_ proc) (let ((the-proc proc)) (let ((last-in '%#$%#%#$%) (last-out #f)) (lambda (arg2) (if (eq? arg2 last-in) last-out (begin (set! last-in arg2) (set! last-out (the-proc arg2)) last-out)))))))) ; |===========| ; |Meta-lambda| ; |===========| ; Meta-lambda is a macro for building parsers and pattern matchers ; over lists or single items. You can also specify "actions" to be ; performed when a list has been successfully parsed, so it can also ; function as a very rudimentary compiler-generator or ; attribute-grammar-generator (using synthesized attributes). ; It's really for constructing simple embedded langauges, and it has its ; limitations if your're not willing to factor out tougher grammars by ; hand. But I've found it useful. ; Here's a simple example so you can see where this is going: ; |=====| ; |Usage| ; |=====| ; meta-lambda grammer -> procedure ; Grammars are described below. The procedure generated is a procedure ; of one argument. When applied to an item (usually a list), it attempts ; to match the grammar with the list and perform any actions specified ; if it was able to completely match all the items in the list (to the ; end of the list). If the list or item can not be matched completely, ; the procedure returns #f. ; |==============| ; |The Basic Idea| ; |==============| ; Meta-lambda distinguishes between literals, and identifiers it expects ; to be bound to "predicate-like" procedures. These are procedures of one ; argument that return either #t or #f (like the usual scheme ; predicates like symbol? or number?), or another value. ; As it processes each input-item and the accompanying grammar-item, if ; the grammar-item is a literal that is equal? to the input-item, then ; meta-lambda accepts the match but discards the input item. ; If the grammar-item is a predicate-procedure, then meta-lambda applies ; that procedure to the input-item. If the result is #f, the match ; fails. If the result is #t, meta-lambda saves the input item in an ; internal accumulator-stack. If the result is any other value, ; meta-lambda saves that value in the stack, instead of the input item. ; Then, when and if the list is empty and meta-lambda encounters an ; action (expressed as a lambda expression in the grammar), meta-lambda ; applies that lambda expression to the items in the stack, and returns ; the result. (The "stack" is a list). Thus if a lambda-expression is ; supplied as an action it must contain as many arguments as there were ; predicate-procedures preceeding it. ; Since lambda-expression's denote actions to be taken at the end of a ; match (when the input-list is null), predicate procedures must be ; expressed by writing the identifier they are bound to. (No anonymous ; predicates!) ; You don't have to supply an action. In that case, if the stack is ; empty, meta-lambda returns true. If there is one item on the stack, ; meta-lambda returns that item. Otherwise, it returns the whole stack ; (as a list). ; There are other options, but that's the gist of it. ; (define match-foo-bar ; (meta-lambda ; (and 'foo 'bar (lambda () 'tada)))) ; (match-foo-bar '(foo bar)) => 'tada ; (match-foo-bar '(3 cat dog)) => #f ; (define match-symbol-number-foo ; (meta-lambda ; (and symbol? number? 'foo (lambda (sym num) ; (string-append (symbol->string sym) ; (number->string num)))))) ; (match-symbol-number-foo '(cat 3 foo)) => "cat3" ; (match-symbol-number-foo '(cat foo foo)) => #f ; (define both-of-em ; (meta-lambda ; (and match-foo-bar match-symbol-number-foo))) ; (both-of-em '((foo bar) (cat 3 foo))) => '(tada "cat3") ; |========| ; |Grammars| ; |========| ; grammar = (or ...) ;choice ; | (and ...) ;sequence ; | (+ ) ;kleene+ ; | (* ) ;kleene* ; | (?* ) ;kleene? ; | ;literals ; | ;predicate-binding ; | () ;end-of-list ; | ? ;anything ; | __ ;rest-of-list ; | () ;sublist ; | (unquote ) ;location ; | (unquote-splicing ) ;not-a-list ; | ;result action ; | (else ) ;else-clause ; action = (lambda ) ; | (begin ) ; literal = (quote ) ; | ; | ; | ; |==================| ; |The usual suspects| ; |==================| ; choice ; ====== ; (or ...) ; Try to match each grammar against the input in order. If a match ; fails, backtrack on the input and revert the stack. ; sequence ; ======== ; (and ...) ; Match each grammar against an item in the input, failing as soon as a ; match fails ; literals ; ======== ; 'cat 'dog "three" 34 #\a '(a b c) etc. ; Compare the input item with the literal using equal?, and discard the ; input and proceed if the result is #t, otherwise fail ; identifier ; ========== ; symbol? number? boolean? match-and-do-something ; The identifier should be bound to a procedure of one argument that ; returns one value. If the result of applying the procedure to the next ; input item is #f, then fail. If the result is #t, then save the ; input-item on the stack and proceed. If the result is any other value, ; save that value on the stack in place of the input item, and proceed. ; action ; ====== ; (lambda (x y) ) ; (begin (display "foo") (narfle! garthaks)) ; If there is any input remaining, these immediately fail. Otherwise, if ; a "lambda", apply the lambda to the accumulated stack of ; predicate-matched items and return the result. If a "begin", ignore ; the stack and perform the sequence, returning the result. ; |================| ; |Useful additions| ; |================| ; kleene-star ; =========== ; (* ) ; Match zero or more occurrences of the grammar, and place the list of ; the results on the stack. ; kleene-plus ; =========== ; (* ) ; Match one or more occurrences of the grammar, and place the list of ; the results on the stack. (If no results than '() is placed on the ; stack). ; kleene? ; =========== ; (?* ) ; Match zero or one occurrences of the grammar, and place the list of ; the results on the stack, or do nothing. ; anything ; ======== ; ? ; Automatically match anything and put it on the input stack. ; rest-of-list ; ============ ; __ ; Automatically match the rest of a list and place it on the input stack. ; If followed by a lambda-action, it should be a variable arity lambda in order to bind the result of the match of __. ; (define number-and-rest ; (meta-lambda ; (and number? __ (lambda (num . rest) ; (cons num (cadr rest)))))) ; (number-and-rest '(3 cat dog foo)) => '(3 . dog) ; |=============| ; |Weirder stuff| ; |=============| ; end-of-list ; =========== ; () ; Explicitly match the end of list and proceed. ; sub-lists ; ========= ; () ; Ah, trees. Wrapping a parens around a grammar causes meta-lambda to ; expect a sublist. It itself can contain actions that return ; values. The sublist is matched and returns results as if you had ; written a separte meta-lambda for the sublist, and whatever it returns ; is placed on the stack as a single item. ; (define match-lambda-one ; (meta-lambda ; (and 'lambda (symbol?) ? (lambda (formals body) ; `(forms ,@formals))))) ; (match-lambda-one '(lambda (a) (foo a (bar b c)))) => '(forms . a) ; (define match-lambda ; (meta-lambda ; (and 'lambda ((* symbol?)) ? (lambda (formals body) ; `(forms ,@formals))))) ; (match-lambda '(lambda (a b c) (foo a (bar b c)))) => '(forms a b c) ; location ; ======== ; (unquote ) ; This means match the literal that is bound to the identifier against ; the next input. Useful for parameterizing. ; (define (make-foo-matcher x) ; (meta-lambda ; (and 'foo ,x))) ; (define foo-3 (make-foo-matcher 3)) ; (define foo-cat (make-foo-matcher 'cat)) ; (foo-3 '(foo 3)) => #t ; (foo-3 '(foo 4)) => #f ; (foo-cat '(foo cat)) => #t ; (foo-cat '(foo 3)) => #f ; not-a-list ; ========== ; (unquote-splicing ) ; Wrap the input (or the next item in the input) in a list, and then ; match. This way meta-lambda can match lists or single items. ; (define infix ; (let ((op? (meta-lambda ;doing this for demo purposes. (case ...) ; ;is better here ; (or (and ,@'+ (begin +)) ; (and ,@'- (begin -)) ; (and ,@'* (begin *)))))) ; (meta-lambda ; (or ,@integer? ; (and infix op? infix (lambda (a op b) (op a b))))))) ; (infix '((3 + 4) * ((6 - 3) + 4))) => 49 ; else ; ==== ; (else ) ; If an else-clause is encountered, the rest of the input is immediately ; accepted, but instead of being accepted on the stack, it is ; immediately passed to , which should be variable arity. The ; proedure's result, if it returns at all, becomes the result of the ; whole meta-lambda. ; (define infix2 ; (let ((op? (lambda (y) ; (case y ; ((+) +) ; ((-) -) ; ((*) *))))) ; (meta-lambda ; (or ,@integer? ; (and infix op? infix (lambda (a op b) (op a b))) ; (else (lambda x (error "bad input" x))))))) ; (infix2 '((3 + 4) * ((foo - 3) + 4))) => &error bad input (foo) ; |======| ; |Extras| ; |======| ; meta-lambda-dot grammer -> procedure ; Like meta-lambda, but the procedure returned is variable arity as in: ; (lambda x ...) ; The match procedure is applied to the list "x"