WIP: n-ary interval tree for token location tracking
ci/woodpecker/push/woodpecker Pipeline was successful Details

WIP-incremental-parsing
Hugo Thunnissen 11 months ago
parent 0ca527dbbd
commit 08d80d3a38

@ -43,17 +43,41 @@ buffer. This variable is only set for buffers where
(defsubst phpinspect-region< (reg1 reg2)
(< (phpinspect-region-size reg1) (phpinspect-region-size reg2)))
(defsubst phpinspect-region-overlaps-point (reg point)
(and (>= (phpinspect-region-end reg) point)
(<= (phpinspect-region-start reg) point)))
(defsubst phpinspect-region-overlaps (reg1 reg2)
(or (phpinspect-region-reg2s-point reg1 (phpinspect-region-start reg2))
(phpinspect-region-reg2s-point reg1 (phpinspect-region-end reg2))
(phpinspect-region-reg2s-point reg2 (phpinspect-region-start reg1))
(phpinspect-region-reg2s-point reg2 (phpinspect-region-end reg1))))
(cl-defstruct (phpinspect-token-metadata (:constructor phpinspect-make-token-metadata))
"An object that represents the metadata associated with a parsed token."
(token nil
:type phpinspect-token
:documentation
"The token that metadata is associated with.")
(location nil
:type phpinspect-region
:documentation
"The region that token occupies.")
(handler nil
:type phpinspect-handler
:documentation
"The handler that was used to parse token. (see `phpinspect-defhandler')"))
(cl-defstruct (phpinspect-buffer (:constructor phpinspect-make-buffer))
"An object containing phpinspect related metadata linked to an
emacs buffer."
(buffer nil
:type buffer
:documentation "The underlying emacs buffer")
(location-map (make-hash-table :test 'eq :size 400 :rehash-size 400)
(metadata-map (make-hash-table :test 'eq :size 400 :rehash-size 400)
:type hash-table
:documentation
"A map that lets us look up the character
positions of a token within this buffer.")
"A map containing metadata associated with tokens.")
(tree nil
:type list
:documentation
@ -65,7 +89,7 @@ with the contents of the buffer."))
"Parse the PHP code in the the emacs buffer that this object is
linked with."
(with-current-buffer (phpinspect-buffer-buffer buffer)
(setf (phpinspect-buffer-location-map buffer)
(setf (phpinspect-buffer-metadata-map buffer)
(make-hash-table :test 'eq
:size 400
:rehash-size 400))
@ -74,21 +98,39 @@ linked with."
(setf (phpinspect-buffer-tree buffer) tree)
tree)))
(cl-defmethod phpinspect-buffer-set-token-metadata
((buffer phpinspect-buffer) token (metadata phpinspect-token-metadata))
"Set the METADATA associated with TOKEN that was parsed in BUFFER"
(puthash token metadata (phpinspect-buffer-metadata-map buffer)))
(cl-defmethod phpinspect-buffer-get-token-metadata ((buffer phpinspect-buffer) token)
(gethash token (phpinspect-buffer-metadata-map buffer)))
(cl-defmethod phpinspect-buffer-token-location ((buffer phpinspect-buffer) token)
(gethash token (phpinspect-buffer-location-map buffer)))
(phpinspect-token-metadata-location (phpinspect-buffer-get-token-metadata buffer token)))
(cl-defmethod phpinspect-buffer-tokens-enclosing-point ((buffer phpinspect-buffer) point)
(let ((tokens))
(maphash
(lambda (token region)
(when (and (<= (phpinspect-region-start region) point)
(>= (phpinspect-region-end region) point))
(push token tokens)))
(phpinspect-buffer-location-map buffer))
(lambda (token meta)
(let ((region (phpinspect-token-metdata-location meta)))
(when (and (<= (phpinspect-region-start region) point)
(>= (phpinspect-region-end region) point))
(push token tokens))))
(phpinspect-buffer-metadata-map buffer))
(sort tokens (lambda (tok1 tok2)
(phpinspect-region< (phpinspect-buffer-token-location tok1)
(phpinspect-buffer-token-location tok2))))))
(cl-defmethod phpinspect-buffer-tokens-overlapping-region
((buffer phpinspect-buffer) (start integer) (end integer))
(let ((tokens)
(query-region (phpinspect-make-region start end)))
(maphash (lambda (token metadata)
(when (phpinspect-region-overlaps
query-region (phpinspect-token-metadata-location metadata))
(push token tokens)))
(phpinspect-buffer-metadata-map buffer))
tokens))
(provide 'phpinspect-buffer)

@ -412,16 +412,18 @@ token is \";\", which marks the end of a statement in PHP."
(progn
(nconc tokens (list token))))
;; When parsing within a buffer that has
;; `phpinspect-current-buffer` set, update the
;; token location map. Usually, this variable
;; is set when `phpinspect-mode` is active.
(when phpinspect-current-buffer
(puthash token
(phpinspect-make-region start-position
(point))
(phpinspect-buffer-location-map
phpinspect-current-buffer)))))))
;; When parsing within a buffer that has
;; `phpinspect-current-buffer` set, update the
;; token metadata maps. Usually, this variable
;; is set when `phpinspect-mode` is active.
(when phpinspect-current-buffer
(phpinspect-buffer-set-token-metadata
phpinspect-current-buffer
token
(phpinspect-make-token-metadata
:location (phpinspect-make-region
start-position (point))
:handler ,handler)))))))
handlers)
(t (forward-char))))
(push ,tree-type tokens))))))

@ -0,0 +1,536 @@
;;; phpinspect-buffer.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 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:
;;; Code:
(cl-defstruct (phpinspect-tree (:constructor phpinspect-make-tree))
"An n-ary tree implementation to store integer intervals.
Nodes within a layer of the tree are not allowed to overlap each
other. Trying to add an overlapping node that cannot enclose or
be enclosed in an existing node will result in an error.
Each node can have an infinite number of child nodes.
It is advisable for performance to define a root node with a
range that encloses all nodes that are going to be added, as this
will limit the amount of pointer shuffling to keep the root node
reference intact (see also `phininspect-tree-insert-node'). That
being said, not doing so will not limit the trees
functionalities."
(parent nil
:type phpinspect-tree)
(children (phpinspect-make-ll)
:type phpinspect-llnode)
(start 0
:type integer)
(end 0
:type integer)
(value nil
:type value))
(cl-defstruct (phpinspect-llnode (:constructor phpinspect-make-ll))
"A linked list implementation.
Links for specific cells are tracked and can be looked up via the
link-map. This does assume that no duplicate cells are inserted
however (multiple cells that are `eq' to each other). If
duplicate cells are inserted, only the last inserted duplicate
can be looked up via the link-map.
A few generic sequence functions have been implemented. Some of
these, like `seq-take-while' return an instance of
`phpinspect-slice', which is a window into a subsection of the
list it was called on."
(left nil
:type phpinspect-llnode)
(right nil
:type phpinspect-llnode)
(link-map (make-hash-table :test #'eq :size 100 :rehash-size 400)
:type hash-table
:documentation
"Table to lookup the links in which values are stored.")
(value nil))
(cl-defstruct (phpinspect-slice (:constructor phpinspect-make-slice))
"A window to a subsection of a (`phpinspect-llnode') linked list. "
(start nil)
(end nil))
(cl-defmethod phpinspect-slice-detach ((slice phpinspect-slice))
"Detach underlying link range from the linked list that it
belongs to. Return resulting linked list."
(let* ((start (phpinspect-slice-start slice))
(end (phpinspect-slice-end slice))
(left-of (phpinspect-llnode-right end))
(right-of (phpinspect-llnode-left start)))
;; No left-linked node means that `start' is the root reference to the
;; list. This cannot be detached, so we need to create a new link that will
;; serve as root for the detached list.
(unless right-of
(let ((new-start (phpinspect-make-ll :right (phpinspect-llnode-right start)
:value (phpinspect-llnode-value start))))
(setf (phpinspect-llnode-left (phpinspect-llnode-right start)) new-start)
(when left-of
(phpinspect-ll-relink start (phpinspect-llnode-value left-of))
(setq left-of (phpinspect-llnode-right left-of)))
(setq right-of start)
(when (eq start end) (setq end new-start))
(setq start new-start)))
(if (eq start end)
start
(when left-of
(setf (phpinspect-llnode-left left-of) right-of))
(setf (phpinspect-llnode-right right-of) left-of)
(setf (phpinspect-llnode-left start) nil)
(setf (phpinspect-llnode-right end) nil)
;; Fix broken references in old link-map and create separate link-map for
;; the new detached list.
(let ((list start)
(link-map (make-hash-table :test #'eq :size 100 :rehash-size 400)))
(while list
(phpinspect-ll-unregister-link list)
(setf (phpinspect-llnode-link-map list) link-map)
(phpinspect-ll-register-link list)
(setq list (phpinspect-llnode-right list))))
start)))
(cl-defmethod phpinspect-ll-register-link ((list phpinspect-llnode))
(puthash (phpinspect-llnode-value list) list (phpinspect-llnode-link-map list)))
(cl-defmethod phpinspect-ll-unregister-link ((list phpinspect-llnode))
(remhash (phpinspect-llnode-value list) (phpinspect-llnode-link-map list)))
(cl-defmethod phpinspect-ll-first ((list phpinspect-llnode))
(while (phpinspect-llnode-left list)
(setq list (phpinspect-llnode-left list)))
(or (phpinspect-llnode-left list) list))
(cl-defmethod phpinspect-ll-last ((list phpinspect-llnode))
(while (phpinspect-llnode-right list)
(setq list (phpinspect-llnode-right list)))
(or (phpinspect-llnode-right list) list))
(cl-defmethod phpinspect-ll-link ((list phpinspect-llnode) value)
(gethash value (phpinspect-llnode-link-map list)))
(cl-defmethod phpinspect-ll-relink ((list phpinspect-llnode) value)
(phpinspect-ll-unregister-link list)
(setf (phpinspect-llnode-value list) value)
(phpinspect-ll-register-link list))
(cl-defmethod phpinspect-ll-push (value (list phpinspect-llnode))
(setq list (phpinspect-ll-first list))
(if (phpinspect-llnode-value list)
(let* ((old-right (phpinspect-llnode-right list))
(new-right (phpinspect-make-ll
:left list
:link-map (phpinspect-llnode-link-map list)
:value (phpinspect-llnode-value list)
:right old-right)))
(phpinspect-ll-register-link new-right)
(setf (phpinspect-llnode-value list) value)
(setf (phpinspect-llnode-right list) new-right)
(phpinspect-ll-register-link list)
(when old-right
(setf (phpinspect-llnode-left old-right) new-right)))
;; else
(setf (phpinspect-llnode-value list) value)
(phpinspect-ll-register-link list))
list)
(cl-defmethod phpinspect-ll-insert-right ((list phpinspect-llnode) value)
(let* ((original-right (phpinspect-llnode-right list))
(new-link (phpinspect-make-ll :left list
:link-map (phpinspect-llnode-link-map list)
:right original-right
:value value)))
(phpinspect-ll-register-link new-link)
(setf (phpinspect-llnode-right list) new-link)
(when original-right
(setf (phpinspect-llnode-left original-right) new-link))))
(cl-defmethod phpinspect-ll-insert-left ((list phpinspect-llnode) value)
(let* ((original-left (phpinspect-llnode-left list))
(new-link (phpinspect-make-ll :right list
:link-map (phpinspect-llnode-link-map list)
:left original-left
:value value)))
(phpinspect-ll-register-link new-link)
(setf (phpinspect-llnode-left list) new-link)
(when original-left
(setf (phpinspect-llnode-right original-left) new-link))))
(cl-defmethod seq-elt ((list phpinspect-llnode) (n integer))
(setq list (phpinspect-ll-first list))
(let ((current-elt 0))
(while (and list (not (= current-elt n)))
(setq list (phpinspect-llnode-right list)
current-elt (+ current-elt 1)))
(when list
(phpinspect-llnode-value list))))
(cl-defmethod seq-elt ((slice phpinspect-slice) (n integer))
(let ((list (phpinspect-slice-start slice))
(end (phpinspect-llnode-right (phpinspect-slice-end slice))))
(let ((current-elt 0))
(while (and list (not (= current-elt n)))
(setq list (phpinspect-llnode-right list)
current-elt (+ current-elt 1)))
(when (eq end list)
(setq list nil)))
(when list
(phpinspect-llnode-value list))))
(cl-defmethod seq-map (fn (list phpinspect-llnode))
(when (phpinspect-llnode-value list)
(let ((values))
(while list
(push (funcall fn (phpinspect-llnode-value list)) values)
(setq list (phpinspect-llnode-right list)))
(nreverse values))))
(cl-defmethod seq-map (fn (slice phpinspect-slice))
(let ((list (phpinspect-slice-start slice))
(end (phpinspect-llnode-right (phpinspect-slice-end slice))))
(when (phpinspect-llnode-value list)
(let ((values))
(while (and list (not (eq end list)))
(push (funcall fn (phpinspect-llnode-value list)) values)
(setq list (phpinspect-llnode-right list)))
(nreverse values)))))
(cl-defmethod seq-take-while (pred (list phpinspect-llnode))
(when (phpinspect-llnode-value list)
(let ((start list)
(end list))
(while (and list (funcall pred (phpinspect-llnode-value list)))
(setq end list)
(setq list (phpinspect-llnode-right list)))
(phpinspect-make-slice :start start :end end))))
(cl-defmethod seq-take-while (pred (slice phpinspect-slice))
(let ((list (phpinspect-slice-start slice))
(slice-end (phpinspect-llnode-right (phpinspect-slice-end slice))))
(when (phpinspect-llnode-value list)
(let ((start list)
(end list))
(while (and list (not (eq slice-end list))
(funcall pred (phpnspect-llnode-value list)))
(setq end list)
(setq list (phpinspect-llnode-right list)))
(phpinspect-make-slice :start start :end end)))))
(cl-defmethod seq-length ((list phpinspect-llnode))
(let ((count 0))
(while (and list (phpinspect-llnode-value list))
(setq count (+ 1 count)
list (phpinspect-llnode-right list)))
count))
(cl-defmethod seq-length ((slice phpinspect-slice))
(let ((count 0)
(list (phpinspect-slice-start slice))
(end (phpinspect-llnode-right (phpinspect-slice-end slice))))
(while (and list (not (eq end list))
(phpinspect-llnode-value list))
(setq count (+ 1 count)
list (phpinspect-llnode-right list))
(when (eq end list)
(setq list nil)))
count))
(cl-defmethod seq-into ((list phpinspect-llnode) type)
(let ((destination)
(list (phpinspect-ll-last list)))
(while list
(push (phpinspect-llnode-value list) destination)
(setq list (phpinspect-llnode-left list)))
(cond ((eq 'vector type) (vconcat destination))
((eq 'list type) destination)
((eq 'string type) (concat destination))
(t (error "Not a sequence type name: %S" type)))))
(cl-defmethod seq-into ((slice phpinspect-slice) type)
(let ((destination)
(list (phpinspect-slice-end slice))
(slice-start (phpinspect-llnode-left (phpinspect-slice-start slice))))
(while (and list (not (eq slice-start list)))
(push (phpinspect-llnode-value list) destination)
(setq list (phpinspect-llnode-left list)))
(cond ((eq 'vector type) (vconcat destination))
((eq 'list type) destination)
((eq 'string type) (concat destination))
(t (error "Not a sequence type name: %S" type)))))
(cl-defmethod seq-find (pred (list phpinspect-llnode) &optional default)
(if (phpinspect-llnode-value list)
(while (and list (not (funcall pred (phpinspect-llnode-value list))))
(setq list (phpinspect-llnode-right list)))
(setq list nil))
(if list
(phpinspect-llnode-value list)
default))
(cl-defmethod seq-find (pred (slice phpinspect-slice) &optional default)
(let ((list (phpinspect-slice-start slice))
(end (phpinspect-llnode-right (phpinspect-slice-end slice))))
(if (phpinspect-llnode-value list)
(while (and list (not (eq end list))
(not (funcall pred (phpinspect-llnode-value list))))
(setq list (phpinspect-llnode-right list)))
(setq list nil))
(if list
(phpinspect-llnode-value list)
default)))
(cl-defmethod phpinspect-ll-pp ((list phpinspect-llnode))
(message "(phpinspect-ll %s)"
(string-join (seq-map (lambda (x) (format "%s" x)) list) ", ")))
(cl-defmethod phpinspect-llnode-is-tail ((list phpinspect-llnode))
(not (phpinspect-llnode-right list)))
(cl-defmethod phpinspect-tree-overlaps ((tree phpinspect-tree) (point integer))
(and (>= (phpinspect-tree-end tree) point)
(<= (phpinspect-tree-start tree) point)))
(cl-defmethod phpinspect-tree-overlaps ((tree1 phpinspect-tree) (tree2 phpinspect-tree))
(and
(or (phpinspect-tree-overlaps tree1 (phpinspect-tree-start tree2))
(phpinspect-tree-overlaps tree1 (phpinspect-tree-end tree2))
(phpinspect-tree-overlaps tree2 (phpinspect-tree-start tree1))
(phpinspect-tree-overlaps tree2 (phpinspect-tree-end tree1)))
(not (or (= (phpinspect-tree-start tree1) (phpinspect-tree-end tree2))
(= (phpinspect-tree-end tree1) (phpinspect-tree-start tree2))))))
(cl-defmethod phpinspect-tree-overlaps ((tree phpinspect-tree) region)
(and
(or (phpinspect-tree-overlaps tree (phpinspect-region-start region))
(phpinspect-tree-overlaps tree (phpinspect-region-end region))
(phpinspect-region-overlaps-point region (phpinspect-tree-start tree))
(phpinspect-region-overlaps-point region (phpinspect-tree-end tree)))
(not (or (= (phpinspect-tree-start tree) (phpinspect-region-end region))
(= (phpinspect-tree-end tree) (phpinspect-region-start region))))))
(cl-defmethod phpinspect-tree-encloses ((tree1 phpinspect-tree) (tree2 phpinspect-tree))
(and (<= (phpinspect-tree-start tree1) (phpinspect-tree-start tree2))
(>= (phpinspect-tree-end tree1) (phpinspect-tree-end tree2))))
(cl-defmethod phpinspect-tree-switch-attributes ((tree1 phpinspect-tree) (tree2 phpinspect-tree))
(let ((parent (phpinspect-tree-parent tree1))
(children (phpinspect-tree-children tree1))
(start (phpinspect-tree-start tree1))
(end (phpinspect-tree-end tree1))
(value (phpinspect-tree-value tree1)))
(setf (phpinspect-tree-parent tree1) (phpinspect-tree-parent tree2))
(setf (phpinspect-tree-children tree1) (phpinspect-tree-children tree2))
(setf (phpinspect-tree-start tree1) (phpinspect-tree-start tree2))
(setf (phpinspect-tree-end tree1) (phpinspect-tree-end tree2))
(setf (phpinspect-tree-value tree1) (phpinspect-tree-value tree2))
(seq-map (lambda (child)
(setf (phpinspect-tree-parent child) tree1))
children)
(setf (phpinspect-tree-parent tree2) parent)
(setf (phpinspect-tree-children tree2) children)
(setf (phpinspect-tree-start tree2) start)
(setf (phpinspect-tree-end tree2) end)
(setf (phpinspect-tree-value tree2) value)
(seq-map (lambda (child)
(setf (phpinspect-tree-parent child) tree2))
children)))
(cl-defmethod phpinspect-tree-find-overlapping-children
((tree phpinspect-tree) (start integer) (end integer))
(let* ((region (phpinspect-make-region start end))
(children (phpinspect-tree-children tree))
(first-overlapper
(seq-find (lambda (child) (phpinspect-tree-overlaps child region))
children)))
(when first-overlapper
(seq-take-while (lambda (child) (phpinspect-tree-overlaps child region))
(phpinspect-ll-link children first-overlapper)))))
(cl-defmethod phpinspect-tree-insert-node ((tree phpinspect-tree) (node phpinspect-tree))
"Insert a new NODE into TREE.
Returns the newly inserted node."
(cond ((phpinspect-tree-encloses node tree)
;; New node encloses entire tree, so it has to become the new root.
(let* ((parent (phpinspect-tree-parent tree)))
(if parent
(progn
(phpinspect-ll-relink
(phpinspect-ll-link (phpinspect-tree-children parent) tree) node)
(phpinspect-tree-insert-node node tree)
;; Return
node)
;; No parent, which means that this is the absolute root node of
;; the tree. To keep things consistent, swap all the attributes of
;; both trees to keep the reference to the root node intact for the
;; caller.
(progn
(phpinspect-tree-switch-attributes node tree)
(phpinspect-tree-insert-node tree node)
;; Return tree, as this is the node that value of node has been
;; stored in.
tree))))
((phpinspect-tree-encloses tree node)
;; New node is entirely enclosed by tree, check tree's children for
;; overlappings.
(let* ((overlappers (phpinspect-tree-find-overlapping-children
tree (phpinspect-tree-start node) (phpinspect-tree-end node)))
(overlap-count (seq-length overlappers)))
(if overlappers
(cond
((= 1 overlap-count)
(phpinspect-tree-insert-node (seq-elt overlappers 0)
node))
((< 1 overlap-count)
;; There are multiple overlapping children. They need to all
;; fit within node, or the hierarchy is broken.
(let ((enclosed
(seq-take-while
(lambda (child) (phpinspect-tree-encloses node child))
overlappers))
(insert-after-link))
(unless (= (seq-length enclosed) overlap-count)
(throw 'phpinspect-tree-conflict
"Node overlaps multiple children, but does not enclose them all"))
(setq insert-after-link (phpinspect-llnode-left
(phpinspect-slice-start enclosed)))
(setq enclosed (phpinspect-slice-detach enclosed))
(phpinspect-ll-insert-right insert-after-link node)
(setf (phpinspect-tree-parent node) tree)
(setf (phpinspect-tree-children node) enclosed))))
;; ELSE: No overlap, node can safely be added as child
(setf (phpinspect-tree-parent node) tree)
(let* ((right-neighbour (phpinspect-tree-children tree))
(right-neighbour-value
(seq-find (lambda (child) (< (phpinspect-tree-end child)
(phpinspect-tree-start node)))
right-neighbour)))
(when right-neighbour-value
(setq right-neighbour (phpinspect-ll-link
right-neighbour
right-neighbour-value)))
(if (phpinspect-llnode-left right-neighbour)
(phpinspect-ll-insert-left right-neighbour node)
(phpinspect-ll-push node right-neighbour)))))
;; Return
node)
(t (throw 'phpinspect-tree-conflict
(format "Tree does not enclose or get enclosed. \nTree: %s \n\nPerspective parent: %s" node tree)))))
(cl-defmethod phpinspect-tree-traverse-overlappig ((tree phpinspect-tree) (point integer))
"Traverse TREE for intervals overlapping POINT.
Returns list of values from overlapping trees, sorted by interval
width with the smallest interval as car."
(when (phpinspect-tree-overlaps tree point)
(let* ((overlapper
(seq-find (lambda (child) (phpinspect-tree-overlaps child point))
(phpinspect-tree-children tree))))
(if overlapper
`(,@(phpinspect-tree-traverse-overlappig overlapper point) ,(phpinspect-tree-value tree))
`(,(phpinspect-tree-value tree))))))
(cl-defmethod phpinspect-tree-find-smallest-overlapping-set ((tree phpinspect-tree) region)
"Traverse TREE for smallest set of intervals overlapping REGION,
Returns list of values from the set of overlapping trees that
collectively have the smallest width."
(when (phpinspect-tree-overlaps tree region)
(let* ((tree-start (phpinspect-tree-start tree))
(tree-end (phpinspect-tree-end tree))
(overlappers (phpinspect-tree-find-overlapping-children
tree (phpinspect-region-start region)
(phpinspect-region-end region)))
(overlap-count (seq-length overlappers))
(overlap-start tree-start)
(overlap-end tree-end))
(when overlappers
(setq overlap-start
(phpinspect-tree-start
(phpinspect-llnode-value (phpinspect-slice-start overlappers))))
(setq overlap-end
(phpinspect-tree-end
(phpinspect-llnode-value (phpinspect-slice-end overlappers)))))
(if (or (> overlap-start tree-start)
(< overlap-end tree-end))
(cond
((< 1 overlap-count)
;; Overlap of children is smaller, but no point recursing if it already
;; spans two children. Return overlappers.
(seq-map #'phpinspect-tree-value overlappers))
((= 1 overlap-count)
;; Overlap of single child is smaller, recurse.
(phpinspect-tree-find-smallest-overlapping-set (seq-elt overlappers 0)
region)))
;; Overlap spans the entire tree, so this already is the smallest
;; overlapping set (of one).
`(,(phpinspect-tree-value tree))))))
(cl-defmethod phpinspect-tree-insert
((tree phpinspect-tree) (start integer) (end integer) value)
"Insert a new interval from START to END linked to VALUE into TREE.
Returns the newly created and inserted node."
(let ((node (phpinspect-make-tree :start start :end end :value value)))
(phpinspect-tree-insert-node tree node)))
(provide 'phpinspect-tree)

@ -31,18 +31,6 @@
(defvar phpinspect-worker nil
"Contains the phpinspect worker that is used by all projects.")
(cl-defstruct (phpinspect-index-task
(:constructor phpinspect-make-index-task-generated))
"Represents an index task that can be executed by a `phpinspect-worker`."
(project nil
:type phpinspect-project
:documentation
"The project that the task should be executed for.")
(type nil
:type phpinspect--type
:documentation
"The type whose file should be indexed."))
(cl-defstruct (phpinspect-queue-item
(:constructor phpinspect-make-queue-item))
(next nil
@ -145,9 +133,6 @@ BODY can be any form."
(when (not (phpinspect-queue-find item thing comparison-func))
(phpinspect-queue-enqueue item thing)))
(cl-defmethod phpinspect-queue-await-insert ((item phpinspect-queue-item))
(condition-wait (phpinspect-queue-item-insert item)))
(cl-defstruct (phpinspect-worker
(:constructor phpinspect-make-worker-generated))
(queue nil
@ -223,19 +208,10 @@ on the worker independent of dynamic variables during testing.")
"Enqueue a TASK to be executed by WORKER.")
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker) task)
(phpinspect-queue-enqueue (phpinspect-worker-queue worker) task))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-worker)
(task phpinspect-index-task))
"Specialized enqueuement method for index tasks. Prevents
indexation tasks from being added when there are identical tasks
already present in the queue."
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-index-task=))
(cl-defmethod phpinspect-index-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(phpinspect-queue-enqueue-noduplicate (phpinspect-worker-queue worker) task #'phpinspect-task=))
(cl-defmethod phpinspect-worker-enqueue ((worker phpinspect-dynamic-worker) task)
(phpinspect-worker-enqueue (phpinspect-resolve-dynamic-worker worker)
@ -328,20 +304,57 @@ CONTINUE must be a condition-variable"
(interactive)
(phpinspect-worker-stop phpinspect-worker))
;;; TASKS
;; The rest of this file contains task definitions. Tasks represent actions that
;; can be executed by `phpinspect-worker'. Some methods are required to be
;; implemented for all tasks, while others aren't.
;; REQUIRED METHODS:
;; - phpinspect-task-execute
;; - phpinspect-task-project
;; OPTIONAL METHODS:
;; - phpinspect-task=
;;; Code:
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(cl-defmethod phpinspect-task= (task1 task2)
"Whether or not TASK1 and TASK2 are set to execute the exact same action."
nil)
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
;;; INDEX TASK
(cl-defstruct (phpinspect-index-task
(:constructor phpinspect-make-index-task-generated))
"Represents an index task that can be executed by a `phpinspect-worker`."
(project nil
:type phpinspect-project
:documentation
"The project that the task should be executed for.")
(type nil
:type phpinspect--type
:documentation
"The type whose file should be indexed."))
(cl-defgeneric phpinspect-make-index-task ((project phpinspect-project)
(type phpinspect--type))
(phpinspect-make-index-task-generated
:project project
:type type))
(cl-defgeneric phpinspect-task-project (task)
"The project that this task belongs to.")
(cl-defmethod phpinspect-task-project ((task phpinspect-index-task))
(phpinspect-index-task-project task))
(cl-defgeneric phpinspect-task-execute (task worker)
"Execute TASK for WORKER.")
(cl-defmethod phpinspect-task= ((task1 phpinspect-index-task) (task2 phpinspect-index-task))
(and (eq (phpinspect-index-task-project task1)
(phpinspect-index-task-project task2))
(phpinspect--type= (phpinspect-index-task-type task1) (phpinspect-index-task-type task2))))
(cl-defmethod phpinspect-task-execute ((task phpinspect-index-task)
(worker phpinspect-worker))
@ -349,12 +362,12 @@ CONTINUE must be a condition-variable"
(let ((project (phpinspect-index-task-project task))
(is-native-type (phpinspect--type-is-native
(phpinspect-index-task-type task))))
(phpinspect--log "Indexing class %s for project in %s from index thread"
(phpinspect--log "Indexing class %s for project in %s as task."
(phpinspect-index-task-type task)
(phpinspect-project-root project))
(cond (is-native-type
(phpinspect--log "Skipping indexation of native type %s"
(phpinspect--log "Skipping indexation of native type %s as task"
(phpinspect-index-task-type task))
;; We can skip pausing when a native type is encountered
@ -367,6 +380,7 @@ CONTINUE must be a condition-variable"
(when root-index
(phpinspect-project-add-index project root-index)))))))
;;; PARSE BUFFER TASK
(provide 'phpinspect-worker)
;;; phpinspect-worker.el ends here

@ -644,6 +644,7 @@ class Thing
(load-file (concat phpinspect-test-directory "/test-class.el"))
(load-file (concat phpinspect-test-directory "/test-type.el"))
(load-file (concat phpinspect-test-directory "/test-util.el"))
(load-file (concat phpinspect-test-directory "/test-tree.el"))
(provide 'phpinspect-test)
;;; phpinspect-test.el ends here

@ -1,4 +1,4 @@
;;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
@ -27,33 +27,34 @@
(require 'phpinspect-parser)
(require 'phpinspect-buffer)
(ert-deftest phpinspect-parse-buffer-location-map ()
(ert-deftest phpinspect-parse-buffer-token-locations ()
"Confirm that the location map of `phpinspect-current-buffer' is
populated when the variable is set and the data in it is accurate."
(let* ((location-map)
(parsed)
(let* ((parsed)
(class))
(with-temp-buffer
(insert-file-contents (concat phpinspect-test-php-file-directory "/NamespacedClass.php"))
(setq phpinspect-current-buffer
(phpinspect-make-buffer :buffer (current-buffer)))
(setq parsed (phpinspect-buffer-parse phpinspect-current-buffer))
(setq location-map
(phpinspect-buffer-location-map phpinspect-current-buffer)))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(class-region (gethash class location-map))
(classname-region (gethash (car (cddadr class)) location-map)))
(should class)
(should class-region)
(should classname-region)
;; Character position of the start of the class token.
(should (= 611 (phpinspect-region-start class-region)))
(should (= 2367 (phpinspect-region-end class-region)))
(let* ((class (seq-find #'phpinspect-class-p
(seq-find #'phpinspect-namespace-p parsed)))
(class-meta (phpinspect-buffer-get-token-metadata
phpinspect-current-buffer class))
(class-region (phpinspect-token-metadata-location class-meta))
(classname-meta (phpinspect-buffer-get-token-metadata
phpinspect-current-buffer (car (cddadr class))))
(classname-region (phpinspect-token-metadata-location classname-meta)))
(should class)
(should class-region)
(should classname-region)
;; Character position of the start of the class token.
(should (= 611 (phpinspect-region-start class-region)))
(should (= 2367 (phpinspect-region-end class-region)))
(should (= 617 (phpinspect-region-start classname-region)))
(should (= 634 (phpinspect-region-end classname-region))))))
(should (= 617 (phpinspect-region-start classname-region)))
(should (= 634 (phpinspect-region-end classname-region)))))))
(ert-deftest phpinspect-parse-buffer-no-current ()
"Confirm that the parser is still functional with

@ -0,0 +1,315 @@
;;; test-buffer.el --- Unit tests for phpinspect.el -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc.
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; 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:
;;
;;; Code:
(require 'ert)
(require 'phpinspect-tree)
(require 'phpinspect-buffer)
(ert-deftest phpinspect-ll-seq-elt ()
"Test `seq-elt' implementation for linked list."
(let ((list (phpinspect-make-ll
:value "a"
:right (phpinspect-make-ll :value "b"
:right (phpinspect-make-ll :value "c")))))
(should (string= "a" (seq-elt list 0)))
(should (string= "b" (seq-elt list 1)))
(should (string= "c" (seq-elt list 2)))
(should-not (seq-elt list 3))))
(ert-deftest phpinspect-ll-push ()
(let ((list (phpinspect-make-ll)))
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "c" list)
(should (string= "c" (seq-elt list 0)))
(should (string= "b" (seq-elt list 1)))
(should (string= "a" (seq-elt list 2)))
(should (string= "c" (phpinspect-llnode-value
(phpinspect-llnode-left
(phpinspect-ll-link list (seq-elt list 1))))))
(should (string= "b" (phpinspect-llnode-value
(phpinspect-llnode-left
(phpinspect-ll-link list (seq-elt list 2))))))))
(ert-deftest phpinspect-ll-link ()
(let ((list (phpinspect-make-ll))
(link-value)
(link))
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "c" list)
(setq link-value (seq-elt list 1))
(setq link (phpinspect-ll-link list link-value))
(should (eq link-value (phpinspect-llnode-value link)))))
(ert-deftest phpinspect-ll-insert-right ()
(let ((list (phpinspect-make-ll))
(link-value)
(link))
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "c" list)
(setq link-value (seq-elt list 1))
(setq link (phpinspect-ll-link list link-value))
(phpinspect-ll-insert-right link "aba")
(should (string= "aba" (seq-elt list 2)))
(should (string= "a" (seq-elt list 3)))))
(ert-deftest phpinspect-ll-insert-left ()
(let ((list (phpinspect-make-ll))
(link-value)
(link))
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "c" list)
(setq link-value (seq-elt list 1))
(setq link (phpinspect-ll-link list link-value))
(phpinspect-ll-insert-left link "aba")
(should (string= "aba" (seq-elt list 1)))
(should (string= "c" (seq-elt list 0)))
(should (string= "b" (seq-elt list 2)))))
(ert-deftest phpinspect-ll-seq-into ()
(let ((list (phpinspect-make-ll)))
(phpinspect-ll-push "d" list)
(phpinspect-ll-push "c" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "a" list)
(should (equal '("a" "b" "c" "d") (seq-into list 'list)))))
(ert-deftest phpinspect-ll-seq-take-while ()
(let ((list (phpinspect-make-ll))
(result))
(phpinspect-ll-push "bla" list)
(phpinspect-ll-push "foo" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(setq result (seq-take-while (lambda (a) (string= a "a"))
list))
(seq-map (lambda (a) (should (string= a "a")))
result)
(should (string= "aaa" (apply #'concat (seq-into result 'list))))))
(ert-deftest phpinspect-ll-seq-take-while-subset ()
"seq-take-while should also work from a different start link than
the start of the list."
(let ((list (phpinspect-make-ll))
(start-link)
(result))
(phpinspect-ll-push "bla" list)
(phpinspect-ll-push "foo" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(phpinspect-ll-push "a" list)
(setq start-link (phpinspect-ll-link list (seq-elt list 2)))
(setq result (seq-take-while (lambda (a) (string= a "a"))
start-link))
(seq-map (lambda (a) (should (string= a "a")))
result)
(should (string= "aaa" (apply #'concat (seq-into result 'list))))))
(ert-deftest phpinspect-ll-seq-find ()
(let ((list (phpinspect-make-ll)))
(phpinspect-ll-push "d" list)
(phpinspect-ll-push "c" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "a" list)
(should (string= "c" (seq-find (lambda (c) (string= "c" c)) list)))))
(ert-deftest phpinspect-ll-link ()
(let ((list (phpinspect-make-ll))
(value1 "a")
(value2 "b")
(value3 "c")
(value4 "d"))
(phpinspect-ll-push value1 list)
(should (phpinspect-ll-link list value1))
(phpinspect-ll-push value2 list)
(should (phpinspect-ll-link list value2))
(phpinspect-ll-insert-right (phpinspect-ll-link list value1) value3)
(should (phpinspect-ll-link list value3))
(phpinspect-ll-insert-left (phpinspect-ll-link list value3) value4)
(should (phpinspect-ll-link list value3))))
(ert-deftest phpinspect-ll-seq-length ()
(let ((list (phpinspect-make-ll)))
(phpinspect-ll-push "d" list)
(phpinspect-ll-push "c" list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "a" list)
(should (= 4 (seq-length list)))))
(ert-deftest phpinspect-slice-detach ()
(let ((list (phpinspect-make-ll))
(val1 "c")
(slice)
(detached-list))
(phpinspect-ll-push "d" list)
(phpinspect-ll-push val1 list)
(phpinspect-ll-push "b" list)
(phpinspect-ll-push "a" list)
(setq slice (phpinspect-make-slice :start list
:end (phpinspect-ll-link list val1)))
(setq detached-list (phpinspect-slice-detach slice))
(should-not (eq detached-list list))
(should (string= "d" (apply #'concat (seq-into list 'list))))
(should (string= "abc" (apply #'concat (seq-into detached-list 'list))))))
(ert-deftest phpinspect-tree-insert-enclosing-node ()
(let ((tree (phpinspect-make-tree :start 10 :end 100))
(node (phpinspect-make-tree :start 9 :end 200)))
(phpinspect-tree-insert-node tree node)
(should (= 9 (phpinspect-tree-start tree)))
(should (= 200 (phpinspect-tree-end tree)))
(should (= 10 (phpinspect-tree-start node)))
(should (= 100 (phpinspect-tree-end node)))
(should (eq node (phpinspect-llnode-value
(phpinspect-tree-children tree))))))
(ert-deftest phpinspect-tree-insert-enclosing-node-into-tree-with-parent ()
(let* ((parent (phpinspect-make-tree :start 0 :end 200))
(tree (phpinspect-make-tree :start 10 :end 100))
(node (phpinspect-make-tree :start 11 :end 50)))
(phpinspect-tree-insert-node parent tree)
(phpinspect-tree-insert-node tree node)
(should (eq parent (phpinspect-tree-parent tree)))
(should (eq tree (phpinspect-tree-parent node)))
(should (eq node (phpinspect-llnode-value
(phpinspect-tree-children tree))))))
(ert-deftest phpinspect-tree-insert-nested ()
(let ((tree (phpinspect-make-tree :start 0 :end 500))
(node1 (phpinspect-make-tree :start 9 :end 200))
(node2 (phpinspect-make-tree :start 20 :end 200))
(node3 (phpinspect-make-tree :start 9 :end 20))
(node4 (phpinspect-make-tree :start 21 :end 44)))
(phpinspect-tree-insert-node tree node1)
(phpinspect-tree-insert-node tree node2)
(phpinspect-tree-insert-node tree node3)
(phpinspect-tree-insert-node tree node4)
(should (= 0 (phpinspect-tree-start tree)))
(should (= 500 (phpinspect-tree-end tree)))
(should (= 1 (seq-length (phpinspect-tree-children tree))))
(let ((firstchild (seq-elt (phpinspect-tree-children tree) 0)))
(should (eq node1 firstchild))
(should (= 2 (seq-length (phpinspect-tree-children firstchild))))
(should (eq node3 (seq-elt (phpinspect-tree-children firstchild) 0)))
(should (eq node2 (seq-elt (phpinspect-tree-children firstchild) 1))))
(should (eq node4 (seq-elt (phpinspect-tree-children node2) 0)))))
(ert-deftest phpinspect-tree-insert-returns-node ()
"Because returning things from lisp functions can be kind of a hassle sometimes ;).
Tests whether phpinspect-tree-insert-node actually returns the
correct node (the one that the nodes values were stored in, or
the node iteself if it has been stored intact)."
(let* ((tree (phpinspect-make-tree :start 0 :end 500))
(node1 (phpinspect-make-tree :start 0 :end 800))
(node2 (phpinspect-make-tree :start 20 :end 200))
(node3 (phpinspect-make-tree :start 9 :end 20))
(node4 (phpinspect-make-tree :start 21 :end 44))
(node1-return (phpinspect-tree-insert-node tree node1))
(node2-return (phpinspect-tree-insert-node tree node2))
(node3-return (phpinspect-tree-insert-node tree node3))
(node4-return (phpinspect-tree-insert-node tree node4)))
(should (eq tree node1-return))
(should (= 800 (phpinspect-tree-end tree)))
(should (eq node2 node2-return))
(should (= 20 (phpinspect-tree-start node2-return)))
(should (eq node3 node3-return))
(should (= 9 (phpinspect-tree-start node3-return)))
(should (eq node4 node4-return))
(should (= 21 (phpinspect-tree-start node4-return)))))
(ert-deftest phpinspect-tree-traverse-overlapping-point ()
(let ((tree (phpinspect-make-tree :start 0 :end 500 :value "tree"))
(node1 (phpinspect-make-tree :start 9 :end 200 :value "node1"))
(node2 (phpinspect-make-tree :start 20 :end 200 :value "node2"))
(node3 (phpinspect-make-tree :start 9 :end 20 :value "node3"))
(node4 (phpinspect-make-tree :start 21 :end 44 :value "node4"))
(result))
(phpinspect-tree-insert-node tree node1)
(phpinspect-tree-insert-node tree node2)
(phpinspect-tree-insert-node tree node3)
(phpinspect-tree-insert-node tree node4)
(setq result (phpinspect-tree-traverse-overlappig tree 22))
(should (equal '("node4" "node2" "node1" "tree") result))))
(ert-deftest phpinspect-tree-find-smallest-overlapping-set ()
(let ((tree (phpinspect-make-tree :start 0 :end 500 :value "tree"))
(node1 (phpinspect-make-tree :start 9 :end 200 :value "node1"))
(node2 (phpinspect-make-tree :start 20 :end 200 :value "node2"))
(node3 (phpinspect-make-tree :start 44 :end 60 :value "node3"))
(node4 (phpinspect-make-tree :start 21 :end 44 :value "node4"))
(result))
(phpinspect-tree-insert-node tree node1)
(phpinspect-tree-insert-node tree node2)
(phpinspect-tree-insert-node tree node3)
(phpinspect-tree-insert-node tree node4)
(should (phpinspect-tree-overlaps tree (phpinspect-make-region 24 55)))
(setq result (phpinspect-tree-find-smallest-overlapping-set
tree (phpinspect-make-region 24 55)))
(should (equal '("node4" "node3") result))))
Loading…
Cancel
Save