-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathphpinspect-parse-context.el
209 lines (180 loc) · 7.53 KB
/
phpinspect-parse-context.el
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
;;; phpinspect-parse-context.el --- PHP parsing context module -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2023 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <[email protected]>
;; Keywords: php, languages, tools, convenience
;; Version: 2.1.0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; A parse context is an object which is used during the lifetime of a parse
;; cycle. Several variables can be set through the parse context to influence
;; the behavior of the parser.
;; See also M-x cl-describe-type RET phpinspect-pctx for a more readable
;; overview of the slot documentation.
;;; Code:
(require 'phpinspect-util)
(require 'phpinspect-meta)
(require 'phpinspect-changeset)
(defvar phpinspect-parse-context nil
"An instance of `phpinspect-pctx' that is used when
parsing. Usually used in combination with
`phpinspect-with-parse-context'")
(cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
"Parser Context"
(incremental
nil
:type boolean
:documentation
"A non-nil value enables incremental parsing.")
(interrupt-threshold
(time-convert '(0 0 2000 0) t)
:documentation
"After how much time `interrupt-predicate' should be consulted.
This is 2ms by default.")
(-start-time
nil
:documentation "The time at which the currently active parse cycle started.
This slot is for private use and does not always have a value.")
(interrupt-predicate
nil
:documentation
"When non-nil, this should be a function. When the parse time
exceeds the configured interrupt-threshold, this function will be
called after each parsed token to make the final decision of
interrupting the parser. If this function returns a non-nil
value, the parse process is interrupted and the symbol
`phpinspect-parse-interrupted' is signaled.")
(changesets
nil
:type list
:documentation
"Restore points for metadata changes executed during this
parse. Usually populated through `phpinspect-meta-with-changeset'.")
(edtrack
nil
:type phpinspect-edtrack
:documentation
"When parsing incrementally, the edit tracker is used to determine
whether a token from a previous parse (in the buffer map that is
in the `previous-bmap' slot) can be recycled or is tainted/edited
and should not be recycled.")
(bmap
nil
:type phpinspect-bmap
:documentation
"The new buffer map to register metadata objects with.")
(previous-bmap
nil
:type phpinspect-bmap
:documentation
"If set, this should be a buffer map containing the metadata
gathered during the previous parse cycle of the
buffer-to-be-parsed. Eligible tokens will be removed from the old
metadata tree and recycled in the new buffer map (in the `bmap'
slot of this structure.)")
(whitespace-before
""
:type string
:documentation
"A slot that is used by the parser to store whitespace which is
encountered before each parsed token. Whitespace is not parsed as
a regular token to avoid pollution of the syntax tree with
useless metadata tokens."))
(define-inline phpinspect-pctx-whitespace-before-length (ctx)
(inline-quote (length (phpinspect-pctx-whitespace-before ,ctx))))
(defmacro phpinspect-with-parse-context (ctx &rest body)
"Set the currently active parce context to CTX and execute body.
If BODY signals an error, `phpinspect-pctx-cancel' is called in
an attempt to revert all changes made to the metadata tree while
parsing incrementally.
The error signal is not intercepted and will still need to be
handled by the code using this macro."
(declare (indent 1))
(let ((completed (gensym))
(result (gensym)))
`(dlet ((phpinspect-parse-context ,ctx))
(let ((,result)
(,completed))
(unwind-protect
(progn
(setq phpinspect-parse-context ,ctx
,result (progn ,@body)
,completed t)
,result)
(progn
(unless ,completed (phpinspect-pctx-cancel ,ctx))))))))
(defmacro phpinspect-pctx-save-whitespace (pctx &rest body)
(declare (indent 1))
(let ((save-sym (gensym)))
`(let ((,save-sym (phpinspect-pctx-whitespace-before ,pctx)))
(unwind-protect
(progn
(setf (phpinspect-pctx-whitespace-before ,pctx) "")
,@body)
(setf (phpinspect-pctx-whitespace-before ,pctx) ,save-sym)))))
(define-inline phpinspect-pctx-register-changeset (pctx changeset)
(inline-quote
(progn
(push ,changeset (phpinspect-pctx-changesets ,pctx)))))
(define-inline phpinspect-meta-with-changeset (meta &rest body)
"Perform mutations on META in BODY, saving changes.
Before BODY is executed, important slots of META are stored in a
changeset object and appended to the changesets slot of the
currently active parse context. The original state of META can be
restored by calling `phpinspect-pctx-cancel'."
(declare (indent 1))
(inline-letevals (meta)
(push 'progn body)
(inline-quote
(progn
(when phpinspect-parse-context
(phpinspect-pctx-register-changeset
phpinspect-parse-context (phpinspect-make-changeset ,meta)))
,body))))
(define-inline phpinspect-pctx-check-interrupt (pctx)
"Signal `phpinspect-parse-interrupted' when conditions are met.
Parsing will be interrupted when the time passed since
`phpinspect--pctx-start-time' exceeds
`phpinspect-pctx-interrupt-threshold' and
`phpinspect-pctx-interrupt-predicate' returns non-nil.
When parsing is interrupted, any changes made to buffer token
metadata will be reverted in a call to `pphinspect-pctx-cancel'."
(inline-letevals (pctx)
(inline-quote
(progn
(unless (phpinspect-pctx--start-time ,pctx)
(setf (phpinspect-pctx--start-time ,pctx) (time-convert nil t)))
;; Interrupt when blocking too long while input is pending.
(when (and (time-less-p (phpinspect-pctx-interrupt-threshold ,pctx)
(time-since (phpinspect-pctx--start-time ,pctx)))
(funcall (phpinspect-pctx-interrupt-predicate ,pctx)))
(phpinspect-pctx-cancel ,pctx)
(throw 'phpinspect-parse-interrupted nil))))))
(define-inline phpinspect-pctx-register-whitespace (pctx whitespace)
(inline-letevals (pctx)
(inline-quote
(setf (phpinspect-pctx-whitespace-before ,pctx) ,whitespace))))
(defsubst phpinspect-pctx-consume-whitespace (pctx)
(let ((whitespace (phpinspect-pctx-whitespace-before pctx)))
(setf (phpinspect-pctx-whitespace-before pctx) "")
whitespace))
(defun phpinspect-pctx-cancel (pctx)
"Cancel PCTX, revert all changes made during its lifetime.
Revert all changes made to the metadata tree while parsing
incrementally. This function is usually called by
`phpinspect-pctx-check-interrupt' when interrupt conditions are
met."
(phpinspect--log "Cancelling parse context")
(dolist (changeset (phpinspect-pctx-changesets pctx))
(phpinspect-changeset-revert changeset))
(setf (phpinspect-pctx-changesets pctx) nil))
(provide 'phpinspect-parse-context)
;;; phpinspect-parse-context.el ends here