#lang racket/base (require racket/list racket/contract/base racket/contract/combinator racket/pretty "core.rkt") (provide (contract-out [xexpr/c contract?] [xexpr? (any/c . -> . boolean?)] [validate-xexpr (any/c . -> . (one-of/c #t))] [rename correct-xexpr/k? correct-xexpr? (any/c (-> any/c) (exn:invalid-xexpr? . -> . any/c) . -> . any/c)]) (struct-out exn:invalid-xexpr)) ;; Xexpr ::= String ;; | (list* Symbol (listof Attribute-srep) (listof Xexpr)) ;; | (cons Symbol (listof Xexpr)) ;; | Symbol ;; | Nat (WFC: Valid Char) ;; | Comment ;; | Processing-instruction ;; | Cdata ;; Attribute-srep ::= (list Symbol String) ;; sorting is no longer necessary, since xt3d uses xml->zxexpr, which sorts. (define xexpr-datum/c (or/c string? symbol? valid-char? comment? p-i? cdata? pcdata?)) (define (xexpr? x) (not (incorrect-xexpr? x))) (define (validate-xexpr x) (define maybe-exn (incorrect-xexpr? x)) (if maybe-exn (raise maybe-exn) #t)) (define xexpr/c (make-flat-contract #:name 'xexpr? #:projection (lambda (blame) (lambda (val) (with-handlers ([exn:invalid-xexpr? (lambda (exn) (raise-blame-error blame val "Not an Xexpr. ~a\n\nContext:\n~a" (exn-message exn) (pretty-format val)))]) (validate-xexpr val) val))) #:first-order xexpr?)) ;; ;; ;; ;; ;; ;; ; ;; ; xexpr? helpers (define-struct (exn:invalid-xexpr exn:fail) (code)) ;; correct-xexpr/k? : any (-> a) (exn -> a) -> a ;; Calls true-k if x is a correct-xexpr. Otherwise, calls the ;; failure continuation false-k with an exn:invalid-xexpr instance. (define (correct-xexpr/k? x true-k false-k) (define maybe-exn (incorrect-xexpr? x)) (if maybe-exn (false-k maybe-exn) (true-k))) ;; incorrect-xexpr?: any -> (or/c #f exn:invalid-xexpr) ;; Returns an exn:invalid-xexpr if the xexpr has incorrect structure. ;; Otherwise, returns #f. (define (incorrect-xexpr? x) (cond [(string? x) #f] [(symbol? x) #f] [(valid-char? x) #f] [(comment? x) #f] [(p-i? x) #f] [(cdata? x) #f] [(pcdata? x) #f] [(list? x) (cond [(null? x) (make-exn:invalid-xexpr "Expected a symbol as the element name, given nothing" (current-continuation-marks) x)] [else (if (symbol? (car x)) (cond [(has-attribute-pairs? x) (define maybe-exn (erroneous-attribute-pairs? (cadr x))) (cond [maybe-exn maybe-exn] [else (for/or ([elt (in-list (cddr x))]) (incorrect-xexpr? elt))])] [else (for/or ([elt (in-list (cdr x))]) (incorrect-xexpr? elt))]) (make-exn:invalid-xexpr (format "Expected a symbol as the element name, given ~s" (car x)) (current-continuation-marks) x))])] [(permissive-xexprs) #f] [else (make-exn:invalid-xexpr (format (string-append "Expected a string, symbol, valid numeric entity, comment, " "processing instruction, or list, given ~s") x) (current-continuation-marks) x)])) ;; has-attribute-pairs? : List -> Boolean ;; True if the Xexpr provided has an attribute list. The attribute list is not ;; checked for correct structure here. (define (has-attribute-pairs? x) (and (> (length x) 1) (list? (cadr x)) (for/and ([attr (in-list (cadr x))]) (pair? attr)))) ;; erroneous-attribute-pairs? : List -> (or/c #f exn:invalid-xexpr) ;; Returns exn:invalid-expr if the attribute pair list is not correctly structured. (define (erroneous-attribute-pairs? attrs) (cond [(null? attrs) #f] [else (define attr (car attrs)) (cond [(pair? attr) (define maybe-exn (erroneous-attribute-symbol-string? attr)) (cond [maybe-exn maybe-exn] [else (erroneous-attribute-pairs? (cdr attrs))])] [else (make-exn:invalid-xexpr (format "Expected an attribute pair, given ~s" attr) (current-continuation-marks) attr)])])) ;; erroneous-attribute-symbol-string? : List -> (or/c #f exn:invalid-xexpr) ;; Returns exn:invalid-expr if the list is not a (String, Symbol) pair. (define (erroneous-attribute-symbol-string? attr) (if (symbol? (car attr)) (if (pair? (cdr attr)) (if (or (string? (cadr attr)) (permissive-xexprs)) #f (make-exn:invalid-xexpr (format "Expected an attribute value string, given ~v" (cadr attr)) (current-continuation-marks) (cadr attr))) (make-exn:invalid-xexpr (format "Expected an attribute value string for attribute ~s, given nothing" attr) (current-continuation-marks) attr)) (make-exn:invalid-xexpr (format "Expected an attribute symbol, given ~s" (car attr)) (current-continuation-marks) (car attr)))) ;; ; end xexpr? helpers ;; ;; ;; ;; ;; ;; ;; ;;