Changeset 29259 in project


Ignore:
Timestamp:
06/27/13 11:30:35 (7 years ago)
Author:
Ivan Raikov
Message:

cgi: moving parser combinators into their own module

Location:
release/4/cgi/trunk
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • release/4/cgi/trunk/cgi.setup

    r29075 r29259  
    33(define (dynld-name fn)         
    44  (make-pathname #f fn ##sys#load-dynamic-extension))   
     5
     6(compile -O -d2 -s grammar.scm -j cgi-grammar -o cgi-grammar.so)
     7(compile -s cgi-grammar.import.scm)
     8
     9(install-extension
     10
     11 ;; Name of your extension:
     12  'cgi-grammar
     13
     14  ;; Files to install for your extension:
     15  `(,(dynld-name "cgi-grammar") ,(dynld-name "cgi-grammar.import") )
     16 
     17
     18  ;; Assoc list with properties for your extension:
     19  '((version 1.0)
     20    ))
     21
    522
    623(compile -O -d2 -s environment.scm -j cgi-environment -o cgi-environment.so)
  • release/4/cgi/trunk/environment.scm

    r29234 r29259  
    11;;
    2 ;;  Parser combinators for CGI request metavariables,
     2;;  Access to CGI request metavariables,
    33;;  as described in RFC 3875 "The Common Gateway Interface (CGI) 1.1".
    44;;
     
    5050        (import (only abnf <CoreABNF> CharLex->CoreABNF))
    5151
     52        (require-extension cgi-grammar)
    5253
    5354        ;; helper macro for mutually-recursive parser definitions
     
    9293        (define (SERVER_PROTOCOL)     (get-environment-variable (->string 'SERVER_PROTOCOL)))
    9394
    94           ;; CGI Variable Parsing
    95 
    96           (define (parse cont p)
    97             (lambda (s)  (p cont (lambda (x) (error 'parse "CGI meta variable parser error" x)) s)))
    98 
    99 
    100           (define (meta-variable s p . rest)
    101             (let-optionals rest ((f identity))
    102                ((parse (compose f car) p) `(() ,(string->list s)))))
    103          
    104 
    105           ;; Match any US-ASCII character except for control characters and
    106           ;; separators.
    107 
    108           (define token  (abnf:repetition1
    109                           (abnf.set (char-set-difference
    110                                      char-set:ascii
    111                                      (char-set-union char-set:iso-control
    112                                                      (char-set #\space #\tab)
    113                                                      (string->char-set "()<>@,;:\\/[]?={}"))))))
    114 
    115 
    116 
    117           ;; within a quoted string, any ASCII graphic or space is permitted
    118           ;; without blackslash-quoting except double-quote and the backslash
    119           ;; itself.
    120           (define qdtext          (abnf.set
    121                                    (char-set-difference
    122                                     char-set:printing
    123                                     (char-set #\" #\\))))
    124 
    125           (define quoted-string    (abnf:concatenation
    126                                     (abnf:drop-consumed abnf.dquote)
    127                                     (abnf:repetition qdtext)
    128                                     (abnf:drop-consumed abnf.dquote)))
    129 
    130 
    131           (define p-auth-type
    132             (abnf:bind-consumed->string
    133              (abnf:alternatives
    134               (abnf.lit "Basic")
    135               (abnf.lit "Digest")
    136               token)))
    137 
    138           (define (auth-type)
    139             (meta-variable
    140              (AUTH_TYPE)
    141              p-auth-type
    142              (lambda (x) (let ((v (car x))) (and (not (string-null? v)) (string->symbol v))))
    143              ))
    144 
    145           (define p-content-length
    146             (abnf:bind-consumed->string
    147              (abnf:repetition1 abnf.decimal)))
    148 
    149           (define (content-length)
    150             (meta-variable
    151              (CONTENT_LENGTH)
    152              p-content-length
    153              (lambda (x) (let ((v (car x)))
     95        ;; CGI Variable Parsing
     96
     97        (define (parse cont p)
     98          (lambda (s)  (p cont (lambda (x) (error 'parse "CGI meta variable parser error" x)) s)))
     99
     100
     101        (define (meta-variable s p . rest)
     102          (let-optionals rest ((f identity))
     103                         ((parse (compose f car) p) `(() ,(string->list s)))))
     104       
     105
     106        (define (auth-type)
     107          (meta-variable
     108           (AUTH_TYPE)
     109           p-auth-type
     110           (lambda (x) (let ((v (car x))) (and (not (string-null? v)) (string->symbol v))))
     111           ))
     112       
     113        (define (content-length)
     114          (meta-variable
     115           (CONTENT_LENGTH)
     116           p-content-length
     117           (lambda (x) (let ((v (car x)))
    154118                           (and (not (string-null? v)) (string->number v))))
    155              ))
    156 
    157 
    158           ;;       CONTENT_TYPE = "" | media-type
    159           ;;       media-type   = type "/" subtype *( ";" parameter )
    160           ;;       type         = token
    161           ;;       subtype      = token
    162           ;;       parameter    = attribute "=" value
    163           ;;       attribute    = token
    164           ;;       value        = token | quoted-string
    165 
    166           (define p-content-type
    167             (let* ((type       (abnf:bind-consumed->string token))
    168                    (subtype    (abnf:bind-consumed->string token))
    169                    (attribute  (abnf:bind-consumed->string token))
    170                    (value      (abnf:bind-consumed->string
    171                                 (abnf:alternatives token quoted-string)))
    172                    (parameter  (lambda (sk fk inp)
    173                                  ((abnf:bind-consumed-strings->list
    174                                    (abnf:concatenation
    175                                     attribute
    176                                     (abnf:drop-consumed (abnf.char #\=))
    177                                     value)) sk fk inp)))
    178                    (media-type (abnf:concatenation
    179                                 (abnf:bind-consumed-strings->list 'type type)
    180                                 (abnf:drop-consumed (abnf.char #\/))
    181                                 (abnf:bind-consumed-strings->list 'subtype subtype)
    182                                 (abnf:repetition
    183                                  (abnf:concatenation
    184                                   (abnf:drop-consumed
    185                                    (abnf:concatenation
    186                                     (abnf.char #\;)
    187                                     (abnf:repetition
    188                                      (abnf.set (char-set #\space #\tab)))
    189                                     ))
    190                                   parameter))))
    191                    )
    192 
    193               (abnf:alternatives
    194                media-type
    195                abnf:pass)
    196              
    197               ))
    198 
    199 
    200           (define (content-type)
    201             (meta-variable
    202              (CONTENT_TYPE)
    203              p-content-type
    204              reverse
    205              ))
    206 
    207 
    208           (define p-gateway-interface
    209             (abnf:concatenation
    210              (abnf:bind-consumed->symbol (abnf.lit "CGI"))
    211              (abnf:drop-consumed (abnf.char #\/))
    212              (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))
    213              (abnf:drop-consumed (abnf.char #\.) )
    214              (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))
    215              ))
    216 
    217 
    218           (define (gateway-interface)
    219             (meta-variable
    220              (GATEWAY_INTERFACE)
    221              p-gateway-interface
    222              ))
    223 
    224 
    225           ;;       PATH_INFO = "" | ( "/" path )
    226           ;;       path      = lsegment *( "/" lsegment )
    227           ;;       lsegment  = *lchar
    228           ;;       lchar     = <any TEXT or CTL except "/">
    229 
    230           (define lchar          (abnf.set
    231                                   (char-set-difference
    232                                    (char-set-union char-set:printing char-set:iso-control)
    233                                    (char-set #\\))))
    234 
    235           (define lsegment       (abnf:bind-consumed->string (abnf:repetition lchar)))
    236           (define path           (abnf:concatenation
    237                                   lsegment
    238                                   (abnf:repetition
    239                                    (abnf:concatenation
    240                                     (abnf:drop-consumed (abnf.char #\/))
    241                                     lsegment))))
    242          
    243           (define p-path-info
    244             (abnf:alternatives
    245              (abnf:concatenation (abnf:drop-consumed (abnf.char #\/))
    246                                  (abnf:consumed-pairs->list path))
    247              abnf:pass))
    248 
    249           (define (path-info)
    250             (meta-variable
    251              (PATH_INFO)
     119           ))
     120       
     121        (define (content-type)
     122          (meta-variable
     123           (CONTENT_TYPE)
     124           p-content-type
     125           reverse
     126           ))
     127       
     128        (define (gateway-interface)
     129          (meta-variable
     130           (GATEWAY_INTERFACE)
     131           p-gateway-interface
     132           ))
     133       
     134        (define (path-info)
     135          (meta-variable
     136           (PATH_INFO)
     137           p-path-info
     138           (lambda (x) (let ((v (car x))) (or (and (string-null? v) (list)) v)))
     139           ))
     140       
     141        (define (path-translated)
     142          (PATH_TRANSLATED))
     143       
     144        (define (query-string)
     145          (let ((s (QUERY_STRING)))
     146            (and s (meta-variable
     147                    (string->list s)
     148                    p-query-string
     149                    ))
     150            ))
     151       
     152        (define (remote-addr)
     153          (meta-variable
     154           (REMOTE_ADDR)
     155           p-remote-addr
     156           ))
     157       
     158        (define (remote-host)
     159          (meta-variable
     160           (REMOTE_HOST)
     161           p-remote-host
     162           (lambda (x) (let ((v (car x))) (or (and (string-null? v) (list)) v)))
     163           ))
     164       
     165        (define (remote-ident)
     166          (REMOTE_IDENT))
     167       
     168        (define (remote-user)
     169          (REMOTE_USER))
     170       
     171        (define (request-method)
     172          (let ((m (REQUEST_METHOD)))
     173            (and m (meta-variable
     174                    m p-request-method
     175                    car
     176                    ))
     177            ))
     178       
     179        (define (script-name)
     180          (meta-variable
     181           (SCRIPT_NAME)
    252182             p-path-info
    253183             (lambda (x) (let ((v (car x))) (or (and (string-null? v) (list)) v)))
    254184             ))
    255185
    256           (define (path-translated)
    257             (PATH_TRANSLATED))
    258          
    259           ;;       QUERY_STRING = query-string
    260           ;;       query-string = *uric
    261           ;;       uric         = reserved | unreserved | escaped
    262 
    263           (define escaped    (abnf:concatenation
    264                               (abnf.char #\%)
    265                               abnf.hexadecimal
    266                               abnf.hexadecimal))
    267           (define reserved   (abnf.set-from-string ";/?:@&=+$,[]"))
    268           (define mark       (abnf.set-from-string "-_.!~*'()"))
    269           (define unreserved (abnf:alternatives abnf.alpha abnf.decimal mark))
    270 
    271           (define uric (abnf:alternatives reserved unreserved escaped))
    272 
    273           (define p-query-string  (abnf:repetition uric))
    274 
    275           (define (query-string)
    276             (let ((s (QUERY_STRING)))
    277               (and s (meta-variable
    278                       (string->list s)
    279                       p-query-string
    280                       ))
    281               ))
    282          
    283 
    284           ;;       REMOTE_ADDR  = hostnumber
    285           ;;       hostnumber   = ipv4-address | ipv6-address
    286           ;;       ipv4-address = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
    287           ;;       ipv6-address = hexpart [ ":" ipv4-address ]
    288           ;;       hexpart      = hexseq | ( [ hexseq ] "::" [ hexseq ] )
    289           ;;       hexseq       = 1*4hex *( ":" 1*4hex )
    290 
    291           (define ddot           (abnf:drop-consumed (abnf.char #\.)))
    292           (define dcolon         (abnf:drop-consumed (abnf.char #\:)))
    293           (define ipv4d          (abnf:bind-consumed->string (abnf:variable-repetition 1 3 abnf.decimal)))
    294           (define ipv4-address   (abnf:concatenation ipv4d ddot ipv4d ddot ipv4d ddot ipv4d))
    295           (define ipv6h          (abnf:bind-consumed->string
    296                                   (abnf:variable-repetition 1 4 abnf.hexadecimal)))
    297           (define hexseq         (abnf:concatenation
    298                                   ipv6h
    299                                   (abnf:repetition
    300                                    (abnf:concatenation dcolon ipv6h))))
    301           (define hexpart        (abnf:alternatives
    302                                   hexseq
    303                                   (abnf:concatenation
    304                                    (abnf:optional-sequence hexseq)
    305                                    (abnf.lit "::")
    306                                    (abnf:optional-sequence hexseq)
    307                                    )))
    308           (define ipv6-address (abnf:concatenation
    309                                 hexpart
    310                                 (abnf:optional-sequence
    311                                  (abnf:concatenation dcolon ipv4-address))))
    312 
    313           (define hostnumber (abnf:alternatives ipv4-address ipv6-address))
    314 
    315           (define p-remote-addr hostnumber)
    316 
    317           (define (remote-addr)
    318             (meta-variable
    319              (REMOTE_ADDR)
    320              p-remote-addr
    321              ))
    322 
    323           ;;       REMOTE_HOST   = "" | hostname | hostnumber
    324           ;;       hostname      = *( domainlabel "." ) toplabel [ "." ]
    325           ;;       domainlabel   = alphanum [ *alphahypdigit alphanum ]
    326           ;;       toplabel      = alpha [ *alphahypdigit alphanum ]
    327           ;;       alphahypdigit = alphanum | "-"
    328 
    329           (define alphanum      (abnf:alternatives
    330                                  abnf.alpha
    331                                  abnf.decimal))
    332 
    333           (define alphahypdigit (abnf:alternatives
    334                                  alphanum
    335                                  abnf.char #\-))
    336 
    337           (define toplabel     (abnf:bind-consumed->string
    338                                 (abnf:concatenation
    339                                  abnf.alpha
    340                                  (abnf:optional-sequence
    341                                   (abnf:concatenation
    342                                    (abnf:repetition alphahypdigit)
    343                                    alphanum)))))
    344 
    345           (define domainlabel  (abnf:bind-consumed->string
    346                                 (abnf:concatenation
    347                                  alphanum
    348                                  (abnf:optional-sequence
    349                                   (abnf:concatenation
    350                                    (abnf:repetition alphahypdigit)
    351                                    alphanum)))))
    352 
    353           (define hostname    (abnf:concatenation
    354                                (abnf:repetition
    355                                 (abnf:concatenation
    356                                  domainlabel
    357                                  ddot))
    358                                toplabel
    359                                (abnf:optional-sequence
    360                                 ddot)))
    361 
    362           (define p-remote-host  (abnf:alternatives
    363                                   hostname hostnumber
    364                                   abnf:pass))
    365 
    366           (define (remote-host)
    367             (meta-variable
    368              (REMOTE_HOST)
    369              p-remote-host
    370              (lambda (x) (let ((v (car x))) (or (and (string-null? v) (list)) v)))
    371              ))
    372 
    373 
    374           (define (remote-ident)
    375             (REMOTE_IDENT))
    376          
    377 
    378           (define (remote-user)
    379             (REMOTE_USER))
    380          
    381 
    382 
    383           ;;       REQUEST_METHOD   = method
    384           ;;       method           = "GET" | "POST" | "HEAD" | extension-method
    385           ;;       extension-method = "PUT" | "DELETE" | token
    386 
    387           (define extension-method  (abnf:alternatives
    388                                      (abnf.lit "PUT")
    389                                      (abnf.lit "DELETE")
    390                                      token))
    391 
    392           (define method (abnf:alternatives
    393                           (abnf.lit "GET")
    394                           (abnf.lit "POST")
    395                           (abnf.lit "HEAD")
    396                           extension-method))
    397 
    398           (define p-request-method (abnf:bind-consumed->string method))
    399 
    400           (define (request-method)
    401             (let ((m (REQUEST_METHOD)))
    402               (and m (meta-variable m
    403                       p-request-method
    404                       car
    405                       ))
    406               ))
    407 
    408           ;;      SCRIPT_NAME = "" | ( "/" path )
    409 
    410           (define (script-name)
    411             (meta-variable
    412              (SCRIPT_NAME)
    413              p-path-info
    414              (lambda (x) (let ((v (car x))) (or (and (string-null? v) (list)) v)))
    415              ))
    416 
    417          
    418           ;;      SERVER_NAME = server-name
    419           ;;      server-name = hostname | ipv4-address | ( "[" ipv6-address "]" )
    420 
    421           (define p-server-name
    422             (abnf:alternatives
    423              hostname
    424              ipv4-address
    425              (abnf:drop-consumed (abnf.char #\[))
    426              ipv6-address
    427              (abnf:drop-consumed (abnf.char #\]))))
    428 
    429           (define (server-name)
    430             (meta-variable
    431               (SERVER_NAME)
    432              p-server-name
    433              ))
    434 
    435 
    436           ;;       SERVER_PORT = server-port
    437           ;;       server-port = 1*digit
    438 
    439           (define p-server-port
    440             (abnf:bind-consumed->string
    441              (abnf:repetition abnf.decimal)))
    442 
    443 
    444           (define (server-port)
    445             (meta-variable
    446              (SERVER_PORT)
    447              p-server-port
    448              (lambda (x) (let ((v (car x))) (and (not (string-null? v)) (string->number v))))
    449              ))
    450 
    451 
    452           ;;       SERVER_PROTOCOL   = HTTP-Version | "INCLUDED" | extension-version
    453           ;;       HTTP-Version      = "HTTP" "/" 1*digit "." 1*digit
    454           ;;       extension-version = protocol [ "/" 1*digit "." 1*digit ]
    455           ;;       protocol          = token
    456 
    457 
    458           (define protocol token)
    459 
    460           (define extension-version
    461             (abnf:concatenation
    462              (abnf:bind-consumed->string protocol )
    463              (abnf:optional-sequence
    464               (abnf:concatenation
    465                (abnf:drop-consumed (abnf.char #\/))
    466                (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))
    467                ddot
    468                (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))))))
    469 
    470           (define HTTP-Version
    471             (abnf:concatenation
    472              (abnf:bind-consumed->string (abnf.lit "HTTP"))
    473              (abnf:optional-sequence
    474               (abnf:concatenation
    475                (abnf:drop-consumed (abnf.char #\/))
    476                (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))
    477                ddot
    478                (abnf:bind-consumed->string (abnf:repetition1 abnf.decimal))))))
    479 
    480          
    481           (define p-server-protocol   
    482             (abnf:bind-consumed-strings->list
    483              (abnf:alternatives
    484               HTTP-Version
    485               (abnf.lit "INCLUDED")
    486               extension-version)))
    487 
    488 
    489           (define (server-protocol)
    490             (meta-variable
    491              (SERVER_PROTOCOL)
    492              p-server-protocol
    493              (lambda (x) (and (not (null? x)) (cons (string->symbol (car x)) (cdr x))))
    494              ))
    495 
    496           (define product
    497             (abnf:concatenation
    498              token
    499              (abnf:optional-sequence
    500               (abnf:concatenation
    501                (abnf.char #\/)
    502                token))))
    503 
    504           (define ctext
    505             (abnf.set (char-set-difference char-set:graphic (char-set #\( #\) #\\))))
    506 
    507           (define comment
    508             (vac
    509              (abnf:concatenation
    510               (abnf.char #\()
    511               (abnf:repetition (abnf:alternatives ctext comment))
    512               (abnf.char #\)))))
    513                
    514           (define p-server-software
    515             (abnf:bind-consumed->string
    516              (abnf:repetition1
    517               (abnf:alternatives
    518                product comment))))
    519            
    520           (define (server-software)
    521             (meta-variable
    522              (SERVER_SOFTWARE)
    523              p-server-software
    524              ))
     186        (define (server-name)
     187          (meta-variable
     188           (SERVER_NAME)
     189           p-server-name
     190           ))
     191       
     192        (define (server-port)
     193          (meta-variable
     194           (SERVER_PORT)
     195           p-server-port
     196           (lambda (x) (let ((v (car x))) (and (not (string-null? v)) (string->number v))))
     197           ))
     198
     199        (define (server-protocol)
     200          (meta-variable
     201           (SERVER_PROTOCOL)
     202           p-server-protocol
     203           (lambda (x) (and (not (null? x)) (cons (string->symbol (car x)) (cdr x))))
     204           ))
     205       
     206        (define (server-software)
     207          (meta-variable
     208           (SERVER_SOFTWARE)
     209           p-server-software
     210           ))
    525211
    526212)
     213
  • release/4/cgi/trunk/protocol.scm

    r29234 r29259  
    4747
    4848;; CGI request type 
    49 (define-record-type request
    50   (Request inputs body)
    51   request?
    52   (inputs request-inputs)
    53   (body   request-body)
     49(define-datatype request request?
     50  (Inputs           (lst (lambda (x) (every input? x))))
     51  (Body             (data byte-blob?))
     52  (InputsWithBody (lst (lambda (x) (every input? x)))
     53                  (data byte-blob?))
    5454  )
     55
     56
     57(define (request-inputs r)
     58  (case request r
     59        (Inputs (lst) lst)
     60        (Body (b) #f)
     61        (InputsWithBody (i b) i)))
     62
     63
     64(define (request-body r)
     65  (case request r
     66        (Inputs (lst) #f)
     67        (Body (b) b)
     68        (InputsWithBody (i b) b)))
     69
    5570
    5671
     
    143158
    144159
    145 
    146 (define (position s b)
    147   (let ((sb (if (string? s) (string->byte-blob s) s)))
    148     (byte-blob-find sb b)
    149     ))
    150        
    151160
    152161;; from SRFI-33, useful in splitting up the bit patterns used to
     
    228237
    229238(define (run-action action inp)
    230   (let-values (((inputs body)  (decode-input inp)))
    231     (let* ((rq   (Request inputs body))
     239  (match-let (((inputs body)  (decode-input inp)))
     240    (let* ((rq   (cond ((null? inputs) (Body body))
     241                       ((not body) (Inputs inputs))
     242                       (else (InputsWithBody inputs body))))
    232243           (rs   (action rq)))
    233244      (if (not (response? rs))
     
    240251
    241252(define (decode-input inp)
    242   (let ((inputs+body  (body-input inp)))
    243     (values (append (query-input) (first inputs+body))
    244             (second inputs+body))))
     253  (match-let (((inputs body)  (body-input inp)))
     254    (list (append (query-input) inputs) body)))
    245255
    246256
     
    255265
    256266
     267(define (positions s b)
     268  (let ((sb (if (string? s) (string->byte-blob s) s)))
     269    (byte-blob-find sb b)
     270    ))
     271       
     272(define (skip-line x)
     273  (byte-blob-char-trim (lambda (c) (not (char=? c #\newline))) x))
     274
     275(define (split-part x)
    257276
    258277(define (multipart-decode ps inp)
    259   (print "ps = " ps)
    260278  (let ((boundary (alist-ref "boundary" ps string=?)))
    261     (print "boundary = " boundary)
    262279    (if (not boundary) (list '() inp)
    263         (let ((boundary1 (string->byte-blob (car boundary)))
    264               (skipbnd  (lambda (x) (byte-blob-char-trim x (lambda (c) (not (char=? c #\newline)))))))
    265           (print "position boundary = " (position boundary1 inp))
    266           (match-let (((_ (contents . rest)) (position boundary1 inp)))
    267                      (print "contents = " contents)
    268                      (let loop ((rest rest) (ax (list (skipbnd contents))))
    269                        (match-let (((pref suff) (position boundary1 rest)))
    270                                   (if (or (byte-blob-empty? pref) (byte-blob-empty? suff))
    271                                       (list (reverse ax) suff)
    272                                       (loop (skipbnd suff) (cons pref ax)))
    273                                   ))
    274                      ))
    275         ))
    276   )
     280        (let ((boundary1 (string->byte-blob (car boundary))))
     281          (match-let (((prefix contents) (positions boundary1 inp)))
     282                     (let ((parts (map (lambda (x) (skip-line (car x))) contents)))
     283                       (map (lambda (part)
     284                              (match-let (((headers content) (split-part part)))
     285                                (let ((ctype (alist-ref 'Content-type headers))
     286                                      (fn (alist-ref 'Content-disposition headers)))
     287                                  (Input content fn ctype))
     288                                ))
     289                            parts)
     290                       ))
     291          ))
     292    ))
    277293
    278294
     
    280296
    281297(define (decode-body ctype inp)
    282   (print "ctype = " ctype)
    283298  (match ctype
    284299         ((('type "multipart") ('subtype "form-data") . ps)
    285           (list (list) (multipart-decode ps inp)))
     300          (list (multipart-decode ps inp) #f))
    286301         ((('type "application") ('subtype "x-www-form-urlencoded"))
    287           (list (form-input inp) (byte-blob-empty)))
     302          (list (form-input inp) #f))
    288303         (else
    289304          (list (list) inp))))
Note: See TracChangeset for help on using the changeset viewer.