de-cpsed command-line pattern-matching code.

This commit is contained in:
Abdulaziz Ghuloum 2008-12-28 23:56:44 -05:00
parent 75aaa0b235
commit 120a6dab52
1 changed files with 150 additions and 139 deletions

View File

@ -3,7 +3,7 @@
;;; WORK IN PROGRESS, NOT FOR CONSUMPTION ;;; WORK IN PROGRESS, NOT FOR CONSUMPTION
;;; TODO: long options ;;; TODO: long options
;;; multiple options in one go, e.g., -XYZ ;;; multiple options in one go, e.g., -XYZ
;;; concat value with option, e.g., -Xxvalue ;;; concat value with option, e.g., -Xxvalue
;;; usage error message [ok?] ;;; usage error message [ok?]
;;; -h --help should not be user-defined ;;; -h --help should not be user-defined
;;; check duplicate options ;;; check duplicate options
@ -11,7 +11,17 @@
(import (ikarus)) (import (ikarus))
(define (dispatch-opts arguments data* proc*) (define (dispatch-opts arguments data* proc*)
(define (print-usage) (define-condition-type &help &condition
make-help-condition help-condition?
(extended? help-extended?))
(define-condition-type &unmatched &condition
make-unmatched-condition unmatched-condition?)
(define (help x)
(raise (make-help-condition x)))
(define (unmatched)
(raise (make-unmatched-condition)))
(define (print-usage detailed?)
(define-record-type f (fields id char type def)) (define-record-type f (fields id char type def))
(define (mkf x id) (define (mkf x id)
(make-f id (car x) (cadr x) (cddr x))) (make-f id (car x) (cadr x) (cddr x)))
@ -27,44 +37,45 @@
(format " [-~a <~a>]~a" (f-char x) (f-id x) c))) (format " [-~a <~a>]~a" (f-char x) (f-id x) c)))
(define (fmt-<> x) (define (fmt-<> x)
(format " <~a>" x)) (format " <~a>" x))
(define (synopsis f* args args-rest)
(let ([opt* (get 'optional f*)]
[flag* (get 'flag f*)]
[req0* (get 'required0 f*)]
[req1* (get 'required1 f*)]
[z0* (get 'zero-plus f*)]
[z1* (get 'one-plus f*)])
(let-values ([(p e) (open-string-output-port)])
(display (car arguments) p)
(display (apply string-append (map fmt-req-no-value req0*)) p)
(unless (null? flag*)
(fprintf p " [-~a]"
(list->string (map f-char flag*))))
(display (apply string-append (map (fmt-z "") opt*)) p)
(display (apply string-append (map (fmt-z "*") z0*)) p)
(display (apply string-append (map (fmt-z "+") z1*)) p)
(display (apply string-append (map fmt-req req1*)) p)
(display (apply string-append (map fmt-<> args)) p)
(when args-rest
(display (string-append (fmt-<> args-rest) " ...") p))
(e))))
(define (print-usage-line help fields field-ids args args-rest dash-rest) (define (print-usage-line help fields field-ids args args-rest dash-rest)
(let ([f* (map mkf fields field-ids)]) (let ([f* (map mkf fields field-ids)])
(let ([opt* (get 'optional f*)] (display " ")
[flag* (get 'flag f*)] (display (synopsis f* args args-rest))
[req0* (get 'required0 f*)] (newline)
[req1* (get 'required1 f*)] (unless (string=? help "")
[z0* (get 'zero-plus f*)] (display " ")
[z1* (get 'one-plus f*)]) (display help)
(display (newline))
(string-append (when detailed?
"\n " (let ([def* (filter f-def (get 'optional f*))])
(car arguments)
(apply string-append (map fmt-req-no-value req0*))
(if (null? flag*)
""
(format " [-~a]"
(list->string (map f-char flag*))))
(apply string-append (map (fmt-z "") opt*))
(apply string-append (map (fmt-z "*") z0*))
(apply string-append (map (fmt-z "+") z1*))
(apply string-append (map fmt-req req1*))
(apply string-append (map fmt-<> args))
(if args-rest
(string-append (fmt-<> args-rest) " ...")
"")
"\n"))
(unless (string=? help "")
(printf "\n ~a\n" help))
(let ([def* (filter f-def opt*)])
(unless (null? def*) (unless (null? def*)
(printf "\n")
(for-each (for-each
(lambda (x) (lambda (x)
(printf " -~a defaults to ~a\n" (f-char x) (printf " -~a defaults to ~a\n" (f-char x)
(f-def x))) (f-def x)))
def*) def*))))))
))))) (printf "\nUsage:\n")
(printf "\nUsage:")
(for-each (lambda (x) (apply print-usage-line x)) data*) (for-each (lambda (x) (apply print-usage-line x)) data*)
(print-usage-line "Display this help message" (print-usage-line "Display this help message"
'([#\h required0 . #f]) '([#\h required0 . #f])
@ -80,127 +91,114 @@
(char=? (string-ref x 0) #\-) (char=? (string-ref x 0) #\-)
(not (char=? (string-ref x 1) #\-)))) (not (char=? (string-ref x 1) #\-))))
(define (fill-char-opt c ls fields k) (define (fill-char-opt c ls fields)
;;; field = [c required ] ;;; field = [c required ]
;;; | [c flag . default] ;;; | [c flag . default]
;;; | [c zero-plus . list] ;;; | [c zero-plus . list]
;;; | [c one-plus . list] ;;; | [c one-plus . list]
;;; | [c optional . str] ;;; | [c optional . str]
(let f ([fields fields] [k k]) (let f ([fields fields])
(and (pair? fields) (when (null? fields) (unmatched))
(let ([field (car fields)]) (let ([field (car fields)])
(if (char=? c (car field)) (if (char=? c (car field))
(let ([t (cadr field)]) (let ([t (cadr field)])
(case t (case t
[(required1 optional) [(required1 optional)
(and (not (null? ls)) (when (null? ls) (unmatched))
(let ([val (car ls)] [ls (cdr ls)]) (let ([val (car ls)] [ls (cdr ls)])
(k (cons (values (cons (cons* c 'ok val) (cdr fields)) ls))]
(cons* c 'ok val) [(flag)
(cdr fields)) (values (cons (cons* c 'ok #t) (cdr fields)) ls)]
ls)))] [(zero-plus one-plus)
[(flag) (when (null? ls) (unmatched))
(k (cons (cons* c 'ok #t) (cdr fields)) ls)] (let ([val (car ls)])
[(zero-plus one-plus) (values
(and (not (null? ls)) (cons (cons* c 'zero-plus (cons val (cddr field)))
(let ([val (car ls)]) (cdr fields))
(k (cons (cons* c 'zero-plus (cdr ls)))]
(cons val (cddr field))) [else (unmatched)]))
(cdr fields)) (let-values ([(fields ls) (f (cdr fields))])
(cdr ls))))] (values (cons field fields) ls))))))
[else #f]))
(f (cdr fields)
(lambda (fields ls)
(k (cons field fields) ls))))))))
(define (fill-option a ls fields k) (define (fill-option a ls fields)
(if (= (string-length a) 2) (if (= (string-length a) 2)
(let ([char (string-ref a 1)]) (let ([char (string-ref a 1)])
(fill-char-opt char ls fields (when (char=? char #\h) (help #f))
(lambda (fields ls) (fill-char-opt char ls fields))
(match-fields fields ls k))))
(error 'fill-option "not yet"))) (error 'fill-option "not yet")))
(define (match-fields fields ls k) (define (match-fields fields ls)
(if (null? ls) (if (null? ls)
(k fields ls) (values fields ls)
(let ([a (car ls)]) (let ([a (car ls)])
(if (option? a) (if (option? a)
(fill-option a (cdr ls) fields k) (let-values ([(fields ls) (fill-option a (cdr ls) fields)])
(k fields ls))))) (match-fields fields ls))
(values fields ls)))))
(define (match-args args ls k) (define (match-args args ls)
(if (null? args) (cond
(k '() ls) [(null? args) (values '() ls)]
(and (not (null? ls)) [(null? ls) (unmatched)]
(let ([a (car ls)]) [else
(and (not (option? a)) (let ([a (car ls)])
(match-args (cdr args) (cdr ls) (when (option? a) (unmatched))
(lambda (a* ls) (let-values ([(a* ls) (match-args (cdr args) (cdr ls))])
(k (cons a a*) ls)))))))) (values (cons a a*) ls)))]))
(define (match-args-rest a/f ls k) (define (match-args-rest a/f ls)
(if a/f (if a/f
(let f ([ls ls] [k (lambda (a ls) (k (list a) ls))]) (let-values ([(x ls)
(if (null? ls) (let f ([ls ls])
(k '() ls) (if (null? ls)
(let ([a (car ls)]) (values '() ls)
(if (string=? a "--") (let ([a (car ls)])
(k '() ls) (if (string=? a "--")
(and (not (option? a)) (values '() ls)
(f (cdr ls) (if (option? a)
(lambda (a* ls) (unmatched)
(k (cons a a*) ls)))))))) (let-values ([(a* ls) (f (cdr ls))])
(and (or (null? ls) (string=? (car ls) "--")) (values (cons a a*) ls)))))))])
(k '() ls)))) (values (list x) ls))
(if (or (null? ls) (string=? (car ls) "--"))
(values '() ls)
(unmatched))))
(define (match-dash-rest a/f ls k) (define (match-dash-rest a/f ls)
(if a/f (if a/f
(if (null? ls) (if (null? ls)
(k '(())) '(())
(and (string=? (car ls) "--") (if (string=? (car ls) "--")
(k (list (cdr ls))))) (list (cdr ls))
(and (null? ls) (k '())))) (unmatched)))
(if (null? ls) '() (unmatched))))
(define (fix-fields ls k) (define (fix-field x)
(cond (let ([type (cadr x)] [value (cddr x)])
[(null? ls) (k '())] (case type
[else [(ok flag optional) value]
(let ([a (car ls)] [(zero-plus) (reverse value)]
[k (lambda (a) [else (unmatched)])))
(fix-fields (cdr ls)
(lambda (ls)
(k (cons a ls)))))])
(let ([type (cadr a)] [value (cddr a)])
(case type
[(ok flag optional) (k value)]
[(zero-plus) (k (reverse value))]
[else #f])))]))
(define (match _help fields _field-ids args args-rest dash-rest) (define (match _help fields _field-ids args args-rest dash-rest)
(let ([prog (car arguments)]) (cons (car arguments)
(match-fields fields (cdr arguments) (let*-values ([(fields ls) (match-fields fields (cdr arguments))]
(lambda (fields ls) [(fields) (map fix-field fields)]
(fix-fields fields [(args ls) (match-args args ls)]
(lambda (fields) [(args-rest ls) (match-args-rest args-rest ls)]
(match-args args ls [(dash-rest) (match-dash-rest dash-rest ls)])
(lambda (args ls) (append fields args args-rest dash-rest))))
(match-args-rest args-rest ls
(lambda (args-rest ls) (guard (con
(match-dash-rest dash-rest ls [(help-condition? con)
(lambda (dash-rest) (print-usage (help-extended? con))])
(cons prog (let f ([data* data*] [proc* proc*])
(append fields (if (null? data*)
args args-rest (help #f)
dash-rest)))))))))))))) (guard (con
[(unmatched-condition? con)
(let f ([data* data*] [proc* proc*]) (f (cdr data*) (cdr proc*))])
(if (null? data*) (apply (car proc*) (apply match (car data*))))))))
(print-usage)
(let ([opts (apply match (car data*))])
(if opts
(apply (car proc*) opts)
(f (cdr data*) (cdr proc*)))))))
(define-syntax parse-command-line (define-syntax parse-command-line
@ -413,6 +411,19 @@
(test command8 ("./prog" "-h") #f) (test command8 ("./prog" "-h") #f)
(define (ls-command ls)
(parse-command-line ls
[(ls "-A?" A "-B?" B "-C?" C "-F?" F "-G?" G "-H?" H "-L?" L
"-P?" P "-R?" R "-S?" S "-T?" T "-W?" W "-Z?" Z "-a?" a
"-b?" b "-c?" c "-d?" d "-e?" e "-f?" f "-g?" g "-i?" i
"-k?" k "-l?" l "-m?" m "-n?" n "-o?" o "-p?" p "-q?" q
"-r?" r "-s?" s "-t?" t "-u?" u "-w?" w "-x?" x "-1?" o1
files ...)
#t]))
(test ls-command ("ls" "-h") #f)
#!eof #!eof
(define (real-test ls) (define (real-test ls)
(command-line-interface ls (command-line-interface ls
@ -438,14 +449,14 @@
(list 2 program libdirs script-file)] (list 2 program libdirs script-file)]
[(program "-O0" "-L" libdirs ... "-l" library-files ... [(program "-O0" "-L" libdirs ... "-l" library-files ...
init-files ... "--script" script-file args ...) init-files ... "--script" script-file args ...)
"Run <script-file> in R5RS-script mode "Run <script-file> in R5RS-script mode"
Each of <library-files> must contain a library and are "Each of <library-files> must contain a library and are"
installed before <init-files> are loaded and "installed before <init-files> are loaded and"
<script-file> is run." "<script-file> is run."
(list 3 program libdirs library-files init-files script-file args)] (list 3 program libdirs library-files init-files script-file args)]
[(program "-O0" "-L" libdirs ... "-l" library-files init-files ... [(program "-O0" "-L" libdirs ... "-l" library-files init-files ...
"--" args ...) "--" args ...)
"Run Ikarus in interactive mode. Each of <library-files> "Run Ikarus in interactive mode."
must contain a library and are installed before the "Each of <library-files> must contain a library and are"
<init-files> are loaded" "installed before the <init-files> are loaded"
(list 4 program libdirs library-files init-files args)])) (list 4 program libdirs library-files init-files args)]))