diff options
Diffstat (limited to 'module/language/sassy/meta-lambda.scm')
-rw-r--r-- | module/language/sassy/meta-lambda.scm | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/module/language/sassy/meta-lambda.scm b/module/language/sassy/meta-lambda.scm new file mode 100644 index 000000000..3b19c57fa --- /dev/null +++ b/module/language/sassy/meta-lambda.scm @@ -0,0 +1,491 @@ +; 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 <grammar> ...) ;choice +; | (and <grammer> ...) ;sequence +; | (+ <grammar>) ;kleene+ +; | (* <grammar>) ;kleene* +; | (?* <grammar>) ;kleene? +; | <literal> ;literals +; | <identifier> ;predicate-binding +; | () ;end-of-list +; | ? ;anything +; | __ ;rest-of-list +; | (<grammar>) ;sublist +; | (unquote <identifier>) ;location +; | (unquote-splicing <grammer>) ;not-a-list +; | <action> ;result action +; | (else <procedure>) ;else-clause + +; action = (lambda <formals> <body>) +; | (begin <sequence>) + +; literal = (quote <scheme datum>) +; | <char> +; | <number> +; | <string> + +; |==================| +; |The usual suspects| +; |==================| + +; choice +; ====== + +; (or <grammar> ...) + +; Try to match each grammar against the input in order. If a match +; fails, backtrack on the input and revert the stack. + +; sequence +; ======== + +; (and <grammer> ...) + +; 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) <stuff>) +; (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 +; =========== + +; (* <grammar>) + +; Match zero or more occurrences of the grammar, and place the list of +; the results on the stack. + +; kleene-plus +; =========== + +; (* <grammar>) + +; 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? +; =========== + +; (?* <grammar>) + +; 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 +; ========= + +; (<grammar>) + +; 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 <identifier>) + +; 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 <grammar>) + +; 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 <procedure>) + +; 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 <procedure>, 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" |