-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathparser.lisp
214 lines (178 loc) · 8.09 KB
/
parser.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
;;; This file is automatically generated from file `literate-lisp.org'.
;;; Please read file `literate-lisp.org' to find out the usage and implementation detail of this source file.
(in-package #:literate-lisp)
(defvar current-org-context (make-hash-table))
(defun org-context (name)
(gethash name current-org-context nil))
(defun set-org-context (name new-value)
(setf (gethash name current-org-context) new-value))
(defsetf org-context set-org-context)
(defmacro define-lexer (name regex-pattern parameters &rest body)
(let ((fun-name (intern (format nil "ORG-LEXER-FOR-~a" name))))
`(progn (defun ,fun-name ,parameters
,@body)
(if (assoc ',name (get 'lexer 'patterns))
(setf (cdr (assoc ',name (get 'lexer 'patterns)))
(list ',fun-name ,regex-pattern ,(length parameters)))
(setf (get 'lexer 'patterns)
(nconc (get 'lexer 'patterns)
(list (list ',name ',fun-name ,regex-pattern ,(length parameters)))))))))
(defun run-patterns (line)
(iter (for (name fun-name regex-pattern parameters-count) in (get 'lexer 'patterns))
(multiple-value-bind (match-start match-end reg-starts reg-ends)
(scan regex-pattern line)
(declare (ignore match-end))
(when match-start
(iter (with arguments = nil)
(for i from 0 below parameters-count)
(for start-index = (aref reg-starts i))
(setf arguments
(nconc arguments
(list (if start-index
(subseq line start-index (aref reg-ends i))
nil))))
(finally
(when debug-literate-lisp-p
(format t "apply pattern ~a with arguments ~a~%" name arguments))
(apply fun-name arguments)))
(finish)))))
(defstruct headline
;; the level
(level 0 :type integer)
;; the content
(content "" :type string)
;; the property specified for this headline
(properties (make-hash-table :test #'equalp) :type hash-table))
(defun org-headlines ()
(org-context :headline))
(defun set-org-headlines (new-value)
(setf (org-context :headline) new-value))
(defsetf org-headlines set-org-headlines)
(defun current-headline ()
(first (org-headlines)))
(defun current-headline-level ()
(headline-level (first (org-headlines))))
(defun current-headline-content ()
(headline-content (first (org-headlines))))
(defun pop-org-headline ()
;; any properties in old headline may change.
(let ((old-headline (pop (org-headlines))))
(iter (for (key) in-hashtable (headline-properties old-headline))
(notify-property-value key))
old-headline))
(defun push-org-headline (level content)
(push (make-headline :level level :content content) (org-headlines)))
(defun setup-headline ()
(push-org-headline 0 ""))
(define-lexer :headline "^\\s*(\\*+)\\s+(.*)$"
(indicators content)
(let ((level (length indicators))
(previous-level (current-headline-level)))
(cond ((= previous-level level)
;; meet a new headline with same level, pop the old one and push the new one
(pop-org-headline)
(push-org-headline level content))
((> previous-level level)
;; meet a new headline with lower level, pop the old one until meet the same level.
(iter (pop-org-headline)
(until (< (current-headline-level) level)))
(push-org-headline level content))
(t
;; meet a new headline with higher level.
(push-org-headline level content)))
(when debug-literate-lisp-p
(format t "current headline, level:~D, content:~a~%"
(current-headline-level)
(current-headline-content)))))
(defmacro define-org-property-value-notifier (name value-name &rest body)
(let ((fun-name (intern (format nil "ORG-PROPERTY-VALUE-NOTIFIER-FOR-~a" name))))
`(progn (defun ,fun-name (,value-name)
,@body)
(if (assoc ',name (get 'org-property-value 'notifier) :test #'string=)
(setf (cdr (assoc ',name (get 'org-property-value 'notifier) :test #'string=))
(list ',fun-name))
(setf (get 'org-property-value 'notifier)
(nconc (get 'org-property-value 'notifier)
(list (list ,name ',fun-name))))))))
(defun notify-property-value (name &optional new-value)
(let ((hook (assoc name (get 'org-property-value 'notifier) :test #'string=)))
(when hook
(when debug-literate-lisp-p
(format t "Notify new property value ~a:~a~%" name new-value))
(funcall (second hook) (or new-value (org-property-value name))))))
(defun property-for-headline (headline key)
(gethash key (headline-properties headline)))
(defun update-property-value (key value)
(setf (gethash key (headline-properties (current-headline))) value)
(notify-property-value key value))
(define-lexer :property-in-a-line "^\\s*\\#\\+PROPERTY:\\s*(\\S+)\\s+(.*)$"
(key value)
(when debug-literate-lisp-p
(format t "Found property in level ~D, ~a:~a.~%"
(current-headline-level) key value))
(update-property-value key value))
(define-lexer :begin-of-properties "^(\\s*:PROPERTIES:\\s*)$"
(line)
(declare (ignore line))
(when debug-literate-lisp-p
(format t "Found beginning of properties.~%"))
(setf (org-context :in-properties) t))
(define-lexer :end-of-properties "(^\\s*:END:\\s*$)"
(line)
(declare (ignore line))
(when (org-context :in-properties)
(when debug-literate-lisp-p
(format t "Found end of properties.~%"))
(setf (org-context :in-properties) nil)))
(define-lexer :property-in-properties "^\\s*:(\\S+):\\s*(\\S+.*)$"
(key value)
(when (org-context :in-properties)
(when debug-literate-lisp-p
(format t "Found property in level ~D, ~a:~a.~%"
(current-headline-level) key value))
(update-property-value key value)))
(defun org-property-value (key)
(iter (for headline in (org-headlines))
(for value = (property-for-headline headline key))
(if value
(return value))))
(defvar *tangle-org-file* nil)
(defun tangle-p ()
*tangle-org-file*)
(defvar *tangle-head-lines* nil)
(defvar *tangle-streams* (make-hash-table :test #'equal))
(defun path-for-literate-name (name)
(cl-fad:merge-pathnames-as-file *tangle-org-file* name))
(defvar *check-outside-modification-p* nil)
(defun tangle-stream (name)
(or (gethash name *tangle-streams*)
(let ((output-file (path-for-literate-name name)))
(when (and *check-outside-modification-p*
(tangled-file-update-outside-p output-file))
(restart-case
(error "The output file ~a has been updated outside, please merge it into your org file before tangling!" output-file)
(override ()
:report (lambda (stream)
(format stream "Override the file with name '~a'!" (pathname-name output-file))))))
(let ((stream (open output-file
:direction :output
:element-type uiop:*default-stream-element-type*
:external-format uiop:*default-encoding*
:if-does-not-exist :create
:if-exists :supersede)))
(when *tangle-head-lines*
(write-string *tangle-head-lines* stream))
(let ((package (org-property-value "LITERATE_EXPORT_PACKAGE")))
(when package
(format stream "(in-package #:~a)~%~%" package)))
(setf (gethash name *tangle-streams*) stream)))))
(defun cleanup-tangle-streams ()
(iter (for (name stream) in-hashtable *tangle-streams*)
(close stream)
(cache-tangled-file (path-for-literate-name name)))
(clrhash *tangle-streams*))
(defvar *current-tangle-stream* nil)
(define-org-property-value-notifier "LITERATE_EXPORT_NAME" name
(when (and (tangle-p) name)
(setf *current-tangle-stream*
(tangle-stream name))))