rename upstream-man-pages to upstream-doc
[clinton/guile-figl.git] / figl / parse.scm
index 7322137..558619a 100644 (file)
   #:use-module (sxml simple)
   #:use-module ((sxml xpath) #:hide (filter))
   #:use-module (sxml transform)
-  #:use-module ((srfi srfi-1) #:select (filter))
+  #:use-module (sxml fold)
+  #:use-module ((srfi srfi-1) #:select (filter fold append-map filter-map))
+  #:use-module (srfi srfi-9)
   #:use-module (texinfo docbook)
   #:use-module (ice-9 ftw)
-  #:export ())
+  #:use-module (ice-9 match)
+  #:export (gl-definition?
+            make-gl-definition
+            gl-definition-name
+            gl-definition-prototypes
+            gl-definition-documentation
+            gl-definition-copyright
+            parse-gl-definitions))
 
-(define *man-sections*
-  '("man2" "man3" "man4"))
+(define-record-type gl-definition
+  (make-gl-definition name prototypes documentation copyright)
+  gl-definition?
+  (name gl-definition-name)
+  (prototypes gl-definition-prototypes)
+  (documentation gl-definition-documentation)
+  (copyright gl-definition-copyright))
 
 (define *namespaces*
   '((mml . "http://www.w3.org/1998/Math/MathML")))
                                      (trim-whitespace text)
                                      text))))))
 
-(define (parse-man-xml section filename)
-  (call-with-input-file (in-vicinity (upstream-man-pages)
-                                     (in-vicinity section filename))
+(define (parse-man-xml version filename)
+  (define subdir (format #f "man~A" version))
+  (call-with-input-file (in-vicinity (upstream-doc)
+                                     (in-vicinity subdir filename))
     (lambda (port)
       (zap-whitespace
        (xml->sxml port #:declare-namespaces? #t
                   #:default-entity-handler default-entity-handler
                   #:doctype-handler docbook-with-mathml-handler)))))
 
-(define (xml-files section)
-  (scandir (in-vicinity (upstream-man-pages) section)
+(define (xml-files version)
+  (define subdir (format #f "man~A" version))
+  (scandir (in-vicinity (upstream-doc) subdir)
            (lambda (x) (string-suffix? ".xml" x))))
 
 (define (take-first proc)
 (define xml-purpose
   (take-first (sxpath '(refentry refnamediv refpurpose *text*))))
 
-(define xml-prototype
-  (take-first (sxpath '(refentry refsynopsisdiv funcsynopsis))))
+(define xml-funcprototypes
+  (sxpath '(refentry refsynopsisdiv funcsynopsis funcprototype)))
 
 (define xml-parameters
   (take-first (sxpath '(refentry (refsect1 (@ id (equal? "parameters")))))))
 (define xml-errors
   (take-first (sxpath '(refentry (refsect1 (@ id (equal? "errors")))))))
 
-(define (parse-prototype xml)
-  xml)
+(define xml-copyright
+  (take-first (sxpath '(refentry (refsect1 (@ id (equal? "Copyright")))))))
+
+(define (string->gl-type str)
+  (string->symbol
+   (string-join (string-split (string-trim-both str) #\space) "-")))
+
+(define (parse-prototypes sxml)
+  (define all-names
+    (match sxml
+      ((('funcprototype ('funcdef return-type ('function names))
+                        . _)
+        ...)
+       names)))
+
+  (define (redundant-variant? s shun-suffix prefer-suffix)
+    (and (string-suffix? shun-suffix s)
+         (member (string-append (substring s 0 (- (string-length s)
+                                                  (string-length shun-suffix)))
+                                prefer-suffix)
+                 all-names)))
+
+  (define (skip? s)
+    (or
+     ;; Skip double variants if we have a float variant.
+     ;; (http://www.opengl.org/wiki/Common_Mistakes#GL_DOUBLE).
+     (redundant-variant? s "d" "f")
+
+     ;; Skip byte variants if there is a short variant.
+     (redundant-variant? s "b" "s")
+
+     ;; Skip short variants if there is an int variant.
+     (redundant-variant? s "s" "i")
+
+     ;; Skip packed setters like glVertex3fv if e.g. glVertex3f exists.
+     (redundant-variant? s "v" "")
+     (redundant-variant? s "dv" "fv")
+     (redundant-variant? s "bv" "sv")
+     (redundant-variant? s "sv" "iv")))
+
+  (filter-map
+   (lambda (sxml)
+     (match sxml
+       (('funcprototype ('funcdef return-type ('function (? skip?)))
+                        . _)
+        #f)
+       (('funcprototype ('funcdef return-type ('function name))
+                        ('paramdef ('parameter "void")))
+        `(,(string->symbol name)
+          -> ,(string->gl-type return-type)))
+       (('funcprototype ('funcdef return-type ('function name))
+                        ('paramdef ptype ('parameter pname))
+                        ...)
+        `(,(string->symbol name)
+          ,@(map (lambda (pname ptype)
+                   (list (string->symbol pname)
+                         (string->gl-type ptype)))
+                 pname ptype)
+          -> ,(string->gl-type return-type)))))
+   sxml))
+
+(define (collapse-fragments nodeset)
+  (match nodeset
+    ((('*fragment* elts ...) nodes ...)
+     (append (collapse-fragments elts)
+             (collapse-fragments nodes)))
+    ((((and tag (? symbol?)) elts ...) nodes ...)
+     (acons tag (collapse-fragments elts) (collapse-fragments nodes)))
+    ((elt nodes ...)
+     (cons elt (collapse-fragments nodes)))
+    (() '())))
+
+(define (list-intersperse src-l elem)
+  (if (null? src-l) src-l
+      (let loop ((l (cdr src-l)) (dest (cons (car src-l) '())))
+        (if (null? l) (reverse dest)
+            (loop (cdr l) (cons (car l) (cons elem dest)))))))
+
+(define (lift-tables sdocbook)
+  ;; Like sdocbook-flatten, but tweaked to lift tables from inside
+  ;; paras, but not paras from inside tables.  Pretty hacky stuff.
+  (define *sdocbook-block-commands*
+    '(informaltable programlisting variablelist))
+
+  (define (inline-command? command)
+    (not (memq command *sdocbook-block-commands*)))
+
+  (define (fhere str accum block cont)
+    (values (cons str accum)
+            block
+            cont))
+  (define (fdown node accum block cont)
+    (match node
+      ((command (and attrs ('% . _)) body ...)
+       (values body '() '()
+               (lambda (accum block)
+                 (values
+                  `(,command ,attrs ,@(reverse accum))
+                  block))))
+      ((command body ...)
+       (values body '() '()
+               (lambda (accum block)
+                 (values
+                  `(,command ,@(reverse accum))
+                  block))))))
+  (define (fup node paccum pblock pcont kaccum kblock kcont)
+    (call-with-values (lambda () (kcont kaccum kblock))
+      (lambda (ret block)
+        (if (inline-command? (car ret))
+            (values (cons ret paccum) (append kblock pblock) pcont)
+            (values paccum (append kblock (cons ret pblock)) pcont)))))
+  (call-with-values
+      (lambda () (foldts*-values fdown fup fhere sdocbook '() '() #f))
+    (lambda (accum block cont)
+      (append (reverse accum)
+              (reverse block)
+              ))))
 
 (define *rules*
   `((refsect1
+     *preorder*
      . ,(lambda (tag id . body)
-          body))
-    (title
-     . ,(lambda (tag body)
-          `(heading ,body)))
+          (append-map (lambda (nodeset)
+                        (map
+                         (lambda (x)
+                           (pre-post-order x *rules*))
+                         nodeset))
+                      (map lift-tables
+                           (match body
+                             ((('title _) body ...) body)
+                             (_ body))))))
     (variablelist
      ((varlistentry
        . ,(lambda (tag term . body)
-            `(entry (% (heading ,@(cdr term))) ,@body)))
+            `(entry (% (heading ,@(cdar term))) ,@(apply append body))))
       (listitem
+       . ,(lambda (tag . body)
+            (map (lambda (x)
+                   (if (string? x)
+                       `(para ,x)
+                       x))
+                 body)))
+      (term
        . ,(lambda (tag . rest)
-            (cond ((null? rest)
-                   (warn "null listitem")
-                   '(*fragment*))
-                  ((pair? (car rest))
-                   (if (not (null? (cdr rest)))
-                       (warn "ignoring listitem extra contents:" (cddr rest)))
-                   (car rest))
-                  (else
-                   (list 'para rest))))))
+            `((itemx ,@rest)))))
      . ,(lambda (tag . body)
-          `(table (% (formatter (var))) ,@body)))
-    (term
-     . ,(lambda (tag param . rest)
-          (if (pair? param)
-              param
-              (list 'var param))))
+          `(table (% (formatter (asis))) ,@body)))
+    (trademark
+     . ,(match-lambda*
+         ((_ ('@ ('class "copyright"))) '(copyright))))
     (parameter
      . ,(lambda (tag body)
           `(var ,body)))
     (constant
      . ,(lambda (tag . body)
           `(code . ,body)))
+    (code
+     . ,(lambda (tag . body)
+          `(code . ,body)))
     (function
      . ,(lambda (tag body . ignored)
           (or (null? ignored) (warn "ignored function tail" ignored))
           `(code ,body)))
     (emphasis
-     . ,(lambda (tag . body)
-          `(var . ,body)))
+     . ,(match-lambda*
+         ((_) "")
+         ((_ ('@ ('role "bold")) (and body (? string?)))
+          `(strong ,(string-trim-both body)))
+         ((_ ('@ ('role "bold")) . body) `(strong ,@body))
+         ((_ body) `(var ,body))))
+    (citerefentry
+     . ,(lambda (tag contents)
+          contents))
+    (refentrytitle
+     . ,(lambda (tag contents)
+          `(code ,contents)))
+    (inlineequation
+     . ,(lambda (tag contents)
+          contents))
+    (informalequation
+     . ,(lambda (tag contents)
+          contents))
+    (informaltable
+     . ,(lambda (tag attrs tgroup)
+          tgroup))
+    (tgroup
+     ((thead
+       . ,(lambda (tag . rows)
+            rows))
+      (colspec
+       . ,(lambda _
+            #f))
+      (tbody
+       . ,(lambda (tag . rows)
+            rows))
+      (row
+       . ,(lambda (tag first . rest)
+            `(entry (% (heading ,@first))
+                    (para ,@(apply
+                             append
+                             (list-intersperse rest '(", ")))))))
+      (entry
+       . ,(match-lambda*
+           ((_) '())
+           ((_ ('@ . _)) '())
+           ((_ ('@ . _) x ...) x)
+           ((_ x ...) x))))
+     . ,(lambda (tag attrs . contents)
+          `(table (% (formatter (asis)))
+                  ,@(apply append (filter identity contents)))))
+
+    ;; Poor man's mathml.
+    (mml:math
+     . ,(lambda (tag . contents)
+          `(r . ,(collapse-fragments contents))))
+    (mml:mn
+     . ,(lambda (tag n . rest)
+          (if (pair? rest)
+              `(*fragment* ,n . ,rest)
+              n)))
+    (mml:mi
+     . ,(case-lambda
+          ((tag contents)
+           `(code ,contents))
+          ((tag attrs contents)
+           (match attrs
+             (('@ (mathvariant "italic"))
+              `(var ,contents))
+             (_ `(code ,contents))))))
+    ;; It would be possible to represent a matrix as a @multitable, but
+    ;; Guile doesn't really have support for that.  So instead print
+    ;; each row in parentheses.
+    (mml:mtable
+     ((mml:mtr
+       . ,(lambda (tag . body)
+            `("(" ,@(list-intersperse body " ") ")")))
+      (mml:mtd
+       . ,(match-lambda*
+           ((tag ('@ . _) body ...)
+            `(*fragment* ,@body))
+           ((tag body ...)
+            `(*fragment* ,@body)))))
+     . ,(lambda (tag . rows)
+          ;; Rely on outer mfence for outer parens, if any
+          (let ((rows (if (and (pair? rows) (eq? (caar rows) '@))
+                          (cdr rows)
+                          rows)))
+            `(*fragment* ,@(apply append (list-intersperse rows '(", ")))))))
+    (mml:mspace
+     . ,(lambda (tag . _)
+          " "))
+    (mml:msup
+     . ,(lambda (tag base exponent)
+          `(*fragment* ,base "^" ,exponent)))
+    (mml:msub
+     . ,(lambda (tag base exponent)
+          `(*fragment* ,base "_" ,exponent)))
+    (mml:mover
+     . ,(lambda (tag base over)
+          `(*fragment* ,base ,over)))
+    (mml:munderover
+     . ,(lambda (tag under base over)
+          `(*fragment* ,under ,base ,over)))
+    (mml:mfrac
+     . ,(lambda (tag num denom)
+          `(*fragment* ,num "/" ,denom)))
+    (mml:msqrt
+     . ,(lambda (tag base)
+          `(*fragment* "√" ,base)))
+    (mml:infinity
+     . ,(lambda (tag)
+          "∞"))
+    (mml:mo
+     . ,(lambda (tag operator)
+          operator))
+    (mml:mrow
+     . ,(lambda (tag . contents)
+          `(*fragment* . ,contents)))
+    (mml:mfenced
+     . ,(lambda (tag attrs left . right)
+          `(*fragment* ,@(assq-ref attrs 'open)
+                       ,left
+                       ","
+                       ,@right
+                       ,@(assq-ref attrs 'close))))
     (*text*
      . ,(lambda (tag text)
           text))
 ;; Produces an stexinfo fragment.
 (define (generate-documentation purpose parameters description errors)
   `(*fragment*
-    (heading ,purpose)
-    ,@(sdocbook->stexi parameters)
-    ,@(sdocbook->stexi description)
-    ,@(sdocbook->stexi errors)))
+    (para ,(string-append (string (char-upcase (string-ref purpose 0)))
+                          (substring purpose 1)
+                          "."))
+    ,@(if parameters (sdocbook->stexi parameters) '())
+    ,@(if description (sdocbook->stexi description) '())
+    ,@(if errors (sdocbook->stexi errors) '())))
 
 (define (xml->definition xml)
-  `((name . ,(xml-name xml))
-    (prototype . ,(parse-prototype (xml-prototype xml)))
-    (documentation . ,(generate-documentation (xml-purpose xml)
-                                              (xml-parameters xml)
-                                              (xml-description xml)
-                                              (xml-errors xml)))))
+  (let ((prototypes (parse-prototypes (xml-funcprototypes xml))))
+    (and (pair? prototypes)
+         (make-gl-definition (xml-name xml)
+                             prototypes
+                             (generate-documentation (xml-purpose xml)
+                                                     (xml-parameters xml)
+                                                     (xml-description xml)
+                                                     (xml-errors xml))
+                             (and=> (xml-copyright xml)
+                                    (lambda (c)
+                                      `(*fragment* ,@(sdocbook->stexi c))))))))
+
+(define (parse-gl-definitions version)
+  (filter-map (lambda (file)
+                (xml->definition (parse-man-xml version file)))
+              (xml-files version)))