3 local P
, S
, V
, Cc
, Cmt
, Cp
, Ct
= l
.P
, l
.S
, l
.V
, l
.Cc
, l
.Cmt
, l
.Cp
, l
.Ct
6 local newline
= S
"\n\r"
7 local function past(_
, position
, pos
) return position
<= pos
end
11 local function startof(node
) return node
.start
end
12 local function finishof(node
) return node
.finish
end
16 local function at_pos(_
, position
, start
, finish
, range
)
17 if range
.start
+ 1 >= start
and range
.start
< finish
and range
.finish
< finish
then
18 return position
, start
- 1, finish
- 1
22 local function gt(pos
, val
)
23 return val
and pos
> val
26 local function le(pos
, val
)
27 return val
and pos
<= val
30 -- binary search a list for the nearest node before pos
31 local function before(t
, pos
, key
, skip
)
32 local left
, right
= 1, #t
33 while left
<= right
do
34 local m
= math
.floor(left
+ (right
- left
) / 2)
35 local m_val
= key(t
[m
])
36 if gt(pos
, m_val
) and (m
== #t
or le(pos
, key(t
[m
+ 1]))) then
38 while (t
[m
] and skip(t
[m
])) do
44 if le(pos
, m_val
) then right
= m
- 1 else left
= m
+ 1 end
48 -- binary search a list for the nearest node after pos
49 local function after(t
, pos
, key
, skip
)
50 if t
.is_root
and (#t
== 0 or pos
>= t
[#t
].start
) then
51 -- XXX: if we are at the last parsed top-level node, try to access the next one
52 -- to get it parsed as well. Otherwise the search will stop prematurely.
55 local left
, right
= 1, #t
56 while left
<= right
do
57 local m
= math
.floor(left
+ (right
- left
) / 2)
58 local m_val
= key(t
[m
])
59 if le(pos
, m_val
) and (m
== 1 or gt(pos
, key(t
[m
- 1]))) then
61 while (t
[m
] and skip(t
[m
])) do
67 if le(pos
, m_val
) then right
= m
- 1 else left
= m
+ 1 end
71 -- binary search a list for the node that contains pos
72 local function around(t
, range
)
73 if not (range
.start
and range
.finish
) then return end
74 local left
, right
= 1, #t
75 while left
<= right
do
76 local m
= math
.floor(left
+ (right
- left
) / 2)
79 if e
.start
and range
.start
>= e
.start
and e
.finish
and range
.finish
<= e
.finish
+ 1 and
80 (not nxt
or nxt
.start
> range
.start
) -- if adjoined - asdf|(qwer) - this will prefer (qwer)
82 if e
.start
and range
.start
< e
.start
then right
= m
- 1 else left
= m
+ 1 end
86 local function find_after(t
, selection
, pred
, stop
)
87 local _
, n
= t
.after(selection
.start
, startof
)
89 if pred(t
[n
]) then return t
[n
], n
90 elseif stop
and stop(t
[n
]) then return end
95 local function find_before(t
, selection
, pred
, stop
)
96 local _
, n
= t
.before(selection
.finish
, finishof
)
98 if pred(t
[n
]) then return t
[n
], n
99 elseif stop
and stop(t
[n
]) then return end
104 local function intersects(t
, range
)
105 local start
= t
.start
+ (t
.p
and #t
.p
or 0)
106 return start
>= range
.start
and t
.finish
>= range
.finish
and start
< range
.finish
107 or start
< range
.start
and t
.finish
< range
.finish
and range
.start
<= t
.finish
110 local function touches(t
, range
)
111 return t
.start
+ (t
.p
and #t
.p
or 0) >= range
.start
and t
.start
<= range
.start
or t
.finish
== range
.finish
- 1
114 local function contains(t
, range
)
116 return t
.start
+ (t
.p
and #t
.p
or 0) < range
.start
and t
.finish
> range
.finish
- 1
118 return t
.start
<= range
.start
and t
.finish
>= range
.finish
- 1
122 local function sexp_around(t
, range
)
123 if t
.is_root
or (t
.is_list
and contains(t
, range
)) then
124 local child
, nth
= t
.around(range
)
125 if child
and child
.is_list
and not touches(child
, range
) then
126 return sexp_around(child
, range
)
133 -- save the current logical position in terms of sexps, not offsets
134 local function _sexp_path(t
, range
, indices
, nodes
)
135 if t
.is_root
or (t
.is_list
and contains(t
, range
)) then
136 local child
, nth
= t
.around(range
)
137 table.insert(indices
, nth
)
138 table.insert(nodes
, child
)
139 if child
and child
.is_list
and not touches(child
, range
) then
140 _sexp_path(child
, range
, indices
, nodes
)
141 elseif not child
then
142 table.insert(indices
, false)
143 table.insert(nodes
, false)
146 return indices
, nodes
149 local function sexp_path(t
, range
)
150 return _sexp_path(t
, range
, {}, {})
153 -- find a sexp by following a previously-saved "sexp path"
154 local function goto_path(root
, path
)
157 for n
, i
in ipairs(path
) do
159 parent
= not parent
and root
or parent
[path
[n
- 1]]
164 local function catchup(tree
, range
)
165 if range
.finish
and range
.finish
>= tree
.first_invalid
then
166 tree
.parse_to(range
.finish
+ 1)
170 local function ensure_reparse(func
)
172 return function(range
, ...)
173 -- TODO: get rid of this type check. It is here because .after and .before take pos, not range
174 catchup(t
, type(range
) == "number" and {finish
= range
} or range
)
175 return func(t
, range
, ...)
180 local root_methods
= {
184 find_after
= find_after
,
185 find_before
= find_before
,
186 sexp_at
= sexp_around
,
187 sexp_path
= sexp_path
,
190 local function bind(func
)
198 local list_methods
= {
199 is_list
= function(_
) return true end,
200 is_empty
= function(t
) return #t
== 0 end,
201 around
= bind(around
),
202 before
= bind(before
),
204 find_after
= bind(find_after
),
205 find_before
= bind(find_before
),
208 local atom_methods
= {}
210 local quasiatom_methods
= {} -- "quasi atom" is a word inside a string or comment
212 local function text(t
)
213 local len
= t
.finish
+ 1 - t
.start
214 return read(t
.start
, len
)
217 atom_methods
.text
= text
218 list_methods
.text
= text
219 quasiatom_methods
.text
= text
221 local function dispatch(vtable
)
222 return function(self
, key
)
224 return vtable
[key
](self
)
229 local quasiatom_node
= {
230 __index
= dispatch(quasiatom_methods
)
233 local quasilist_methods
= (function(word
) -- "quasi list" is a string or comment
235 start_before
= function(t
, pos
)
236 pos
= pos
- (t
.start
+ #t
.d
)
237 local stops
= P
{Ct(((1 - word
)^
0 * Cp() * Cmt(Cc(pos
), past
) * word
)^
0)}:match(t
.itext
)
238 local newpos
= stops
and stops
[#stops
]
239 return newpos
and (t
.start
+ #t
.d
+ newpos
- 1)
242 start_after
= function(t
, pos
)
243 pos
= pos
- (t
.start
+ #t
.d
)
244 local stop
= P
{(1 - word
) * Cp() * word
+ 1 * V(1)}:match(t
.itext
, pos
+ 1)
245 return stop
and (t
.start
+ #t
.d
+ stop
- 1)
248 finish_before
= function(t
, pos
)
249 pos
= pos
- (t
.start
+ #t
.d
)
250 local stops
= P
{Ct(((1 - word
)^
0 * word
* Cp() * Cmt(Cc(pos
), past
))^
0)}:match(t
.itext
)
251 local newpos
= stops
and stops
[#stops
]
252 return newpos
and (t
.start
+ #t
.d
+ newpos
- 1)
255 finish_after
= function(t
, pos
)
256 pos
= pos
- (t
.start
+ #t
.d
)
257 local stop
= P
{word
* Cp() + 1 * V(1)}:match(t
.itext
, pos
+ 1)
258 return stop
and (t
.start
+ #t
.d
+ stop
- 1)
261 word_at
= function(t
, range
)
263 r
.start
= range
.start
- (t
.start
+ #t
.d
)
264 r
.finish
= range
.finish
- (t
.start
+ #t
.d
)
265 local start
, finish
= P
{Cmt(Cp() * word
* Cp() * Cc(r
), at_pos
) +
266 Cmt(1 * Cc(r
.finish
+ 1), past
) * V(1)}:match(t
.itext
)
267 if not start
then return end
268 local node
= {start
= t
.start
+ #t
.d
+ start
, finish
= t
.start
+ #t
.d
+ finish
- 1}
269 return setmetatable(node
, quasiatom_node
)
272 end)(P
{"\\" * P(1 - hspace
- newline
) + P(1 - hspace
- newline
)^
1})
274 local function quasilist_is_empty(t
)
275 return not t
.finish_after(t
.start
+ (t
.p
and #t
.p
or 0) + #t
.d
)
278 local function quasilist_new(opposite
)
280 for k
, v
in pairs(quasilist_methods
) do
283 methods
.is_empty
= quasilist_is_empty
285 function methods
.itext(t
)
286 local start
= t
.start
+ (t
.p
and #t
.p
or 0) + #t
.d
287 local len
= t
.finish
+ 1 - #opposite
[t
.d
] - start
288 return read(start
, len
) or ""
291 local quasilist_node
= {
292 __index
= dispatch(methods
)
295 return quasilist_node
299 __index
= dispatch(atom_methods
)
303 __index
= dispatch(list_methods
)
306 local function root_rewind(t
)
307 return function(index
)
308 assert(index
<= t
.first_invalid
, ("%d > %d - You can only rewind backwards!"):format(index
, t
.first_invalid
))
309 t
.first_invalid
= index
313 local function root_is_parsed(t
)
314 return function(index
)
315 return index
< t
.first_invalid
319 local function root_goto_path(t
)
320 return function(path
)
321 local range
= {start
= t
[path
[1]]
.finish
, finish
= t
[path
[1]]
.finish
}
323 return goto_path(t
, path
)
327 local function root_new(opposite
)
329 for k
, v
in pairs(root_methods
) do
330 methods
[k
] = ensure_reparse(v
)
333 methods
.rewind
= root_rewind
334 methods
.is_parsed
= root_is_parsed
335 methods
.goto_path
= root_goto_path
337 local function unbalanced_delimiters(t
, range
)
338 if t
.d
or t
.is_root
then
340 if t
[i
].d
and (contains(t
[i
], range
) or intersects(t
[i
], range
)) then
341 unbalanced_delimiters(t
[i
], range
)
344 if not t
.is_root
then
345 local start
= t
.start
+ (t
.p
and #t
.p
or 0) + #t
.d
346 local finish
= t
.finish
+ 1 - #opposite
[t
.d
]
347 if start
<= range
.start
and finish
< range
.finish
then
348 coroutine
.yield({start
= finish
, finish
= t
.finish
+ 1, closing
= true})
349 elseif start
> range
.start
and t
.finish
>= range
.finish
then
350 coroutine
.yield({start
= t
.start
, finish
= start
, opening
= true})
356 function methods
.unbalanced_delimiters(t
)
357 local slice_at
= coroutine
.wrap(unbalanced_delimiters
)
358 return function(range
)
362 local slice_pos
= slice_at(t
, range
)
363 if slice_pos
then table.insert(skips
, slice_pos
) end
375 quasilist
= quasilist_new
,