Implement parser interruption on user input
ci/woodpecker/push/woodpecker Pipeline was successful Details

WIP-cache
Hugo Thunnissen 10 months ago
parent 91e24b97d4
commit 5548734ef7

@ -55,6 +55,7 @@ linked with."
(let* ((map (phpinspect-make-bmap))
(buffer-map (phpinspect-buffer-map buffer))
(ctx (phpinspect-make-pctx
:interrupt-predicate #'input-pending-p
:bmap map
:incremental t
:previous-bmap buffer-map
@ -81,6 +82,16 @@ linked with."
(cl-defmethod phpinspect-buffer-register-edit
((buffer phpinspect-buffer) (start integer) (end integer) (pre-change-length integer))
"Mark a region of the buffer as edited."
;; Take into account "atoms" (tokens without clear delimiters like words,
;; variables and object attributes. The meaning of these tokens will change as
;; they grow or shrink, so their ful regions need to be marked for a reparse).
(save-excursion
(goto-char start)
(when (looking-back "\\($->|::\\)?[^][)(}{[:blank:]\n;'\"]+" nil t)
(setq start (- start (length (match-string 0))))))
(phpinspect-edtrack-register-edit
(phpinspect-buffer-edit-tracker buffer) start end pre-change-length))

@ -37,6 +37,69 @@
(define-inline phpinspect--word-end-regex ()
(inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)")))
(defvar phpinspect-parse-context nil
"An instance of `phpinspect-pctx' that is used when
parsing. Usually used in combination with
`phpinspect-with-parse-context'")
(defmacro phpinspect-with-parse-context (ctx &rest body)
(declare (indent 1))
(let ((old-ctx phpinspect-parse-context))
`(unwind-protect
(progn
(setq phpinspect-parse-context ,ctx)
,@body)
(setq phpinspect-parse-context ,old-ctx))))
(cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
"Parser Context"
(incremental nil)
(interrupt-threshold (time-convert '(2 . 1000))
:documentation
"After how much time `interrupt-predicate'
should be polled. This is 2ms by default.")
(-start-time nil
:documentation "The time at which the parse started.
This variable is for private use and not always set.")
(interrupt-predicate nil
:documentation
"A function that is called in intervals during parsing when
set. If this function returns a non-nil value, the parse process
is interrupted and the symbol `phpinspect-parse-interrupted' is
thrown.")
(edtrack nil
:type phpinspect-edtrack)
(bmap (phpinspect-make-bmap)
:type phpinspect-bmap)
(previous-bmap nil
:type phpinspect-bmap)
(whitespace-before ""
:type string))
(defsubst phpinspect-pctx-check-interrupt (pctx)
(unless (phpinspect-pctx--start-time pctx)
(setf (phpinspect-pctx--start-time pctx) (time-convert nil)))
;; 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)))
(throw 'phpinspect-parse-interrupted nil)))
(defsubst phpinspect-pctx-register-token (pctx token start end)
(phpinspect-bmap-register
(phpinspect-pctx-bmap pctx) start end token (phpinspect-pctx-consume-whitespace pctx)))
(defsubst phpinspect-pctx-register-whitespace (pctx whitespace)
(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-list-handlers ()
(let ((handlers))
(mapatoms (lambda (handler)
@ -370,9 +433,14 @@ parser function is then returned in byte-compiled form."
(if (and phpinspect-parse-context
(phpinspect-pctx-incremental phpinspect-parse-context))
(let ((func (phpinspect-parser-compile-incremental (symbol-value parser-symbol))))
(lambda (&rest arguments)
(apply func phpinspect-parse-context arguments)))
(if (phpinspect-pctx-interrupt-predicate phpinspect-parse-context)
(lambda (&rest arguments)
(phpinspect-pctx-check-interrupt phpinspect-parse-context)
(apply func phpinspect-parse-context arguments))
(lambda (&rest arguments)
(apply func phpinspect-parse-context arguments))))
(or (symbol-function parser-symbol)
(defalias parser-symbol
(phpinspect-parser-compile (symbol-value parser-symbol)))))))
@ -457,43 +525,6 @@ token is \";\", which marks the end of a statement in PHP."
;; Return
tokens)))))
(defvar phpinspect-parse-context nil
"An instance of `phpinspect-pctx' that is used when
parsing. Usually used in combination with
`phpinspect-with-parse-context'")
(defmacro phpinspect-with-parse-context (ctx &rest body)
(declare (indent 1))
(let ((old-ctx phpinspect-parse-context))
`(unwind-protect
(progn
(setq phpinspect-parse-context ,ctx)
,@body)
(setq phpinspect-parse-context ,old-ctx))))
(cl-defstruct (phpinspect-pctx (:constructor phpinspect-make-pctx))
"Parser Context"
(incremental nil)
(edtrack nil
:type phpinspect-edtrack)
(bmap (phpinspect-make-bmap)
:type phpinspect-bmap)
(previous-bmap nil
:type phpinspect-bmap)
(whitespace-before ""
:type string))
(defsubst phpinspect-pctx-register-token (pctx token start end)
(phpinspect-bmap-register
(phpinspect-pctx-bmap pctx) start end token (phpinspect-pctx-consume-whitespace pctx)))
(defsubst phpinspect-pctx-register-whitespace (pctx whitespace)
(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-make-incremental-parser-function (tree-type handler-list &optional delimiter-predicate)
"Like `phpinspect-make-parser-function', but returned function is able to reuse an already parsed tree."
@ -516,6 +547,7 @@ parsing. Usually used in combination with
(previous-bmap (phpinspect-pctx-previous-bmap context))
(edtrack (phpinspect-pctx-edtrack context))
(taint-iterator (when edtrack (phpinspect-edtrack-make-taint-iterator edtrack)))
(check-interrupt (phpinspect-pctx-interrupt-predicate context))
;; Loop variables
(start-position)
@ -551,6 +583,9 @@ parsing. Usually used in combination with
(goto-char current-end-position)
(when check-interrupt
(phpinspect-pctx-check-interrupt context))
;; Skip over whitespace after so that we don't do a full
;; run down all of the handlers during the next iteration
(when (looking-at (phpinspect-handler-regexp 'whitespace))

@ -282,87 +282,88 @@ TODO:
- Respect `eldoc-echo-area-use-multiline-p`
- This function is too big and has repetitive code. Split up and simplify.
"
(let* ((token-map (phpinspect-buffer-parse-map phpinspect-current-buffer))
(resolvecontext (phpinspect-get-resolvecontext token-map (point)))
(parent-token (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(enclosing-token (cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(statement (phpinspect--resolvecontext-subject resolvecontext))
(arg-list)
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext))
(static))
(phpinspect--log "Eldoc statement before checking outside list: %s" statement)
(when (and (phpinspect-list-p parent-token) enclosing-token)
(setq statement
(phpinspect-find-statement-before-point
token-map (phpinspect-bmap-token-meta token-map enclosing-token)
(phpinspect-meta-end
(phpinspect-bmap-token-meta token-map parent-token)))))
(phpinspect--log "Enclosing token: %s" enclosing-token)
(phpinspect--log "Eldoc statement: %s" statement)
(setq arg-list (seq-find #'phpinspect-list-p (reverse statement)))
(when (and (phpinspect-list-p arg-list)
enclosing-token
(or (phpinspect-object-attrib-p (car (last statement 2)))
(setq static (phpinspect-static-attrib-p (car (last statement 2))))))
;; Set resolvecontext subject to the last statement in the enclosing token, minus
;; the method name. The last enclosing token is an incomplete list, so point is
;; likely to be at a location inside a method call like "$a->b->doSomething(". The
;; resulting subject would be "$a->b".
(setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token (butlast statement 2)))
(let* ((type-of-previous-statement
(phpinspect-resolve-type-from-context resolvecontext type-resolver))
(method-name-sym (phpinspect-intern-name (cadr (cadar (last statement 2)))))
(class (phpinspect-project-get-class-create
(phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect--resolvecontext-project-root resolvecontext))
type-of-previous-statement))
(method (when class
(if static
(phpinspect--class-get-static-method class method-name-sym)
(catch 'phpinspect-parse-interrupted
(let* ((token-map (phpinspect-buffer-parse-map phpinspect-current-buffer))
(resolvecontext (phpinspect-get-resolvecontext token-map (point)))
(parent-token (car (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(enclosing-token (cadr (phpinspect--resolvecontext-enclosing-tokens
resolvecontext)))
(statement (phpinspect--resolvecontext-subject resolvecontext))
(arg-list)
(type-resolver (phpinspect--make-type-resolver-for-resolvecontext
resolvecontext))
(static))
(phpinspect--log "Eldoc statement before checking outside list: %s" statement)
(when (and (phpinspect-list-p parent-token) enclosing-token)
(setq statement
(phpinspect-find-statement-before-point
token-map (phpinspect-bmap-token-meta token-map enclosing-token)
(phpinspect-meta-end
(phpinspect-bmap-token-meta token-map parent-token)))))
(phpinspect--log "Enclosing token: %s" enclosing-token)
(phpinspect--log "Eldoc statement: %s" statement)
(setq arg-list (seq-find #'phpinspect-list-p (reverse statement)))
(when (and (phpinspect-list-p arg-list)
enclosing-token
(or (phpinspect-object-attrib-p (car (last statement 2)))
(setq static (phpinspect-static-attrib-p (car (last statement 2))))))
;; Set resolvecontext subject to the last statement in the enclosing token, minus
;; the method name. The last enclosing token is an incomplete list, so point is
;; likely to be at a location inside a method call like "$a->b->doSomething(". The
;; resulting subject would be "$a->b".
(setf (phpinspect--resolvecontext-subject resolvecontext)
(phpinspect--get-last-statement-in-token (butlast statement 2)))
(let* ((type-of-previous-statement
(phpinspect-resolve-type-from-context resolvecontext type-resolver))
(method-name-sym (phpinspect-intern-name (cadr (cadar (last statement 2)))))
(class (phpinspect-project-get-class-create
(phpinspect--cache-get-project-create
(phpinspect--get-or-create-global-cache)
(phpinspect--resolvecontext-project-root resolvecontext))
type-of-previous-statement))
(method (when class
(if static
(phpinspect--class-get-static-method class method-name-sym)
(phpinspect--class-get-method class method-name-sym)))))
(phpinspect--log "Eldoc method name: %s" method-name-sym)
(phpinspect--log "Eldoc type of previous statement: %s"
type-of-previous-statement)
(phpinspect--log "Eldoc method: %s" method)
(when method
(let ((arg-count -1)
(comma-count
(length (seq-filter #'phpinspect-comma-p arg-list))))
(concat (truncate-string-to-width
(phpinspect--function-name method) phpinspect-eldoc-word-width) ": ("
(mapconcat
(lambda (arg)
(setq arg-count (+ arg-count 1))
(if (= arg-count comma-count)
(propertize (concat
"$"
(truncate-string-to-width
(car arg)
phpinspect-eldoc-word-width)
" "
(phpinspect--format-type-name (or (cadr arg) "")))
'face 'eldoc-highlight-function-argument)
(concat "$"
(truncate-string-to-width (car arg)
phpinspect-eldoc-word-width)
(if (cadr arg) " " "")
(phpinspect--format-type-name (or (cadr arg) "")))))
(phpinspect--function-arguments method)
", ")
"): "
(phpinspect--format-type-name
(phpinspect--function-return-type method)))))))))
(phpinspect--log "Eldoc method name: %s" method-name-sym)
(phpinspect--log "Eldoc type of previous statement: %s"
type-of-previous-statement)
(phpinspect--log "Eldoc method: %s" method)
(when method
(let ((arg-count -1)
(comma-count
(length (seq-filter #'phpinspect-comma-p arg-list))))
(concat (truncate-string-to-width
(phpinspect--function-name method) phpinspect-eldoc-word-width) ": ("
(mapconcat
(lambda (arg)
(setq arg-count (+ arg-count 1))
(if (= arg-count comma-count)
(propertize (concat
"$"
(truncate-string-to-width
(car arg)
phpinspect-eldoc-word-width)
" "
(phpinspect--format-type-name (or (cadr arg) "")))
'face 'eldoc-highlight-function-argument)
(concat "$"
(truncate-string-to-width (car arg)
phpinspect-eldoc-word-width)
(if (cadr arg) " " "")
(phpinspect--format-type-name (or (cadr arg) "")))))
(phpinspect--function-arguments method)
", ")
"): "
(phpinspect--format-type-name
(phpinspect--function-return-type method))))))))))
(cl-defstruct (phpinspect--assignment
(:constructor phpinspect--make-assignment))
@ -974,9 +975,6 @@ level of a token. Nested variables are ignored."
(resolvecontext &optional static)
"Suggest object or class attributes at point.
TOKEN-TREE must be a syntax tree containing enough context to
infer the types of the preceding statements
RESOLVECONTEXT must be a structure of the type
`phpinspect--resolvecontext'. The PHP type of its subject is
resolved to provide completion candidates.
@ -1097,22 +1095,23 @@ static variables and static methods."
arg)))
(insert "(")))
((eq command 'candidates)
(let ((completion-list (phpinspect--make-completion-list))
(candidates))
(dolist (completion (phpinspect--suggest-at-point))
(phpinspect--completion-list-add
completion-list
(phpinspect--make-completion completion)))
(setq candidates
(seq-filter (lambda (completion)
(when completion
(string-match (concat "^" (regexp-quote arg))
completion)))
(phpinspect--completion-list-strings
completion-list)))
(setq phpinspect--last-completion-list completion-list)
candidates))
(catch 'phpinspect-parse-interrupted
(let ((completion-list (phpinspect--make-completion-list))
(candidates))
(dolist (completion (phpinspect--suggest-at-point))
(phpinspect--completion-list-add
completion-list
(phpinspect--make-completion completion)))
(setq candidates
(seq-filter (lambda (completion)
(when completion
(string-match (concat "^" (regexp-quote arg))
completion)))
(phpinspect--completion-list-strings
completion-list)))
(setq phpinspect--last-completion-list completion-list)
candidates)))
((eq command 'annotation)
(concat " " (phpinspect--completion-annotation
(phpinspect--completion-list-get-metadata

Loading…
Cancel
Save