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.
phpinspect.el/phpinspect-cache.el

291 lines
12 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)))
;;; phpinspect.el ends here
(provide 'phpinspect-cache)