@(name file)
@;;
@;; Check syntax block after function
@;;
@(define check-synb ())
.synb
@  (assert bad ln `bad .synb block`)
@  (repeat :gap 0 :mintimes 1)
.mets @(skip)
@    (maybe)
@      (repeat :gap 0 :mintimes 1)
.mets \ \ @(skip)
@      (last :mandatory)

@      (trailer)
.mets @(skip)
@      (end)
@    (end)
@  (last :mandatory)
.syne
@  (end)
@  (assert bad ln `missing .desc`)
.desc
@  (assert bad ln `blank line after .desc`)
@/..*/
@(end)
@;;
@;; Check variable/operator/function/... description headings
@;; introduced by .coNP
@;;
@(define check-coNP ())
@  (cases)
@;   exception
.coNP @/Variables|Special Variables/ @@, s-ifmt @(skip)
@    (or)
@;   exception
.coNP @/Variables|Special Variables/ @@, *0 @(skip)
@    (or)
.coNP @/Variables|Special Variables/@(assert bad ln `bad Variables heading`)@(rep :gap 0) @@, @{x /\S+/}@(last :mandatory) @@ @y and @@ @{z /\S+/}@(end)
@      (assert bad ln `no .desc after variables heading`)
.desc
@    (or)
.coNP @/Variable|Special Variable/@(assert bad ln `bad Variable heading`) @@ @{x /\S+/}
@      (assert bad ln `no .desc after variable heading`)
.desc
@    (or)
@;   exception
.coNP @/Accessors/ @@, caar @(skip)
@    (or)
.coNP @{type /Operator|Macro/}/function @(skip)
@      (assert bad ln `no .synb after @type/function heading`)
@      (check-synb)
@    (or)
.coNP Operator @@ @op and Macro @@ @mac
@      (assert bad ln `no .synb after Operator and macro heading`)
@      (check-synb)
@    (or)
.coNP @{type /Function|Operator|Macro|Accessor|Method|Structure|Pattern Operator|(Operators|Macros)\/Function/}s@(assert bad ln `bad @{type}s heading`)@(rep :gap 0) @@, @{x /\S+/}@(last :mandatory) @@ @y and @@ @{z /\S+/}@(end)
@      (assert bad ln `no .synb after @{type}s heading`)
@      (check-synb)
@    (or)
.coNP @{type /Function|Operator|Macro|Accessor|Method|Structure|Pattern (Operator|Macro)|Parameter List Macro|(Operator|Macro)\/Function/}@(assert bad ln `bad @type heading`) @@ @{x /\S+/}@junk
@      (assert bad ln `extra elements in singular @type heading`)
@      (bind junk "")
@      (assert bad ln `no .synb after @type heading`)
@      (check-synb)
@    (or)
.coNP @/Argument Generation|Passing Options|The Lisp @|Keyword Param|\
        The @ |Keyword Para|Keywords in|Lisp Forms in|Mandatory @|\
        Specifying Variables in|@ catch Clauses|\
        Interaction Between|Vertical-Horizontal|Horizontal-Horizontal|\
        Nested @|@ repeat an|Conventions Used|Struct Clause Macro|\
        Treatment of|Examples of|FFI type|Differences Due to|\
        Unbound Symbols in|Bound symbols in|File-Wide|\
        Delimited Continuations|Symbol Macro/@nil
@    (or)
.coNP @junk
@      (throw bad ln `unrecognized .coNP arguments: @junk`)
@  (end)
@(end)
@;;
@;; check .code, .codn, .cod2, .cod3, .meta and .metn.
@;;
@(define check-code ())
@  (cases)
.@{type /code|meta/} "@(assert bad ln `.@type needs one argument`)@x"@(eol)
@  (or)
.@{type /code|meta/}@(assert bad ln `.@type needs one argument`) @{x /\S+/}@(eol)
@  (or)
.cod3 @(assert bad ln `.cod3 needs three arguments`)@x @y @{z /\S+/}@(eol)
@  (or)
.@{type /codn|cod2|metn/} @(assert bad ln `.@type needs two arguments`)@(cases)"@x"@(or)@{x /\S+/}@(end) @{y /\S+/}@(eol)
@    (assert bad ln `.codn second argument doesn't begin with punctuation`)
@    (require (or (not (memqual type '("codn" "metn")))
                  (equal "s" y)
                  (and (starts-with "s" y)
                       (chr-ispunct [y 1]))
                  (chr-ispunct [y 0])))
@  (end)
@(end)
@;;
@;; Check .mono/.onom pairing
@;;
@(define check-mono ())
.mono
@  (assert bad ln `.mono not closed`)
@  (repeat :gap 0)
@    (none)
.mono
@    (end)
@  (last :mandatory)
.onom
@  (end)
@(end)
@;;
@;; Check .verb/.brev pairing
@;;
@(define check-verb ())
.verb
@  (assert bad ln `.verb not closed`)
@  (repeat :gap 0)
@    (none)
.verb
@    (end)
@  (last :mandatory)
.brev
@  (end)
@(end)
@;;
@;; Check for various dangling
@;; macros.
@;;
@(define check-spurious ())
@  {mac /.(cble|syne)/}@(skip)
@  (throw bad ln `dangling @mac`)
@(end)
@;;
@;; Check for .meti not wrapped in .mono/.onom macros.
@;;
@(define check-meti ())
.meti @(skip)
@  (throw bad ln ".meti not in .mono")
@(end)
@;;
@;; Check for .IP, coIP or .meIP followed by blank line
@;;
@(define check-ip ())
.@{ip /IP|coIP|meIP/}@(skip)

@  (throw bad ln `.@ip followed by blank line`)
@(end)
@;;
@;; Check for .meIP, .meti or .mets containing spurious spaces.
@;;
@(define check-spaces ())
.@{mac /meIP|meti|mets/}@/(( \\)+  )?/@(skip)  @(skip)
@  (throw bad ln `.@mac contains spurious spaces`)
@(end)
@;;
@;; Main
@;;
@(bind errors 0)
@(repeat)
@  (line ln)
@  (try)
@    (cases)
@      (check-coNP)
@    (or)
@      (check-code)
@    (or)
@      (check-verb)
@    (or)
@      (check-mono)
@    (or)
@      (check-synb)
@    (or)
@      (check-spurious)
@    (or)
@      (check-meti)
@    (or)
@      (check-ip)
@    (end)
@  (catch bad (line msg))
@    (do (inc errors)
         (put-line `@file:@line:@msg`))
@  (end)
@(end)
@(next file)
@(repeat)
@  (line ln)
@  (try)
@    (check-spaces)
@  (catch bad (line msg))
@    (do (inc errors)
         (put-line `@file:@line:@msg`))
@  (end)
@(end)
@(do (exit (zerop errors)))