changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/lib/doc/file.lisp

changeset 431: c40d2a41d7ce
parent: d876b572b5b9
author: Richard Westhaver <ellis@rwest.io>
date: Sun, 09 Jun 2024 02:04:18 -0400
permissions: -rw-r--r--
description: source concatenating std.lisp, more systems, got zstd simple working, IO work, added dat/tar
1 ;;; lib/doc/file.lisp --- File Documentation
2 
3 ;; Support for inline documentation and comments in source-code files.
4 
5 ;;; Commentary:
6 
7 ;; As of [2024-04-04] we aren't considering any other files besides
8 ;; Lisp source-code. We'll eventually open the door to other langs via
9 ;; SYN, and finally integrate with other types of
10 ;; documentation-specific files like READMEs.
11 
12 ;;;; Lisp Files
13 
14 ;; We determine the flavor of Lisp used in a source file by the file
15 ;; extension. In reality we're only dealing with two flavors: Common
16 ;; Lisp and Emacs Lisp.
17 
18 ;; The way we treat them at the read stage is almost identical. The
19 ;; only difference being that Emacs Lisp does not support the inline
20 ;; comment syntax '#| some comment |#'.
21 
22 ;; In any case the idea is to do 'something' with comments instead of
23 ;; getting rid of them at read-time.
24 
25 ;;;; Headers
26 
27 ;; Special consideration is given to source-code 'header' blocks. In
28 ;; our own code, we use them as much as possible but haven't been
29 ;; using them to their full potential just yet.
30 
31 ;; You will find most code, including this file begins with a block of
32 ;; the following form:
33 
34 #|
35  ;;; PATH --- SHORT-DESCRIPTION
36 
37  ;; LONG-DESCRIPTION
38 
39  ;;; Commentary:
40 
41  ;; COMMENTARY
42 
43  ;;; Code:
44  CODE
45 |#
46 
47 ;; Note the difference in comment characters used between the
48 ;; lines. Headings start with 3, and the contents of those Headings
49 ;; start with 2. The first heading/section is an 'anonymous' or 'meta'
50 ;; section that should be considered required. All headings beneath it
51 ;; are 'named' sections. 'Code:' is the only required named section,
52 ;; so in the example above, we may exclude the 'Commentary:' section.
53 
54 ;;;;; Headings
55 
56 ;; We define headings according to the Emacs notion of the term, as
57 ;; used in outline-mode and org-mode. As mentioned, headings in source
58 ;; files begin with a minimum of 3 comment characters. For each
59 ;; additional comment character, the nested 'level' of the heading is
60 ;; increased and any non-header elements or header elements with a
61 ;; level greater than the top-level are nested inside that heading.
62 
63 ;; 3 comment headings represent a level of 0. Any heading with a level
64 ;; > 0 is a Subheading. For example, we are in a subheading named
65 ;; 'Headings' of level 2, inside a subheading of level 1, inside a
66 ;; heading named 'Commentary'.
67 
68 ;;; Notes:
69 
70 ;; NOTE 2024-04-05: sb-impl::read-comment takes an 'ignore' second
71 ;; arg, but the return value is always ignored anyways?
72 
73 ;;; Code:
74 (in-package :doc)
75 
76 ;; asdf:source-file-type asdf:source-file-explicit-type
77 (defvar *source-file-types* nil)
78 
79 (defmacro define-source-file* (type ext &optional opts shebangp &body body)
80  (with-gensyms (f)
81  (let ((rname (symbolicate "READ-" type "-SOURCE-FILE"))
82  (wname (symbolicate "WRITE-" type "-SOURCE-FILE"))
83  #+nil (kw (sb-int:keywordicate type)))
84  `(progn
85  (pushnew ',type *source-file-types*)
86  (defun ,rname (path)
87  (with-open-file (,f path)
88  (read ,f)))
89  (defun ,wname (source path)
90  (with-open-file (,f path)
91  (write source :stream ,f)))))))
92 
93 (define-source-file* rust "rs")
94 (define-source-file* shell "sh")
95 (define-source-file* makefile "mk")
96 (define-source-file* nushell "nu")
97 (define-source-file* common-lisp "lisp")
98 (define-source-file* emacs-lisp "el")
99 (define-source-file* scheme "scm")
100 (define-source-file* skel "sk")
101 (define-source-file* sxp "sxp")
102 
103 (defconstant +max-file-heading-level+ 8)
104 (defconstant +min-file-heading-level+ 3)
105 
106 (defclass file-heading ()
107  ((name :initarg :name :type string)
108  (level :initform 0 :initarg :level :type (integer 0 #.+max-file-heading-level+))
109  (contents :initarg :contents :type string)))
110 
111 (defun heading-line-p (string)
112  (uiop:string-prefix-p ";;;" string))
113 
114 (defun read-comment-line (stream)
115  "Read a comment line from STREAM. Returns two values: the uncommented
116 string and a 'level' indicating how many comment characters were
117 stripped. Note that this level is NOT the same as the heading level."
118  (let ((level 0) (contents (organ::read-line stream)))
119  (loop for c = (char contents level)
120  until (not (char= c #\;))
121  do (incf level))
122  (values
123  (if (zerop level) contents (subseq contents (1+ level)))
124  level)))
125 
126 (defun read-file-heading (stream)
127  (destructuring-bind (name level) (read-comment-line stream)
128  (make-instance 'file-heading :name name :level level :contents "")))
129 
130 (defclass file-headline (file-heading)
131  ((summary :initarg :summary :type string)
132  (opts :initform nil :initarg :opts :type list)))
133 
134 (defun read-file-headline-description (stream)
135  "Read a headline description returning a string. Second value is the
136 next heading line found or nil if EOF."
137  (apply #'concatenate 'string
138  (loop for l = (read-line stream)
139  until (heading-line-p l)
140  collect l)))
141 
142 (defun headline-values-p (string)
143  (let ((found (search " --- " string)))
144  (values (subseq string 0 found) (when found (subseq string (+ found 5))))))
145 
146 (defun split-headline-values (string)
147  "Split the headline in STRING into individual values."
148  (multiple-value-bind (name rest) (headline-values-p string)
149  (if rest
150  (multiple-value-bind (summary opts) (headline-values-p rest)
151  (values name summary opts))
152  (values name nil nil))))
153 
154 (defun read-file-headline (stream)
155  (let ((line (read-comment-line stream))) ;; throw out second value
156  (multiple-value-bind (name summary opts) (split-headline-values line)
157  (make-instance 'file-headline
158  :name name
159  :summary summary
160  :opts opts
161  :level 0
162  :contents (read-file-headline-description stream)))))
163 
164 (defclass file-header ()
165  ((headline :initarg :headline :type file-headline)
166  (headings :initarg :headings :type (array file-heading)))
167  (:documentation "A source-file header object containing a FILE-HEADLINE and array of
168 optional top-level FILE-HEADINGs."))
169 
170 (defun read-file-header (path)
171  "Read a FILE-HEADER from PATH which should be an INPUT-FILE-STREAM.
172 
173 File headers always appear at the very start of a file so the stream
174 position is always assumed to be 0."
175  (with-open-file (f path :if-does-not-exist :error)
176  (let ((hl (read-file-headline f)))
177  (loop for l = (read-line f nil)
178  while l
179  until (uiop:string-prefix-p ";;; Code:" l)
180  collect l into contents)
181  (make-instance 'file-header
182  :headline hl
183  :headings #()
184  ))))
185 
186 ;; (defmacro define-file-heading (type slots))
187 
188 (defclass file-documentation ()
189  ((path :initarg :path :type pathname :accessor doc-path)
190  (header :initarg :header :type file-header)
191  (contents :initarg :contents :type sequence)
192  (locations :initarg :locations :type sequence))
193  (:documentation "An object containing the header, contents, and relevant
194  locations of a source file. This object should be the result of a
195  function like COMPILE-FILE-DOCUMENTATION. Note that this object only
196  contains inline comments. Symbol documentation such as this one will
197  not be captured in instances of this object."))
198 
199 (defmethod print-object ((self file-documentation) stream)
200  (print-unreadable-object (self stream :type t)
201  (format stream "~A" (doc-path self))))
202 
203 (defun file-documentation (path)
204  "Return the FILE-DOCUMENTATION for PATH."
205  (make-instance 'file-documentation
206  :path path
207  :header (read-file-header path)))
208 
209 ;; asdf:source-file