#lang racket/base (provide schema-check-problems schema-check-problems! valid-id?) (require racket/match) (require (only-in racket/list check-duplicates)) (require (only-in racket/string string-join)) (require preserves/order) (require (only-in "type.rkt" unwrap)) (require "gen/schema.rkt") (define (valid-id? x) (and (symbol? x) (regexp-match? #px"^[a-zA-Z][a-zA-Z_0-9]*$" (symbol->string x)))) (define (schema-check-problems schema) (define problems '()) (define (problem! context f . args) (set! problems (cons (format "~a: ~a" (reverse context) (apply format f args)) problems))) (define (check-binding context scope n) (when (hash-has-key? scope n) (problem! context "duplicate binding ~v" n)) (when (not (valid-id? n)) (problem! context "invalid binding name ~v" n)) (hash-set! scope n #t)) (define (check-pattern context scope p value-available?) (match (unwrap p) [(SimplePattern-lit _) (void)] [(? SimplePattern? u) (when (not value-available?) (problem! context "necessary information not captured")) (match u [(SimplePattern-Ref (Ref (ModulePath ids) id)) (when (not (andmap valid-id? (cons id ids))) (problem! context "invalid reference name"))] [_ (void)])] [(CompoundPattern-rec l f) (check-named-pattern (cons "label" context) scope l) (check-named-pattern (cons "fields" context) scope f)] [(CompoundPattern-tuple ps) (for [(p (in-list ps)) (i (in-naturals))] (check-named-pattern (cons i context) scope p))] [(CompoundPattern-tuplePrefix ps v) (for [(p (in-list ps)) (i (in-naturals))] (check-named-pattern (cons i context) scope p)) (check-named-pattern (cons "tail" context) scope v)] [(CompoundPattern-dict (DictionaryEntries entries)) (for [(entry (in-list (sorted-dict-entries entries)))] (check-named-pattern (cons (car entry) context) scope (cdr entry)))])) (define (check-named-pattern context scope p) (match (unwrap p) [(Binding n p) (check-binding context scope n) (check-pattern (cons n context) scope p #t)] [p (check-pattern context scope p #f)])) (for [((name def) (in-hash (Definitions-value (Schema-definitions schema))))] (define context (list name)) (match def [(Definition-or p0 p1 pN) (define alts (list* p0 p1 pN)) (unless (void? (check-duplicates alts #:key NamedAlternative-variantLabel #:default (void))) (problem! context "duplicate variant label")) (for [(a (in-list alts))] (define label (string->symbol (NamedAlternative-variantLabel a))) (when (not (valid-id? label)) (problem! context "invalid variant label ~v" label)) (check-pattern (cons label context) (make-hash) (NamedAlternative-pattern a) #t))] [(Definition-and p0 p1 pN) (define scope (make-hash)) (for [(p (in-list (list* p0 p1 pN)))] (check-named-pattern context scope p))] [(Definition-Pattern p) (check-pattern context (make-hash) p #t)])) (reverse problems)) (define (schema-check-problems! schema #:name [name ""]) (let ((problems (schema-check-problems schema))) (when (not (null? problems)) (error 'schema-check-problems! "Problems checking schema ~a:\n - ~a" name (string-join problems "\n - ")))) schema)