You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
664 lines
30 KiB
EmacsLisp
664 lines
30 KiB
EmacsLisp
;;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2021-2023 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:
|
|
|
|
(require 'phpinspect-project)
|
|
(require 'phpinspect-autoload)
|
|
(require 'phpinspect-worker)
|
|
|
|
(defcustom phpinspect-load-stubs t
|
|
"If and when phpinspect should load code stubs."
|
|
:type '(choice
|
|
(const
|
|
:tag
|
|
"Load stubs on first mode init." t)
|
|
(const
|
|
:tag
|
|
"Never load stubs." nil))
|
|
:group 'phpinspect)
|
|
|
|
(defvar phpinspect-buffers (make-hash-table :test #'eq)
|
|
"All buffers for which `phpinspect-mode' is currently active.
|
|
|
|
Hash table with buffer (native emacs buffer object, `bufferp') as
|
|
key, and a reset-function as value. The reset-function is called
|
|
without arguments when the cache is purged (see
|
|
`phpinspect-purge-cache'.")
|
|
|
|
(defun phpinspect-register-current-buffer (reset-func)
|
|
(puthash (current-buffer) reset-func phpinspect-buffers))
|
|
|
|
(defun phpinspect-unregister-current-buffer ()
|
|
(remhash (current-buffer) phpinspect-buffers))
|
|
|
|
(defvar phpinspect-stub-cache nil
|
|
"An instance of `phpinspect--cache' containing an index of PHP
|
|
functions and classes which phpinspect preloads. This index is
|
|
not supposed to be mutated after initial creation.")
|
|
|
|
(defmacro phpinspect--cache-edit (cache &rest body)
|
|
(declare (indent 1))
|
|
`(unless (phpinspect--cache-read-only-p ,cache)
|
|
,@body))
|
|
|
|
(defvar phpinspect-cache nil
|
|
"An object used to store and access metadata of PHP projects.")
|
|
|
|
(cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache))
|
|
(read-only-p nil
|
|
:type boolean
|
|
:documentation
|
|
"Whether this cache instance is read-only, meaning that it's data
|
|
should never be changed.
|
|
|
|
When the value of this slot is non-nil:
|
|
|
|
- Actions that would normally mutate it's data should become
|
|
no-ops.
|
|
- All projects that are retrieved from it should be marked as read-only as well.")
|
|
(extra-class-retriever nil
|
|
:type lambda
|
|
:documentation
|
|
"A function that should accept a `phpinspect--type' and return
|
|
matching `phpinspect--class' instances or nil. Used to discover
|
|
classes that are defined outside of code that this cache knows about.")
|
|
(extra-function-retriever nil
|
|
:type lambda
|
|
:documentation
|
|
"A function that should accept a `phpinspect-name' (see
|
|
`phpinspect-intern-name') and return matching
|
|
`phpinspect--function' instances or nil. Used to discover
|
|
functions that are defined outside of code that this cache knows
|
|
about.")
|
|
(projects (make-hash-table :test 'equal :size 10)
|
|
:type hash-table
|
|
:documentation
|
|
"A `hash-table` with the root directories of projects
|
|
as keys and project caches as values."))
|
|
|
|
(defun phpinspect--get-stub-class (fqn)
|
|
(when phpinspect-stub-cache
|
|
(phpinspect--log "Getting stub class")
|
|
(catch 'return
|
|
(maphash (lambda (_name project)
|
|
(when-let ((class (phpinspect-project-get-class project fqn)))
|
|
(throw 'return class)))
|
|
(phpinspect--cache-projects phpinspect-stub-cache)))))
|
|
|
|
(defun phpinspect--get-stub-function (name)
|
|
(when phpinspect-stub-cache
|
|
(if name
|
|
(catch 'return
|
|
(phpinspect--log "Getting stub function by name %s" name)
|
|
(maphash (lambda (_name project)
|
|
(when-let ((class (phpinspect-project-get-function project name)))
|
|
(throw 'return class)))
|
|
(phpinspect--cache-projects phpinspect-stub-cache)))
|
|
(let* ((funcs (cons nil nil))
|
|
(funcs-rear funcs))
|
|
(phpinspect--log "Retrieving all stub functions for nil name")
|
|
(maphash (lambda (_name project)
|
|
(setq funcs-rear (last (nconc funcs-rear (phpinspect-project-get-functions project)))))
|
|
(phpinspect--cache-projects phpinspect-stub-cache))
|
|
(cdr funcs)))))
|
|
|
|
(defun phpinspect--get-or-create-global-cache ()
|
|
"Get `phpinspect-cache'.
|
|
If its value is nil, it is created and then returned."
|
|
(or phpinspect-cache
|
|
(setq phpinspect-cache
|
|
(phpinspect--make-cache
|
|
:extra-class-retriever #'phpinspect--get-stub-class
|
|
:extra-function-retriever #'phpinspect--get-stub-function))))
|
|
|
|
(defun phpinspect-purge-cache ()
|
|
"Assign a fresh, empty cache object to `phpinspect-cache'.
|
|
This effectively purges any cached code information from all
|
|
currently opened projects."
|
|
(interactive)
|
|
(when phpinspect-cache
|
|
;; Allow currently known cached projects to cleanup after themselves
|
|
(maphash (lambda (_ project)
|
|
(phpinspect-project-purge project))
|
|
(phpinspect--cache-projects phpinspect-cache)))
|
|
|
|
|
|
(maphash (lambda (buffer reset-hook)
|
|
(with-current-buffer buffer
|
|
(funcall reset-hook)))
|
|
phpinspect-buffers)
|
|
|
|
;; Assign a fresh cache object
|
|
(setq phpinspect-cache (phpinspect--get-or-create-global-cache))
|
|
(setq phpinspect-names (phpinspect-make-name-hash))
|
|
(phpinspect-define-standard-types))
|
|
|
|
(cl-defmethod phpinspect--cache-get-project
|
|
((cache phpinspect--cache) (project-root string))
|
|
(let ((project (gethash project-root (phpinspect--cache-projects cache))))
|
|
(when (and project (phpinspect--cache-read-only-p cache)
|
|
(not (phpinspect-project-read-only-p project)))
|
|
(setf (phpinspect-project-read-only-p project) t))
|
|
|
|
project))
|
|
|
|
(defun phpinspect-get-or-create-cached-project-class (project-root class-fqn)
|
|
(when project-root
|
|
(let ((project (phpinspect--cache-get-project-create
|
|
(phpinspect--get-or-create-global-cache)
|
|
project-root)))
|
|
(phpinspect-project-get-class-extra-or-create project class-fqn))))
|
|
|
|
(cl-defmethod phpinspect--cache-get-project-create
|
|
((cache phpinspect--cache) (project-root string))
|
|
"Get a project that is located in PROJECT-ROOT from CACHE.
|
|
If no such project exists in the cache yet, it is created and
|
|
then returned."
|
|
(let ((project (phpinspect--cache-get-project cache project-root)))
|
|
(unless project
|
|
(phpinspect--cache-edit cache
|
|
(setq project
|
|
(puthash project-root
|
|
(phpinspect--make-project
|
|
:fs (phpinspect-make-fs)
|
|
:root project-root
|
|
:extra-class-retriever (phpinspect--cache-extra-class-retriever cache)
|
|
:extra-function-retriever (phpinspect--cache-extra-function-retriever cache)
|
|
:worker (phpinspect-make-dynamic-worker))
|
|
(phpinspect--cache-projects cache)))
|
|
(let ((autoloader (phpinspect-make-autoloader
|
|
:fs (phpinspect-project-fs project)
|
|
:file-indexer (phpinspect-project-make-file-indexer project)
|
|
:project-root-resolver (phpinspect-project-make-root-resolver project))))
|
|
(setf (phpinspect-project-autoload project) autoloader)
|
|
(phpinspect-autoloader-refresh autoloader)
|
|
(phpinspect-project-enqueue-include-dirs project))))
|
|
project))
|
|
|
|
(defun phpinspect-project-enqueue-include-dirs (project)
|
|
(interactive (list (phpinspect--cache-get-project-create
|
|
(phpinspect--get-or-create-global-cache)
|
|
(phpinspect-current-project-root))))
|
|
(phpinspect-project-edit project
|
|
(let ((dirs (alist-get 'include-dirs
|
|
(alist-get (phpinspect-project-root project)
|
|
phpinspect-projects
|
|
nil nil #'string=))))
|
|
(dolist (dir dirs)
|
|
(phpinspect-message "enqueueing dir %s" dir)
|
|
(phpinspect-worker-enqueue
|
|
(phpinspect-project-worker project)
|
|
(phpinspect-make-index-dir-task :dir dir :project project))))))
|
|
|
|
(defun phpinspect-project-add-include-dir (dir)
|
|
"Configure DIR as an include dir for the current project."
|
|
(interactive (list (read-directory-name "Include Directory: ")))
|
|
(custom-set-variables '(phpinspect-projects))
|
|
(let ((existing
|
|
(alist-get (phpinspect-current-project-root) phpinspect-projects nil #'string=)))
|
|
(if existing
|
|
(push dir (alist-get 'include-dirs existing))
|
|
(push `(,(phpinspect-current-project-root) . ((include-dirs . (,dir)))) phpinspect-projects)))
|
|
|
|
(customize-save-variable 'phpinspect-projects phpinspect-projects)
|
|
|
|
(phpinspect-project-enqueue-include-dirs (phpinspect--cache-get-project-create
|
|
(phpinspect--get-or-create-global-cache)
|
|
(phpinspect-current-project-root))))
|
|
|
|
(defconst phpinspect-stub-directory
|
|
(expand-file-name "stubs" (file-name-directory (macroexp-file-name)))
|
|
"Directory where PHP stub files are located.")
|
|
|
|
(defconst phpinspect-data-directory
|
|
(expand-file-name "data" (file-name-directory (macroexp-file-name)))
|
|
"Directory for data distributed with phpinspect.")
|
|
|
|
(defconst phpinspect-stub-cache-file
|
|
(expand-file-name "builtin-stubs.eld" phpinspect-data-directory)
|
|
"")
|
|
|
|
(defconst phpinspect-builtin-index-file
|
|
(expand-file-name (concat "builtin-stubs-index.eld" (if (zlib-available-p) ".gz" ""))
|
|
phpinspect-data-directory)
|
|
"")
|
|
|
|
(defun phpinspect-build-stub-cache ()
|
|
(let* ((cache (phpinspect--make-cache))
|
|
(builtin-project (phpinspect--cache-get-project-create cache "builtins"))
|
|
(phpinspect-worker 'nil-worker))
|
|
(phpinspect-project-add-index builtin-project (phpinspect-build-stub-index))))
|
|
|
|
(defun phpinspect-build-stub-index ()
|
|
(phpinspect--index-tokens (phpinspect-parse-file (expand-file-name "builtins.php" phpinspect-stub-directory))))
|
|
|
|
(defun phpinspect-dump-stub-index ()
|
|
(interactive)
|
|
(let* ((phpinspect-names (phpinspect-make-name-hash))
|
|
(index (phpinspect-build-stub-index)))
|
|
(with-temp-buffer
|
|
(let ((print-length nil)
|
|
(print-level nil)
|
|
(print-circle t))
|
|
|
|
(prin1 (list (cons 'names phpinspect-names)
|
|
(cons 'index index))
|
|
(current-buffer))
|
|
(write-file phpinspect-builtin-index-file)))))
|
|
|
|
(defun phpinspect-load-stub-index ()
|
|
(interactive)
|
|
(unless (file-exists-p phpinspect-builtin-index-file)
|
|
(phpinspect-message "No stub index dump found, dumping stub index ...")
|
|
(phpinspect-dump-stub-index))
|
|
|
|
(let* ((data (with-temp-buffer
|
|
(insert-file-contents phpinspect-builtin-index-file)
|
|
(goto-char (point-min))
|
|
(read (current-buffer))))
|
|
(project (phpinspect--make-project :worker 'nil-worker)))
|
|
(phpinspect-purge-cache)
|
|
(setq phpinspect-names (alist-get 'names data))
|
|
(phpinspect-define-standard-types)
|
|
(setq phpinspect-stub-cache (phpinspect--make-cache))
|
|
(phpinspect-project-add-index project (alist-get 'index data))
|
|
(puthash "builtins" project (phpinspect--cache-projects phpinspect-stub-cache))
|
|
(setf (phpinspect--cache-read-only-p phpinspect-stub-cache) t)))
|
|
|
|
(cl-defstruct (phpinspect-cache (:constructor phpinspect-make-cache))
|
|
(groups (make-hash-table :test #'equal :size 2000 :rehash-size 1.2)))
|
|
|
|
(cl-defstruct (phpinspect-cache-type (:constructor phpinspect-make-cache-type))
|
|
(category nil)
|
|
(name nil)
|
|
(methods nil)
|
|
(variables nil))
|
|
|
|
(cl-defstruct (phpinspect-cache-namespace (:constructor phpinspect-make-cache-namespace))
|
|
(types nil)
|
|
(functions nil))
|
|
|
|
(cl-defstruct (phpinspect-cache-group (:constructor phpinspect-make-cache-group))
|
|
(namespaces (make-hash-table :test #'eq :size 2000 :rehash-size 2.0)))
|
|
|
|
(eval-and-compile
|
|
(defconst phpinspect-cache-types '(interface trait class method type
|
|
abstract-method function variable namespace)
|
|
"Types of entities that the cache can store.")
|
|
|
|
(defconst phpinspect-cache-containing-types '(class trait interface)
|
|
"Types of entities that the cache can store members for.")
|
|
|
|
(defconst phpinspect-cache-member-types '(method abstract-method function variable)
|
|
"Types of entities that the cache can store as members."))
|
|
|
|
(defun phpinspect-cache-group-get-namespace (group namespace)
|
|
(gethash namespace (phpinspect-cache-group-namespaces group)))
|
|
|
|
(defun phpinspect-cache-group-get-namespace-create (group namespace)
|
|
(or (phpinspect-cache-group-get-namespace group namespace)
|
|
(puthash namespace (phpinspect-make-cache-namespace)
|
|
(phpinspect-cache-group-namespaces group))))
|
|
|
|
(defun phpinspect-cache-namespace-get-type-create (namespace type category)
|
|
(or (phpinspect-cache-namespace-get-type namespace type category)
|
|
(push (cons (phpinspect--type-short-name type)
|
|
(phpinspect-make-cache-type :name (phpinspect--type-name-symbol type)
|
|
:category category))
|
|
(phpinspect-cache-namespace-types namespace))))
|
|
|
|
(defun phpinspect-cache-namespace-get-type (namespace type category)
|
|
(when-let ((entity
|
|
(cdr (assq (phpinspect--type-short-name type)
|
|
(phpinspect-cache-namespace-types namespace)))))
|
|
(and (or (eq 'type category)
|
|
(eq category (phpinspect-cache-type-category entity)))
|
|
entity)))
|
|
|
|
(defun phpinspect-cache-namespace-delete-type (namespace type category)
|
|
(let ((types (phpinspect-cache-namespace-types namespace))
|
|
(name (phpinspect--type-short-name type))
|
|
cell-before)
|
|
(catch 'break
|
|
(while types
|
|
(let ((type-cell (car types)))
|
|
(when (eq (car type-cell) name)
|
|
(when (or (eq 'type category)
|
|
(eq category (phpinspect-cache-type-category (cdr type-cell))))
|
|
(if cell-before
|
|
(setcdr cell-before (cdr types))
|
|
(setf (phpinspect-cache-namespace-types namespace) (cdr types)))
|
|
|
|
(throw 'break (cdr type-cell)))))
|
|
|
|
(setq cell-before types
|
|
types (cdr types))))))
|
|
|
|
(define-inline phpinspect-cache-query--do-delete (cache group param type member namespace implements extends)
|
|
(cl-assert (and (inline-const-p type) (symbolp (inline-const-val type))))
|
|
|
|
(let ((type (inline-const-val type))
|
|
delete-form)
|
|
(inline-letevals (group param member namespace implements extends)
|
|
(cond
|
|
((memq type (cons 'type phpinspect-cache-containing-types))
|
|
(if (and (inline-const-p param) (eq '* (inline-const-val param)))
|
|
(setq delete-form
|
|
(if namespace
|
|
(inline-quote
|
|
(when-let ((namespace (phpinspect-cache-group-get-namespace ,group ,namespace)))
|
|
(let (resultset)
|
|
(setf (phpinspect-cache-namespace-types namespace)
|
|
(seq-filter
|
|
(lambda (type-cell)
|
|
(if ,(if (eq type 'type) t `(eq (phpinspect-cache-type-category (cdr type-cell)) (quote ,type)))
|
|
(progn (push (cdr type-cell) resultset)
|
|
nil)
|
|
t))
|
|
(phpinspect-cache-namespace-types namespace)))
|
|
(cons 'phpinspect-cache-multiresult resultset))))
|
|
(inline-quote
|
|
(let (resultset)
|
|
(dolist (namespace (hash-table-values (phpinspect-cache-group-namespaces ,group)))
|
|
(let (new-types)
|
|
(dolist (type-cell (phpinspect-cache-namespace-types namespace))
|
|
(if ,(if (eq type 'type) t `(eq (phpinspect-cache-type-category (cdr type-cell)) (quote ,type)))
|
|
(push (cdr type-cell) resultset)
|
|
(push type-cell new-types)))
|
|
(setf (phpinspect-cache-namespace-types namespace) new-types)))
|
|
(cons 'phpinspect-cache-multiresult resultset)))))
|
|
(setq delete-form
|
|
(inline-quote
|
|
(let* ((namespace (phpinspect-cache-group-get-namespace-create
|
|
,group
|
|
(or ,namespace (phpinspect--type-namespace ,param)))))
|
|
(phpinspect-cache-namespace-delete-type namespace ,param (quote ,type)))))))
|
|
(t (inline-error "Delete not supported for entity type %s" type))))
|
|
delete-form))
|
|
|
|
(defun phpinspect-cache-namespace-get-function (namespace func-name)
|
|
(cdr (assq func-name (phpinspect-cache-namespace-functions namespace))))
|
|
|
|
(define-inline phpinspect-cache-query--do-insert (cache group param type member namespace implements extends)
|
|
(cl-assert (and (inline-const-p type) (symbolp (inline-const-val type))))
|
|
|
|
(let ((type (inline-const-val type))
|
|
register-form)
|
|
(inline-letevals (group param member namespace implements extends)
|
|
(cond
|
|
((memq type (cons 'type phpinspect-cache-containing-types))
|
|
|
|
(setq register-form
|
|
(inline-quote
|
|
(let* ((namespace (phpinspect-cache-group-get-namespace-create
|
|
,group
|
|
(or ,namespace (phpinspect--type-namespace ,param)))))
|
|
(phpinspect-cache-namespace-get-type-create namespace ,param (quote ,type))))))
|
|
((and (eq 'function type) (not member))
|
|
(setq register-form
|
|
(inline-quote
|
|
(let ((namespace (phpinspect-cache-group-get-namespace-create
|
|
,group (or ,namespace (phpinspect--function-namespace ,param)))))
|
|
(or (when-let ((existing (assq (phpinspect--function-name-symbol ,param)
|
|
(phpinspect-cache-namespace-functions namespace))))
|
|
(setcdr existing ,param))
|
|
(cdar (push (cons (phpinspect--function-name-symbol ,param) ,param)
|
|
(phpinspect-cache-namespace-functions namespace))))))))
|
|
(t (inline-error "Insert not supported for entity type %s" type))))
|
|
register-form))
|
|
|
|
(define-inline phpinspect-cache-query--do-get (cache group param type member namespace implements extends)
|
|
(cl-assert (and (inline-const-p type) (symbolp (inline-const-val type))))
|
|
|
|
(let ((type (inline-const-val type))
|
|
get-form)
|
|
(inline-letevals (group param member namespace implements extends)
|
|
(cond
|
|
((memq type (cons 'type phpinspect-cache-containing-types))
|
|
(if (and (inline-const-p param) (eq '* (inline-const-val param)))
|
|
(setq get-form
|
|
(if namespace
|
|
(inline-quote
|
|
(when-let ((namespace (phpinspect-cache-group-get-namespace ,group ,namespace)))
|
|
(cons 'phpinspect-cache-multiresult
|
|
(mapcar #'cdr (phpinspect-cache-namespace-types namespace)))))
|
|
(inline-quote
|
|
(cons 'phpinspect-cache-multiresult
|
|
(mapcan (lambda (namespace) (mapcar #'cdr (phpinspect-cache-namespace-types namespace)))
|
|
(hash-table-values (phpinspect-cache-group-namespaces ,group)))))))
|
|
(setq get-form
|
|
(inline-quote
|
|
(progn
|
|
(when-let* ((namespace (phpinspect-cache-group-get-namespace
|
|
,group
|
|
(or ,namespace (phpinspect--type-namespace ,param)))))
|
|
(phpinspect-cache-namespace-get-type namespace ,param (quote ,type))))))))
|
|
((and (eq 'function type) (not member))
|
|
(setq get-form
|
|
(if ,namespace
|
|
(inline-quote
|
|
(when-let ((namespace (phpinspect-cache-group-get-namespace ,group ,namespace)))
|
|
(phpinspect-cache-namespace-get-function namespace ,param)))
|
|
(inline-quote
|
|
(let (resultset)
|
|
(dolist (namespace (hash-table-values (phpinspect-cache-group-namespaces ,group)))
|
|
(when-let ((func (phpinspect-cache-namespace-get-function namespace ,param)))
|
|
(push func resultset)))
|
|
(cons 'phpinspect-cache-multiresult resultset))))
|
|
(t (inline-error "Get not supported for entity type %s" type))))
|
|
get-form))
|
|
|
|
(defmacro phpinspect-cache-query--wrap-action (action cache group param type member namespace implements extends)
|
|
(let ((cache-sym (gensym "cache"))
|
|
(group-sym (gensym "group"))
|
|
(param-sym (gensym "param"))
|
|
(member-sym (gensym "member"))
|
|
(namespace-sym (gensym "namespace"))
|
|
(implements-sym (gensym "implements"))
|
|
(extends-sym (gensym "extends")))
|
|
`(let ((,param-sym ,param))
|
|
(if (and (sequencep ,param-sym) (not (phpinspect-name-p ,param-sym)))
|
|
(let* ((,cache-sym ,cache)
|
|
(,group-sym ,group)
|
|
(,member-sym ,member)
|
|
(,namespace-sym ,namespace)
|
|
(,implements-sym ,implements)
|
|
(,extends-sym ,extends)
|
|
(result (cons 'phpinspect-cache-multiresult nil))
|
|
(result-rear result))
|
|
(seq-doseq (p ,param-sym)
|
|
(when-let ((action-result
|
|
(,action ,cache-sym ,group-sym p ,type ,member-sym ,namespace-sym ,implements-sym ,extends-sym)))
|
|
(setq result-rear
|
|
(setcdr result-rear
|
|
(cons action-result nil)))))
|
|
result)
|
|
(,action ,cache ,group ,param ,type ,member ,namespace ,implements ,extends)))))
|
|
|
|
(defun phpinspect-cache-query--compile-groups (cache group-specs intent)
|
|
(cl-assert (phpinspect-cache-p cache))
|
|
(let (groups)
|
|
|
|
(if group-specs
|
|
(dolist (spec group-specs)
|
|
(cl-assert (listp spec))
|
|
(cl-assert (memq (car spec) '(project label))
|
|
t "Spec car must be the symbol `project' or `label'")
|
|
(let ((group (gethash spec (phpinspect-cache-groups cache))))
|
|
(when (and (eq :insert intent) (not group))
|
|
(setq group (puthash spec (phpinspect-make-cache-group) (phpinspect-cache-groups cache))))
|
|
(push group groups)))
|
|
(if (eq :insert intent)
|
|
(error "Cannot insert without defining cache group")
|
|
(setq groups (hash-table-values (phpinspect-cache-groups cache)))))
|
|
|
|
groups))
|
|
|
|
(define-inline phpinspect-cache-transact (cache group-specs &rest query)
|
|
(declare (indent 2))
|
|
(cl-assert (listp query))
|
|
|
|
(let (type intent intent-param member namespace implements extends key value)
|
|
(condition-case err
|
|
(progn
|
|
(while (setq key (pop query))
|
|
(cl-assert (keywordp key) t "Query keys must be keywords, %s provided" key)
|
|
(setq value (pop query))
|
|
(cl-assert value t "Key %s has no value" key)
|
|
|
|
(pcase key
|
|
((or :insert :get :delete)
|
|
(when intent
|
|
(inline-error "Defined duplicate intent: %s, %s" intent key))
|
|
(setq intent key
|
|
intent-param value))
|
|
(:as (cl-assert (and (inline-const-p value)
|
|
(memq (inline-const-val value) phpinspect-cache-types))
|
|
t ":type must be one of %s" phpinspect-cache-types)
|
|
(setq type (inline-const-val value)))
|
|
(:member-of (setq member value))
|
|
(:in (setq namespace value))
|
|
(:implementing (setq implements value))
|
|
(:extending (setq extends value))
|
|
(_ (error "Unexpected query keyword %s" key))))
|
|
|
|
;; Query validation
|
|
(unless type
|
|
(error "Providing entity type with keyword :as is required."))
|
|
|
|
(when (and member (not (memq type phpinspect-cache-member-types)))
|
|
(error "Keyword :member-of can only be used for types %s" phpinspect-cache-member-types))
|
|
|
|
(when (and extends (not (memq type '(class trait interface type))))
|
|
(error "Keyword :extending cannot be used for types other than %s" '(class trait interface)))
|
|
|
|
(when (eq :insert intent)
|
|
(when (and (memq type '(variable method abstract-method)) (not member))
|
|
(error "Variable and methods must be member of %s." phpinspect-cache-containing-types))
|
|
|
|
(when (eq 'type type)
|
|
(error ":as 'type cannot be used for insertions.")))
|
|
|
|
(when (and intent (not intent-param))
|
|
(error "Intent %s must have a parameter %s" intent)))
|
|
(t (inline-error "phpinspect-cache-transact: %s" err)))
|
|
|
|
|
|
(inline-letevals (group-specs intent-param cache type member namespace implements extends)
|
|
(let* ((action-args `(,intent-param (quote ,type) ,member ,namespace ,implements ,extends))
|
|
(action (pcase intent
|
|
(:insert 'phpinspect-cache-query--do-insert)
|
|
(:delete 'phpinspect-cache-query--do-delete)
|
|
(:get 'phpinspect-cache-query--do-get)
|
|
(_ (inline-error "Invalid intent %s" intent)))))
|
|
(inline-quote
|
|
(let ((groups (phpinspect-cache-query--compile-groups ,cache ,group-specs ,intent))
|
|
resultset)
|
|
,(cons 'phpinspect-cache-query--validate (cons intent action-args))
|
|
(dolist (group groups)
|
|
(when-let ((result ,(cons 'phpinspect-cache-query--wrap-action
|
|
(cons action (cons cache (cons 'group action-args))))))
|
|
(if (and (listp result) (eq 'phpinspect-cache-multiresult (car result)))
|
|
(setq resultset (nconc resultset (cdr result)))
|
|
(push result resultset))))
|
|
resultset))))))
|
|
|
|
(defun phpinspect-cache-query--validate (intent intent-param type member namespace implements extends)
|
|
(and
|
|
;; Validate intent-param
|
|
(cond
|
|
((phpinspect--type-p intent-param)
|
|
(cond
|
|
((not (memq type '(class trait interface type)))
|
|
(error "Cannot use intent-param of type phpinspect--type when querying for %s" type))
|
|
((and (phpinspect--type-fully-qualified intent-param)
|
|
namespace)
|
|
(error "Use of fully qualified type %s while specifying namespace %s in query"
|
|
intent-param namespace)))
|
|
t)
|
|
((phpinspect-name-p intent-param) t)
|
|
((listp intent-param)
|
|
(if (memq type '(class trait interface type))
|
|
(unless (seq-every-p #'phpinspect--type-p intent-param)
|
|
(error "Each intent param must be of type `phpinspect--type'. Got: %s" intent-param))
|
|
(pcase intent
|
|
(:insert
|
|
(unless (or (seq-every-p #'phpinspect--variable-p intent-param)
|
|
(seq-every-p #'phpinspect--function-p intent-param))
|
|
(error "Each intent param must be an instance of `phpinspect--function' or `phpinspect--variable'")))
|
|
(:get
|
|
(unless (seq-every-p #'phpinspect-name-p intent-param)
|
|
(error "Each intent param must be a name, got: %s" intent-param)))
|
|
(:delete
|
|
(unless (or (seq-every-p #'phpinspect-name-p intent-param)
|
|
(seq-every-p #'phpinspect--variable-p intent-param)
|
|
(seq-every-p #'phpinspect--function-p intent-param))
|
|
(error "Each intent param must be an instance of `phpinspect--function', `phpinspect--variable' or `phpinspect-name'")))))
|
|
t)
|
|
((eq '* intent-param)
|
|
(unless (memq intent '(:get :delete))
|
|
(error "Wildcard '* cannot be used with intent %s" intent)))
|
|
((phpinspect--function-p intent-param)
|
|
(cond
|
|
((not (memq intent '(:insert :delete)))
|
|
(error "`phpinspect--function' can only be used as parameter for :insert and :delete intents."))
|
|
((not (memq type '(function method abstract-method)))
|
|
(error "Inserting/deleting `phpinspect--function' as type %s is not supported" type)))
|
|
t)
|
|
(t (error "Unsupported intent-param %s" intent-param)))
|
|
;; Validate member
|
|
(cond
|
|
((phpinspect--type-p member) t)
|
|
((not member) t)
|
|
(t (error "unsupported member type (allowed `phpinspect--type'): %s" member)))
|
|
|
|
;; Validate namespace
|
|
(cond
|
|
((phpinspect-name-p namespace) t)
|
|
((not namespace) t)
|
|
(t (error "unsupported namespace type (allowed `phpinspect-name'): %s" namespace)))
|
|
|
|
;; Validate implements
|
|
(cond
|
|
((listp implements)
|
|
(unless (seq-every-p #'phpinspect--type-p implements)
|
|
(error "Each parameter of :implementing must be of type `phpinspect--type'. Got: %s" implements))
|
|
t)
|
|
((phpinspect--type-p implements) t)
|
|
((not implements) t)
|
|
(t (error "unsupported parameter for :implementing (allowed `phpinspect--type': %s" implements)))
|
|
|
|
;; Validate extends
|
|
(cond
|
|
((listp extends)
|
|
(unless (seq-every-p #'phpinspect--type-or-name-p extends)
|
|
(error "Each parameter of :extending must be of type `phpinspect--type'. Got: %s" extends))
|
|
t)
|
|
((phpinspect--type-p extends) t)
|
|
((not extends) t)
|
|
(t (error "unsupported parameter for :extending (allowed `phpinspect--type': %s" extends)))))
|
|
|
|
;;; phpinspect.el ends here
|
|
(provide 'phpinspect-cache)
|