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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
|
;;; phpinspect-bmap.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021-2025 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 3.0.1
;; 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:
;; This file contains code for the mapping of tokens to metadata objects and
;; vice versa. See also phpinspect-meta.el.
;;
;; The code in this file is heavily used in phpinspect-parser.el for incremental
;; parsing.
;;; Code:
(require 'phpinspect-splayt)
(require 'phpinspect-meta)
(require 'phpinspect-parse-context)
(require 'phpinspect-util)
(require 'compat)
(require 'phpinspect-token-predicates)
(defvar phpinspect-bmap-map-token-metadata nil
"Set to non-nil value to make bmap save metadata in
`phpinspect-token-references'.
When this variable is non-nil, `phpinspect-bmap-register' will
store references from tokens to their metadata objects in
`phpinspect-token-references'.
These references can be fetched via `phpinspect-bmap-token-meta'.
Useful for testing or debugging.")
(defvar phpinspect-token-references
(make-hash-table :test #'eq :size 1000 :rehash-size 2 :weakness 'value)
"A hash-table which allows lookups of token metadata objects by their tokens.")
(eval-when-compile
(require 'cl-macs)
(defvar phpinspect-parse-context nil
"dummy for compilation")
(phpinspect--declare-log-group 'bmap))
(cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap))
"A bmap, short for buffer-map, is a structure whose purpose is to
map parsed tokens to metadata about them and vice versa."
(meta (make-hash-table :test #'eq
:size (floor (/ (point-max) 2))
:rehash-size 1.5)
:documentation "A hash-table containing all newly registered tokens.")
(token-stack nil
:type list)
(-root-meta nil
:type phpinspect-meta)
(last-meta nil :type phpinspect-meta)
(last-token-start nil
:type integer)
(recycled-p nil
:type boolean
:documentation "Whether bmap contains recycled tokens")
(pos-filter nil
:type bool-vector))
(define-inline phpinspect-bmap-root-meta (bmap)
(inline-letevals (bmap)
(inline-quote
(with-memoization (phpinspect-bmap--root-meta ,bmap)
(when-let ((last-meta (phpinspect-bmap-last-meta ,bmap)))
(if (phpinspect-root-p (phpinspect-meta-token last-meta))
last-meta
(phpinspect-meta-find-parent-matching-token last-meta #'phpinspect-root-p)))))))
(defsubst phpinspect-bmap-register (bmap start end token &optional whitespace-before recycle)
"Register TOKEN with START and END region as queryable metadata in BMAP.
If RECYCLE is non-nil, it is assumed to be a `phpinspect-meta'
object and re-used instead of instantiating a new object."
(let* ((meta (phpinspect-bmap-meta bmap))
(last-token-start (phpinspect-bmap-last-token-start bmap))
(token-meta (or recycle (phpinspect-make-meta nil start end whitespace-before token))))
(when (< end start)
(error "Token %s ends before it starts. Start: %s, end: %s" token start end))
(unless whitespace-before
(setq whitespace-before ""))
(puthash token token-meta meta)
(when phpinspect-bmap-map-token-metadata
(puthash token token-meta phpinspect-token-references))
(when (and last-token-start
(<= start last-token-start))
(let ((child)
(stack (phpinspect-bmap-token-stack bmap)))
(while (and (car stack) (>= (phpinspect-meta-start (car stack)) start))
(setq child (pop stack))
(phpinspect-meta-set-parent child token-meta))
(setf (phpinspect-bmap-token-stack bmap) stack)))
(setf (phpinspect-bmap-last-token-start bmap) start)
(push token-meta (phpinspect-bmap-token-stack bmap))
(setf (phpinspect-bmap-last-meta bmap) token-meta)))
(define-inline phpinspect-pctx-register-token (pctx token start end)
(inline-letevals (pctx)
(inline-quote
(phpinspect-bmap-register
(phpinspect-pctx-bmap ,pctx) ,start ,end ,token (phpinspect-pctx-consume-whitespace ,pctx)))))
(define-inline phpinspect-bmap-token-starting-at (bmap point)
(inline-letevals (bmap point)
(inline-quote
(when-let ((root-meta (phpinspect-bmap-root-meta ,bmap)))
(if (= ,point (phpinspect-meta-start root-meta))
root-meta
(phpinspect-meta-find-child-starting-at-recursively
root-meta ,point))))))
(define-inline phpinspect-bmap-token-starting-after (bmap point)
(inline-letevals (bmap point)
(inline-quote
(let ((root-meta (phpinspect-bmap-root-meta ,bmap)))
(phpinspect-meta-find-child-after-recursively root-meta ,point)))))
(defun phpinspect-bmap-tokens-overlapping (bmap point)
(sort
(phpinspect-meta-find-overlapping-children (phpinspect-bmap-root-meta bmap) point)
#'phpinspect-meta-sort-width))
(defun phpinspect-bmap-token-meta (_bmap token)
"Get metadata object associated with TOKEN.
Requires `phpinspect-bmap-map-token-metadata' to be
non-nil. Otherwise no references are not stored in
`phpinspect-token-references' while parsing.
The _BMAP parameter is not used as references are stored in a
global variable. It has been kept as an argument for backwards
compatibility with tests and for easy refactoring later on."
(unless phpinspect-bmap-map-token-metadata
(error "`phpinspect-bmap-map-token-metadata' is not enabled"))
(gethash token phpinspect-token-references))
(defun phpinspect-bmap-last-token-before-point (bmap point)
"Search backward in BMAP for last token ending before POINT."
(let ((root-meta (phpinspect-bmap-root-meta bmap)))
(phpinspect-meta-find-child-before-recursively root-meta point)))
(define-inline phpinspect-bmap-recycle (bmap token-meta pos-delta &optional whitespace-before)
"Re-use TOKEN-META as a token in BMAP, applying POS-DELTA.
TOKEN-META start and end positions are shifted by POS-DELTA. See
`phpinspect-meta-shift'.
If WHITESPACE-BEFORE is provided, it is assigned to TOKEN-META's
whitespace-before slot.
Before mutating TOKEN-META, its slots are saved in a changeset
which can be used to revert the changes. Changesets are managed
via `phpinspect-parse-context'."
(inline-letevals (bmap token-meta pos-delta whitespace-before)
(inline-quote
(let* ((start (+ (phpinspect-meta-start ,token-meta) ,pos-delta))
(end (+ (phpinspect-meta-end ,token-meta) ,pos-delta)))
(setf (phpinspect-bmap-recycled-p ,bmap) t)
(phpinspect-meta-detach-parent ,token-meta)
(phpinspect-meta-shift ,token-meta ,pos-delta)
(dlet ((phpinspect-meta--point-offset-base nil))
(phpinspect-bmap-register
,bmap start end (phpinspect-meta-token ,token-meta) ,whitespace-before ,token-meta))))))
(defun phpinspect-make-region (start end)
(list start end))
(defalias 'phpinspect-region-start #'car)
(defalias 'phpinspect-region-end #'cadr)
(defun phpinspect-region-size (region)
(- (phpinspect-region-end region) (phpinspect-region-start region)))
(defun phpinspect-region> (reg1 reg2)
(> (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defun phpinspect-region< (reg1 reg2)
(< (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defun phpinspect-region-overlaps-point (reg point)
(and (> (phpinspect-region-end reg) point)
(<= (phpinspect-region-start reg) point)))
(defun phpinspect-region-overlaps (reg1 reg2)
(or (phpinspect-region-overlaps-point reg1 (phpinspect-region-start reg2))
(phpinspect-region-overlaps-point reg1 (- (phpinspect-region-end reg2) 1))
(phpinspect-region-overlaps-point reg2 (phpinspect-region-start reg1))
(phpinspect-region-overlaps-point reg2 (- (phpinspect-region-end reg1) 1))))
(defun phpinspect-region-encloses (reg1 reg2)
(and (<= (phpinspect-region-start reg1) (phpinspect-region-start reg2))
(>= (phpinspect-region-end reg1) (phpinspect-region-end reg2))))
(defun phpinspect-bmap-make-location-resolver (bmap)
(lambda (token)
(let ((meta (phpinspect-bmap-token-meta bmap token)))
(if meta
(phpinspect-make-region (phpinspect-meta-start meta)
(phpinspect-meta-end meta))
(phpinspect-make-region 0 0)))))
(provide 'phpinspect-bmap)
;;; phpinspect-bmap.el ends here
|