From e07e1ed9e6659512695b6e175719f8c4211ca2c2 Mon Sep 17 00:00:00 2001 From: Hugo Thunnissen Date: Fri, 22 Apr 2022 10:30:25 +0200 Subject: [PATCH] WIP: Split code up into separate files --- phpinspect-cache.el | 63 ++ phpinspect-class.el | 168 +++++ phpinspect-index.el | 395 ++++++++++ phpinspect-parser.el | 828 +++++++++++++++++++++ phpinspect-project.el | 92 +++ phpinspect-type.el | 187 +++++ phpinspect-util.el | 66 ++ phpinspect.el | 1641 +---------------------------------------- 8 files changed, 1806 insertions(+), 1634 deletions(-) create mode 100644 phpinspect-cache.el create mode 100644 phpinspect-class.el create mode 100644 phpinspect-index.el create mode 100644 phpinspect-parser.el create mode 100644 phpinspect-project.el create mode 100644 phpinspect-type.el create mode 100644 phpinspect-util.el diff --git a/phpinspect-cache.el b/phpinspect-cache.el new file mode 100644 index 0000000..f546068 --- /dev/null +++ b/phpinspect-cache.el @@ -0,0 +1,63 @@ +;;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(require 'phpinspect-project) + +(cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache)) + (active-projects nil + :type alist + :documentation + "An `alist` that contains the root directory + paths of all currently active phpinspect + projects") + (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.")) + +(cl-defgeneric phpinspect--cache-getproject + ((cache phpinspect--cache) (project-name string)) + "Get project by PROJECT-NAME that is located in CACHE.") + +(cl-defmethod phpinspect--cache-getproject + ((cache phpinspect--cache) (project-root string)) + (gethash project-root (phpinspect--cache-projects cache))) + +(cl-defgeneric 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.") + +(cl-defmethod phpinspect--cache-get-project-create + ((cache phpinspect--cache) (project-root string)) + (or (phpinspect--cache-getproject cache project-root) + (puthash project-root + (phpinspect--make-project-cache) + (phpinspect--cache-projects cache)))) + +(provide 'phpinspect-cache) +;;; phpinspect.el ends here diff --git a/phpinspect-class.el b/phpinspect-class.el new file mode 100644 index 0000000..d03a21a --- /dev/null +++ b/phpinspect-class.el @@ -0,0 +1,168 @@ +;;; phpinspect-class.el --- PHP parsing module -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(require 'phpinspect-type) + +(cl-defstruct (phpinspect--class (:constructor phpinspect--make-class-generated)) + (project nil + :type phpinspect--project + :documentaton + "The project that this class belongs to") + (index nil + :type phpinspect--indexed-class + :documentation + "The index that this class is derived from") + (methods (make-hash-table :test 'eq :size 20 :rehash-size 20) + :type hash-table + :documentation + "All methods, including those from extended classes.") + (static-methods (make-hash-table :test 'eq :size 20 :rehash-size 20) + :type hash-table + :documentation + "All static methods this class provides, + including those from extended classes.") + (variables nil + :type list + :documentation + "Variables that belong to this class.") + (extended-classes (make-hash-table :test 'eq) + :type hash-table + :documentation + "All extended/implemented classes.") + (subscriptions nil + :type list + :documentation + "A list of subscription functions that should be + called whenever anything about this class is + updated")) + +(cl-defmethod phpinspect--class-trigger-update ((class phpinspect--class)) + (dolist (sub (phpinspect--class-subscriptions class)) + (funcall sub class))) + +(cl-defmethod phpinspect--class-set-index ((class phpinspect--class) + (index (head phpinspect--indexed-class))) + (setf (phpinspect--class-index class) index) + (dolist (method (alist-get 'methods index)) + (phpinspect--class-update-method class method)) + + (dolist (method (alist-get 'static-methods index)) + (phpinspect--class-update-static-method class method)) + + (setf (phpinspect--class-variables class) + (alist-get 'variables index)) + + (setf (phpinspect--class-extended-classes class) + (seq-filter + #'phpinspect--class-p + (mapcar + (lambda (class-name) + (phpinspect--project-get-class-create (phpinspect--class-project class) + class-name)) + `(,@(alist-get 'implements index) ,@(alist-get 'extends index))))) + + (dolist (extended (phpinspect--class-extended-classes class)) + (phpinspect--class-incorporate class extended) + (phpinspect--class-subscribe class extended)) + + (phpinspect--class-trigger-update class)) + +(cl-defmethod phpinspect--class-get-method ((class phpinspect--class) method-name) + (gethash method-name (phpinspect--class-methods class))) + +(cl-defmethod phpinspect--class-get-static-method ((class phpinspect--class) method-name) + (gethash method-name (phpinspect--class-static-methods class))) + +(cl-defmethod phpinspect--class-set-method ((class phpinspect--class) + (method phpinspect--function)) + (phpinspect--log "Adding method by name %s to class" + (phpinspect--function-name method)) + (puthash (phpinspect--function-name-symbol method) + method + (phpinspect--class-methods class))) + +(cl-defmethod phpinspect--class-set-static-method ((class phpinspect--class) + (method phpinspect--function)) + (puthash (phpinspect--function-name-symbol method) + method + (phpinspect--class-static-methods class))) + +(cl-defmethod phpinspect--class-get-method-return-type + ((class phpinspect--class) (method-name symbol)) + (let ((method (phpinspect--class-get-method class method-name))) + (when method + (phpinspect--function-return-type method)))) + +(cl-defmethod phpinspect--class-get-method-list ((class phpinspect--class)) + (let ((methods)) + (maphash (lambda (key method) + (push method methods)) + (phpinspect--class-methods class)) + methods)) + +(cl-defmethod phpinspect--class-update-static-method ((class phpinspect--class) + (method phpinspect--function)) + (let ((existing (gethash (phpinspect--function-name-symbol method) + (phpinspect--class-static-methods class)))) + (if existing + (progn + (unless (eq (phpinspect--function-return-type method) + phpinspect--null-type) + (setf (phpinspect--function-return-type existing) + (phpinspect--function-return-type method)) + (setf (phpinspect--function-arguments existing) + (phpinspect--function-arguments method)))) + (phpinspect--class-set-static-method class method)))) + +(cl-defmethod phpinspect--class-update-method ((class phpinspect--class) + (method phpinspect--function)) + (let ((existing (gethash (phpinspect--function-name-symbol method) + (phpinspect--class-methods class)))) + (if existing + (progn + (unless (eq (phpinspect--function-return-type method) + phpinspect--null-type) + (setf (phpinspect--function-return-type existing) + (phpinspect--function-return-type method)) + (setf (phpinspect--function-arguments existing) + (phpinspect--function-arguments method)))) + (phpinspect--class-set-method class method)))) + +(cl-defmethod phpinspect--class-incorporate ((class phpinspect--class) + (other-class phpinspect--class)) + (maphash (lambda (k method) + (phpinspect--class-update-method class method)) + (phpinspect--class-methods other-class))) + +(cl-defmethod phpinspect--class-subscribe ((class phpinspect--class) + (subscription-class phpinspect--class)) + (let ((update-function + (lambda (new-class) + (phpinspect--class-incorporate class new-class) + (phpinspect--class-trigger-update class)))) + (push update-function (phpinspect--class-subscriptions subscription-class)))) + +(provide 'phpinspect-class) +;;; phpinspect-class.el ends here diff --git a/phpinspect-index.el b/phpinspect-index.el new file mode 100644 index 0000000..9211ed9 --- /dev/null +++ b/phpinspect-index.el @@ -0,0 +1,395 @@ +;;; phpinspect-index.el --- PHP parsing and completion package -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(require 'phpinspect-util) +(require 'phpinspect-type) + +(defun phpinspect--function-from-scope (scope) + (cond ((and (phpinspect-static-p (cadr scope)) + (phpinspect-function-p (caddr scope))) + (caddr scope)) + ((phpinspect-function-p (cadr scope)) + (cadr scope)) + (t nil))) + +(defun phpinspect-var-annotation-p (token) + (phpinspect-token-type-p token :var-annotation)) + +(defun phpinspect-return-annotation-p (token) + (phpinspect-token-type-p token :return-annotation)) + +(defun phpinspect--index-function-arg-list (type-resolver arg-list) + (let ((arg-index) + (current-token) + (arg-list (cl-copy-list arg-list))) + (while (setq current-token (pop arg-list)) + (cond ((and (phpinspect-word-p current-token) + (phpinspect-variable-p (car arg-list))) + (push `(,(cadr (pop arg-list)) + ,(funcall type-resolver (phpinspect--make-type :name (cadr current-token)))) + arg-index)) + ((phpinspect-variable-p (car arg-list)) + (push `(,(cadr (pop arg-list)) + nil) + arg-index)))) + (nreverse arg-index))) + +(defun phpinspect--index-function-from-scope (type-resolver scope comment-before) + (let* ((php-func (cadr scope)) + (declaration (cadr php-func)) + (type (if (phpinspect-word-p (car (last declaration))) + (phpinspect--make-type :name (cadar (last declaration)))))) + + ;; @return annotation. Has precedence over typehint when dealing with a collection. + (let* ((is-collection + (when type + (member (phpinspect--type-name + (funcall type-resolver type)) phpinspect-collection-types))) + (return-annotation-type + (when (or (not type) is-collection) + (cadadr + (seq-find #'phpinspect-return-annotation-p + comment-before))))) + (phpinspect--log "found return annotation %s" return-annotation-type) + + (when return-annotation-type + (cond ((not type) + (setq type (funcall type-resolver + (phpinspect--make-type :name return-annotation-type)))) + (is-collection + (phpinspect--log "Detected collection type in: %s" scope) + (setf (phpinspect--type-contains type) + (funcall type-resolver + (phpinspect--make-type :name return-annotation-type))) + (setf (phpinspect--type-collection type) t))))) + + (phpinspect--make-function + :scope `(,(car scope)) + :name (cadadr (cdr declaration)) + :return-type (if type (funcall type-resolver type) + phpinspect--null-type) + :arguments (phpinspect--index-function-arg-list + type-resolver + (phpinspect-function-argument-list php-func))))) + +(defun phpinspect--index-const-from-scope (scope) + (phpinspect--make-variable + :scope `(,(car scope)) + :name (cadr (cadr (cadr scope))))) + +(defun phpinspect--var-annotations-from-token (token) + (seq-filter #'phpinspect-var-annotation-p token)) + +(defun phpinspect--index-variable-from-scope (type-resolver scope comment-before) + "Index the variable inside `scope`." + (let* ((var-annotations (phpinspect--var-annotations-from-token comment-before)) + (variable-name (cadr (cadr scope))) + (type (if var-annotations + ;; Find the right annotation by variable name + (or (cadr (cadr (seq-find (lambda (annotation) + (string= (cadr (caddr annotation)) variable-name)) + var-annotations))) + ;; Give up and just use the last one encountered + (cadr (cadr (car (last var-annotations)))))))) + (phpinspect--log "calling resolver from index-variable-from-scope") + (phpinspect--make-variable + :name variable-name + :scope `(,(car scope)) + :type (if type (funcall type-resolver (phpinspect--make-type :name type)))))) + +(defun phpinspect-doc-block-p (token) + (phpinspect-token-type-p token :doc-block)) + +(defun phpinspect--get-class-name-from-token (class-token) + (let ((subtoken (seq-find (lambda (word) + (and (phpinspect-word-p word) + (not (string-match + (concat "^" (phpinspect-handler-regexp 'class-keyword)) + (concat (cadr word) " "))))) + (cadr class-token)))) + (cadr subtoken))) + +(defun phpinspect--index-class (type-resolver class) + "Create an alist with relevant attributes of a parsed class." + (phpinspect--log "INDEXING CLASS") + (let ((methods) + (static-methods) + (static-variables) + (variables) + (constants) + (extends) + (implements) + (class-name (phpinspect--get-class-name-from-token class)) + ;; Keep track of encountered comments to be able to use type + ;; annotations. + (comment-before)) + + ;; Find out what the class extends or implements + (let ((enc-extends nil) + (enc-implements nil)) + (dolist (word (cadr class)) + (if (phpinspect-word-p word) + (cond ((string= (cadr word) "extends") + (phpinspect--log "Class %s extends other classes" class-name) + (setq enc-extends t)) + ((string= (cadr word) "implements") + (setq enc-extends nil) + (phpinspect--log "Class %s implements in interface" class-name) + (setq enc-implements t)) + (t + (phpinspect--log "Calling Resolver from index-class on %s" (cadr word)) + (cond (enc-extends + (push (funcall type-resolver (phpinspect--make-type + :name (cadr word))) + extends)) + (enc-implements + (push (funcall type-resolver (phpinspect--make-type + :name (cadr word))) + implements)))))))) + + (dolist (token (caddr class)) + (cond ((phpinspect-scope-p token) + (cond ((phpinspect-const-p (cadr token)) + (push (phpinspect--index-const-from-scope token) constants)) + + ((phpinspect-variable-p (cadr token)) + (push (phpinspect--index-variable-from-scope type-resolver + token + comment-before) + variables)) + + ((phpinspect-static-p (cadr token)) + (cond ((phpinspect-function-p (cadadr token)) + (push (phpinspect--index-function-from-scope type-resolver + (list (car token) + (cadadr token)) + comment-before) + static-methods)) + + ((phpinspect-variable-p (cadadr token)) + (push (phpinspect--index-variable-from-scope type-resolver + (list (car token) + (cadadr token)) + comment-before) + static-variables)))) + (t + (phpinspect--log "comment-before is: %s" comment-before) + (push (phpinspect--index-function-from-scope type-resolver + token + comment-before) + methods)))) + ((phpinspect-static-p token) + (cond ((phpinspect-function-p (cadr token)) + (push (phpinspect--index-function-from-scope type-resolver + `(:public + ,(cadr token)) + comment-before) + static-methods)) + + ((phpinspect-variable-p (cadr token)) + (push (phpinspect--index-variable-from-scope type-resolver + `(:public + ,(cadr token)) + comment-before) + static-variables)))) + ((phpinspect-const-p token) + ;; Bare constants are always public + (push (phpinspect--index-const-from-scope (list :public token)) + constants)) + ((phpinspect-function-p token) + ;; Bare functions are always public + (push (phpinspect--index-function-from-scope type-resolver + (list :public token) + comment-before) + methods)) + ((phpinspect-doc-block-p token) + (phpinspect--log "setting comment-before %s" token) + (setq comment-before token)) + + ;; Prevent comments from sticking around too long + (t + (phpinspect--log "Unsetting comment-before") + (setq comment-before nil)))) + + ;; Dirty hack that assumes the constructor argument names to be the same as the object + ;; attributes' names. + ;;; + ;; TODO: actually check the types of the variables assigned to object attributes + (let* ((constructor-sym (phpinspect-intern-name "__construct")) + (constructor (seq-find (lambda (method) + (eq (phpinspect--function-name-symbol method) + constructor-sym)) + methods))) + (when constructor + (phpinspect--log "Constructor was found") + (dolist (variable variables) + (when (not (phpinspect--variable-type variable)) + (phpinspect--log "Looking for variable type in constructor arguments (%s)" + variable) + (let ((constructor-parameter-type + (car (alist-get (phpinspect--variable-name variable) + (phpinspect--function-arguments constructor) + nil nil #'string=)))) + (if constructor-parameter-type + (setf (phpinspect--variable-type variable) + (funcall type-resolver constructor-parameter-type)))))))) + + (let ((class-name (funcall type-resolver (phpinspect--make-type :name class-name)))) + `(,class-name . + (phpinspect--indexed-class + (methods . ,methods) + (class-name . ,class-name) + (static-methods . ,static-methods) + (static-variables . ,static-variables) + (variables . ,variables) + (constants . ,constants) + (extends . ,extends) + (implements . ,implements)))))) + +(defsubst phpinspect-namespace-body (namespace) + "Return the nested tokens in NAMESPACE tokens' body. +Accounts for namespaces that are defined with '{}' blocks." + (if (phpinspect-block-p (caddr namespace)) + (cdaddr namespace) + (cdr namespace))) + +(defun phpinspect--index-classes (types classes &optional namespace indexed) + "Index the class tokens in `classes`, using the types in `types` +as Fully Qualified names. `namespace` will be assumed the root +namespace if not provided" + (if classes + (let ((class (pop classes))) + (push (phpinspect--index-class + (phpinspect--make-type-resolver types class namespace) + class) + indexed) + (phpinspect--index-classes types classes namespace indexed)) + (nreverse indexed))) + +(defun phpinspect--use-to-type (use) + (let* ((fqn (cadr (cadr use))) + (type (phpinspect--make-type :name fqn :fully-qualified t)) + (type-name (if (and (phpinspect-word-p (caddr use)) + (string= "as" (cadr (caddr use)))) + (cadr (cadddr use)) + (progn (string-match "[^\\]+$" fqn) + (match-string 0 fqn))))) + (cons (phpinspect-intern-name type-name) type))) + +(defun phpinspect--uses-to-types (uses) + (mapcar #'phpinspect--use-to-type uses)) + +(defun phpinspect--index-namespace (namespace) + (phpinspect--index-classes + (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace)) + (seq-filter #'phpinspect-class-p namespace) + (cadadr namespace))) + +(defun phpinspect--index-namespaces (namespaces &optional indexed) + (if namespaces + (progn + (push (phpinspect--index-namespace (pop namespaces)) indexed) + (phpinspect--index-namespaces namespaces indexed)) + (apply #'append (nreverse indexed)))) + +(defun phpinspect--index-functions (&rest _args) + "TODO: implement function indexation. This is a stub function.") + +(defun phpinspect--index-tokens (tokens) + "Index TOKENS as returned by `phpinspect--parse-current-buffer`." + `(phpinspect--root-index + ,(append + (append '(classes) + (phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)) + (phpinspect--index-classes + (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens)) + (seq-filter #'phpinspect-class-p tokens)))) + (functions)) + ;; TODO: Implement function indexation + ) + +;; (defun phpinspect--get-or-create-index-for-class-file (class-fqn) +;; (phpinspect--log "Getting or creating index for %s" class-fqn) +;; (phpinspect-get-or-create-cached-project-class +;; (phpinspect-project-root) +;; class-fqn)) + +(defun phpinspect-index-file (file-name) + (phpinspect--index-tokens (phpinspect-parse-file file-name))) + +(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-create project class-fqn)))) + + ;; (let ((existing-index (phpinspect-get-cached-project-class + ;; project-root + ;; class-fqn))) + ;; (or + ;; existing-index + ;; (progn + ;; (let* ((class-file (phpinspect-class-filepath class-fqn)) + ;; (visited-buffer (when class-file (find-buffer-visiting class-file))) + ;; (new-index)) + + ;; (phpinspect--log "No existing index for FQN: %s" class-fqn) + ;; (phpinspect--log "filepath: %s" class-file) + ;; (when class-file + ;; (if visited-buffer + ;; (setq new-index (with-current-buffer visited-buffer + ;; (phpinspect--index-current-buffer))) + ;; (setq new-index (phpinspect-index-file class-file))) + ;; (dolist (class (alist-get 'classes new-index)) + ;; (when class + ;; (phpinspect-cache-project-class + ;; project-root + ;; (cdr class)))) + ;; (alist-get class-fqn (alist-get 'classes new-index) + ;; nil + ;; nil + ;; #'phpinspect--type=)))))))) + + +(defun phpinspect--index-current-buffer () + (phpinspect--index-tokens (phpinspect-parse-current-buffer))) + +(defun phpinspect-index-current-buffer () + "Index a PHP file for classes and the methods they have" + (phpinspect--index-tokens (phpinspect-parse-current-buffer))) + +;; (defun phpinspect--get-variables-for-class (buffer-classes class &optional static) +;; (let ((class-index (or (assoc-default class buffer-classes #'phpinspect--type=) +;; (phpinspect--get-or-create-index-for-class-file class)))) +;; (when class-index +;; (if static +;; (append (alist-get 'static-variables class-index) +;; (alist-get 'constants class-index)) +;; (alist-get 'variables class-index))))) + + +(provide 'phpinspect-index) +;;; phpinspect-index.el ends here diff --git a/phpinspect-parser.el b/phpinspect-parser.el new file mode 100644 index 0000000..9d2609b --- /dev/null +++ b/phpinspect-parser.el @@ -0,0 +1,828 @@ +;;; phpinspect-parser.el --- PHP parsing module -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(defvar phpinspect-parser-obarray (obarray-make) + "An obarray containing symbols for all phpinspect (sub)parsers.") + +(defvar phpinspect-handler-obarray (obarray-make) + "An obarray containing symbols for all phpinspect parser handlers.") + +(eval-when-compile + (define-inline phpinspect--word-end-regex () + (inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)"))) + +(defun phpinspect-list-handlers () + (let ((handlers)) + (mapatoms (lambda (handler) + (push (symbol-name handler) handlers)) + phpinspect-handler-obarray) + handlers)) + +(defun phpinspect-describe-handler (handler-name) + "Display a buffer containing HANDLER-NAMEs docstring and attribute-plist." + (interactive (list (completing-read "Pick a handler:" (phpinspect-list-handlers)))) + (with-current-buffer (get-buffer-create "phpinspect-handler-description") + (insert (concat + (pp (symbol-value (intern handler-name phpinspect-handler-obarray))) + "\n" + (documentation (intern handler-name phpinspect-handler-obarray)))) + (pop-to-buffer (current-buffer)))) + +(defsubst phpinspect--strip-last-char (string) + (substring string 0 (- (length string) 1))) + +(defsubst phpinspect-munch-token-without-attribs (string token-keyword) + "Return a token of type TOKEN-KEYWORD with STRING as value. +If STRING has text properties, they are stripped." + (let ((value (copy-sequence string)) + (length (length string))) + (forward-char length) + (set-text-properties 0 length nil value) + (list token-keyword value))) + + +(defsubst phpinspect-token-type-p (object type) + "Returns t if OBJECT is a token of type TYPE. +Type can be any of the token types returned by +`phpinspect-parse-buffer-until-point`" + (and (listp object) (eq (car object) type))) + +(defsubst phpinspect-object-attrib-p (token) + (phpinspect-token-type-p token :object-attrib)) + +(defsubst phpinspect-static-attrib-p (token) + (phpinspect-token-type-p token :static-attrib)) + +(defsubst phpinspect-attrib-p (token) + (or (phpinspect-object-attrib-p token) + (phpinspect-static-attrib-p token))) + +(defun phpinspect-html-p (token) + (phpinspect-token-type-p token :html)) + +(defun phpinspect-comma-p (token) + (phpinspect-token-type-p token :comma)) + +(defsubst phpinspect-terminator-p (token) + (phpinspect-token-type-p token :terminator)) + +(defsubst phpinspect-end-of-token-p (token) + (or (phpinspect-terminator-p token) + (phpinspect-comma-p token) + (phpinspect-html-p token))) + +(defsubst phpinspect-end-of-statement-p (token) + (or (phpinspect-end-of-token-p token) + (phpinspect-block-p token))) + +(defsubst phpinspect-incomplete-block-p (token) + (phpinspect-token-type-p token :incomplete-block)) + +(defsubst phpinspect-block-p (token) + (or (phpinspect-token-type-p token :block) + (phpinspect-incomplete-block-p token))) + +(defun phpinspect-end-of-use-p (token) + (or (phpinspect-block-p token) + (phpinspect-end-of-token-p token))) + +(defun phpinspect-static-p (token) + (phpinspect-token-type-p token :static)) + +(defsubst phpinspect-incomplete-const-p (token) + (phpinspect-token-type-p token :incomplete-const)) + +(defsubst phpinspect-const-p (token) + (or (phpinspect-token-type-p token :const) + (phpinspect-incomplete-const-p token))) + +(defsubst phpinspect-scope-p (token) + (or (phpinspect-token-type-p token :public) + (phpinspect-token-type-p token :private) + (phpinspect-token-type-p token :protected))) + +(defsubst phpinspect-namespace-p (object) + (phpinspect-token-type-p object :namespace)) + +(defun phpinspect-incomplete-class-p (token) + (and (phpinspect-class-p token) + (phpinspect-incomplete-block-p (car (last token))))) + +(defun phpinspect-incomplete-namespace-p (token) + (and (phpinspect-namespace-p token) + (or (phpinspect-incomplete-block-p (car (last token))) + (phpinspect-incomplete-class-p (car (last token)))))) + +(defun phpinspect-function-p (token) + (phpinspect-token-type-p token :function)) + + +(defun phpinspect-class-p (token) + (phpinspect-token-type-p token :class)) + +(defun phpinspect-incomplete-method-p (token) + (or (phpinspect-incomplete-function-p token) + (and (phpinspect-scope-p token) + (phpinspect-incomplete-function-p (car (last token)))) + (and (phpinspect-scope-p token) + (phpinspect-static-p (car (last token))) + (phpinspect-incomplete-function-p (car (last (car (last token)))))) + (and (phpinspect-scope-p token) + (phpinspect-function-p (car (last token)))))) + +(defun phpinspect-incomplete-function-p (token) + (and (phpinspect-function-p token) + (phpinspect-incomplete-block-p (car (last token))))) + +(defsubst phpinspect-incomplete-list-p (token) + (phpinspect-token-type-p token :incomplete-list)) + +(defsubst phpinspect-list-p (token) + (or (phpinspect-token-type-p token :list) + (phpinspect-incomplete-list-p token))) + +(defun phpinspect-declaration-p (token) + (phpinspect-token-type-p token :declaration)) + +(defsubst phpinspect-assignment-p (token) + (phpinspect-token-type-p token :assignment)) + +(defun phpinspect-function-argument-list (php-func) + "Get the argument list of a function" + (seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil)) + +(defsubst phpinspect-variable-p (token) + (phpinspect-token-type-p token :variable)) + +(defsubst phpinspect-word-p (token) + (phpinspect-token-type-p token :word)) + +(defsubst phpinspect-incomplete-array-p (token) + (phpinspect-token-type-p token :incomplete-array)) + +(defsubst phpinspect-array-p (token) + (or (phpinspect-token-type-p token :array) + (phpinspect-incomplete-array-p token))) + +(defsubst phpinspect-incomplete-token-p (token) + (or (phpinspect-incomplete-class-p token) + (phpinspect-incomplete-block-p token) + (phpinspect-incomplete-list-p token) + (phpinspect-incomplete-array-p token) + (phpinspect-incomplete-const-p token) + (phpinspect-incomplete-function-p token) + (phpinspect-incomplete-method-p token) + (phpinspect-incomplete-namespace-p token))) + +(defun phpinspect--static-terminator-p (token) + (or (phpinspect-function-p token) + (phpinspect-end-of-token-p token))) + +(defun phpinspect--scope-terminator-p (token) + (or (phpinspect-function-p token) + (phpinspect-end-of-token-p token) + (phpinspect-const-p token) + (phpinspect-static-p token))) + +(defun phpinspect-namespace-keyword-p (token) + (and (phpinspect-word-p token) (string= (car (last token)) "namespace"))) + +(defun phpinspect-use-keyword-p (token) + (and (phpinspect-word-p token) (string= (car (last token)) "use"))) + + +(defsubst phpinspect-root-p (object) + (phpinspect-token-type-p object :root)) + +(defsubst phpinspect-namespace-or-root-p (object) + (or (phpinspect-namespace-p object) + (phpinspect-root-p object))) + +(defun phpinspect-use-p (object) + (phpinspect-token-type-p object :use)) + +(defun phpinspect-comment-p (token) + (phpinspect-token-type-p token :comment)) + +(defsubst phpinspect-class-block (class) + (caddr class)) + +(defsubst phpinspect-namespace-block (namespace) + (when (and (= (length namespace) 3) + (phpinspect-block-p (caddr namespace))) + (caddr namespace))) + +(defsubst phpinspect-function-block (php-func) + (caddr php-func)) + +(defsubst phpinspect-not-class-p (token) + "Apply inverse of `phpinspect-class-p' to TOKEN." + (not (phpinspect-class-p token))) + +(defmacro phpinspect-defhandler (name arguments docstring attribute-plist &rest body) + "Define a parser handler that becomes available for use with phpinspect-parse. + +A parser handler is a function that is able to identify and parse +tokens from PHP code at `point` in the current buffer. It's +return value must be the resulting token. Aside from parsing it +has to manage the state of `point` in a way that it skips over +the tokens it has parsed. That way the next handler can +correctly pick up from where it has left off. + +Parser handlers are unrolled in a `cond` statement by +`phpinspect-make-parser`. The resulting code is something akin +to the following: + +(while ... + (cond (((looking-at \"{\") + (funcall block-handler (match-string 0) max-point) + ((looking-at \"\\$\") + (funcall variable-handler ... +etc. + +NAME must be a symbol. It does not need to be prefixed with a +\"namespace\" because parser handlers are stored in their own +obarray (`phpinspect-handler-obarray`). + +ARGUMENTS must an argument list as accepted by `lambda`. A +handler must be able to accept 2 arguments: START-TOKEN and +MAX-POINT. START-TOKEN is the match string that resulted from +the comparison of the handlers' `regexp` attribute with the text +at `point`. MAX-POINT is the point in the current buffer up +until which the parser is supposed to parse. For some tokens you +may not want/need to respect MAX-POINT, in which case you can +ignore it. + +DOCSTRING is mandatory. It should contain a description of the +tokens the handler is able to process and (if present) any +particularities of the handler. + +ATTRIBUTE-PLIST is a plist that must contain at least a `regexp` key. + Possible keys: + - regexp: The regular expression that marks the start of the token. + +BODY is a function body as accepted by `lambda` that parses the +text at point and returns the resulting token. + +When altering/adding handlers during runtime, make sure to purge +the parser cache to make sure that your new handler functions are used. +You can purge the parser cache with \\[phpinspect-purge-parser-cache]." + (declare (indent defun)) + (when (not (symbolp name)) + (error "In definition of phpinspect handler %s: NAME bust be a symbol" name)) + + (when (not (plist-member attribute-plist 'regexp)) + (error "In definition of phpinspect handler %s ATTRIBUTE-PLIST must contain key `regexp`" + name)) + + ;; Eval regexp. It might be a `concat` statement and we don't want to be executing that + ;; every time the parser advances one character and has to check for the regexp + ;; occurence. + (setq attribute-plist (plist-put attribute-plist 'regexp + (eval (plist-get attribute-plist 'regexp) + t))) + (let ((name (symbol-name name))) + `(progn + (set (intern ,name phpinspect-handler-obarray) (quote ,attribute-plist)) + (defalias (intern ,name phpinspect-handler-obarray) + #'(lambda (,@arguments) + ,docstring + ,@body)) + (unless (byte-code-function-p + (symbol-function + (intern ,name phpinspect-handler-obarray))) + (byte-compile (intern ,name phpinspect-handler-obarray)))))) + +(defun phpinspect-get-parser-create (tree-type &rest parser-parameters) + "Retrieve a parser for TREE-TYPE from `phpinspect-parser-obarray'. + +TREE-TYPE must be a symbol or keyword representing the type of +the token the parser is able to parse. + +If a parser by TREE-TYPE doesn't exist, it is created by callng +`phpinspect-make-parser` with TREE-TYPE as first argument and +PARSER-PARAMETERS as the rest of the arguments. The resulting +parser function is then returned in byte-compiled form." + (let* ((parser-name (symbol-name tree-type)) + (parser-symbol (intern-soft parser-name phpinspect-parser-obarray))) + (or (and parser-symbol (symbol-function parser-symbol)) + (defalias (intern parser-name phpinspect-parser-obarray) + (byte-compile (apply #'phpinspect-make-parser + `(,tree-type ,@parser-parameters))))))) + +(defun phpinspect-purge-parser-cache () + "Empty `phpinspect-parser-obarray`. + +This is useful when you need to change parser handlers or parsers +during runtime. Parsers are implemented with macros, so changing +handler functions without calling this function will often not +have any effect." + (interactive) + (setq phpinspect-parser-obarray (obarray-make))) + +(defun phpinspect-make-parser (tree-type handler-list &optional delimiter-predicate) + "Create a parser function using the handlers by names defined in HANDLER-LIST. + +See also `phpinspect-defhandler`. + +TREE-TYPE must be a symbol or a keyword representing the token +type. + +HANDLER-LIST must be a list of either symbol or string +representation of handler symbols which can be found in +`phpinspect-handler-obarray`. + +DELIMITER-PREDICATE must be a function. It is passed the last +parsed token after every handler iteration. If it evaluates to +something other than nil, parsing is deemed completed and the +loop exits. An example use case of this is to determine the end +of a statement. You can use `phpinspect-terminator-p` as +delimiter predicate and have parsing stop when the last parsed +token is \";\", which marks the end of a statement in PHP." + (let ((handlers (mapcar + (lambda (handler-name) + (let* ((handler-name (symbol-name handler-name)) + (handler (intern-soft handler-name phpinspect-handler-obarray))) + (if handler + handler + (error "No handler found by name \"%s\"" handler-name)))) + handler-list)) + (delimiter-predicate (if (symbolp delimiter-predicate) + `(quote ,delimiter-predicate) + delimiter-predicate))) + `(lambda (buffer max-point &optional continue-condition) + (with-current-buffer buffer + (let ((tokens) + (delimiter-predicate (when (functionp ,delimiter-predicate) ,delimiter-predicate))) + (while (and (< (point) max-point) + (if continue-condition (funcall continue-condition) t) + (not (if delimiter-predicate + (funcall delimiter-predicate (car (last tokens))) + nil))) + (cond ,@(mapcar + (lambda (handler) + `((looking-at ,(plist-get (symbol-value handler) 'regexp)) + (let ((token (funcall ,(symbol-function handler) + (match-string 0) + max-point))) + (when token + (if (null tokens) + (setq tokens (list token)) + (nconc tokens (list token))))))) + handlers) + (t (forward-char)))) + (push ,tree-type tokens)))))) + +(phpinspect-defhandler comma (comma &rest _ignored) + "Handler for comma tokens" + (regexp ",") + (phpinspect-munch-token-without-attribs comma :comma)) + +(phpinspect-defhandler word (word &rest _ignored) + "Handler for bareword tokens" + (regexp "[A-Za-z_\\][\\A-Za-z_0-9]*") + (let ((length (length word))) + (forward-char length) + (set-text-properties 0 length nil word) + (list :word word))) + +(defsubst phpinspect-handler (handler-name) + (intern-soft (symbol-name handler-name) phpinspect-handler-obarray)) + +(defsubst phpinspect-handler-regexp (handler-name) + (plist-get (symbol-value (phpinspect-handler handler-name)) 'regexp)) + +(defsubst phpinspect--parse-annotation-parameters (parameter-amount) + (let* ((words) + (word-handler (phpinspect-handler 'word)) + (word-regexp (phpinspect-handler-regexp 'word)) + (variable-handler (phpinspect-handler 'variable)) + (variable-regexp (phpinspect-handler-regexp 'variable))) + (while (not (or (looking-at "\\*/") (= (length words) parameter-amount))) + (forward-char) + (cond ((looking-at word-regexp) + (push (funcall word-handler (match-string 0)) words)) + ((looking-at variable-regexp) + (push (funcall variable-handler (match-string 0)) words)))) + (nreverse words))) + +(phpinspect-defhandler annotation (start-token &rest _ignored) + "Handler for in-comment @annotations" + (regexp "@") + (forward-char (length start-token)) + (if (looking-at (phpinspect-handler-regexp 'word)) + (let ((annotation-name (match-string 0))) + (forward-char (length annotation-name)) + (cond ((string= annotation-name "var") + ;; The @var annotation accepts 2 parameters: + ;; the type and the $variable name + (append (list :var-annotation) + (phpinspect--parse-annotation-parameters 2))) + ((string= annotation-name "return") + ;; The @return annotation only accepts 1 word as parameter: + ;; The return type + (append (list :return-annotation) + (phpinspect--parse-annotation-parameters 1))) + ((string= annotation-name "param") + ;; The @param annotation accepts 2 parameters: + ;; The type of the param, and the param's $name + (append (list :param-annotation) + (phpinspect--parse-annotation-parameters 2))) + (t + (list :annotation annotation-name)))) + (list :annotation nil))) + +(phpinspect-defhandler tag (start-token max-point) + "Handler that discards any inline HTML it encounters" + (regexp "\\?>") + (forward-char (length start-token)) + (or (re-search-forward "<\\?php\\|<\\?" nil t) + (goto-char max-point)) + (list :html)) + +(phpinspect-defhandler comment (start-token max-point) + "Handler for comments and doc blocks" + (regexp "#\\|//\\|/\\*") + (forward-char (length start-token)) + + (cond ((string-match "/\\*" start-token) + (let* ((continue-condition (lambda () (not (looking-at "\\*/")))) + (parser (phpinspect-get-parser-create + :doc-block + '(annotation whitespace))) + (doc-block (funcall parser (current-buffer) max-point continue-condition))) + (forward-char 2) + doc-block)) + (t + (let ((parser (phpinspect-get-parser-create :comment '(tag) #'phpinspect-html-p)) + (end-position (line-end-position))) + (funcall parser (current-buffer) end-position))))) + +(phpinspect-defhandler variable (start-token &rest _ignored) + "Handler for tokens indicating reference to a variable" + (regexp "\\$") + (forward-char (length start-token)) + (if (looking-at (phpinspect-handler-regexp 'word)) + (phpinspect-munch-token-without-attribs (match-string 0) :variable) + (list :variable nil))) + +(phpinspect-defhandler whitespace (whitespace &rest _ignored) + "Handler that discards whitespace" + (regexp "[[:blank:]]+") + (forward-char (length whitespace))) + +(phpinspect-defhandler equals (equals &rest _ignored) + "Handler for strict and unstrict equality comparison tokens." + (regexp "===?") + (phpinspect-munch-token-without-attribs equals :equals)) + +(phpinspect-defhandler assignment-operator (operator &rest _ignored) + "Handler for tokens indicating that an assignment is taking place" + (regexp "[+-]?=") + (phpinspect-munch-token-without-attribs operator :assignment)) + +(phpinspect-defhandler terminator (terminator &rest _ignored) + "Handler for statement terminators." + (regexp ";") + (phpinspect-munch-token-without-attribs terminator :terminator)) + +(phpinspect-defhandler use-keyword (start-token max-point) + "Handler for the use keyword and tokens that might follow to give it meaning" + (regexp (concat "use" (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (forward-char (length start-token)) + + (let ((parser (phpinspect-get-parser-create + :use + '(word tag block-without-scopes terminator) + #'phpinspect-end-of-use-p))) + (funcall parser (current-buffer) max-point))) + +(phpinspect-defhandler attribute-reference (start-token &rest _ignored) + "Handler for references to object attributes, or static class attributes." + (regexp "->\\|::") + (forward-char (length start-token)) + (looking-at (phpinspect-handler-regexp 'word)) + (let ((name (if (looking-at (phpinspect-handler-regexp 'word)) + (funcall (phpinspect-handler 'word) (match-string 0)) + nil))) + (cond + ((string= start-token "::") + (list :static-attrib name)) + ((string= start-token "->") + (list :object-attrib name))))) + +(phpinspect-defhandler namespace (start-token max-point) + "Handler for the namespace keyword. This is a special one + because it is not always delimited by a block like classes or + functions. This handler parses the namespace declaration, and + then continues to parse subsequent tokens, only stopping when + either a block has been parsed or another namespace keyword has + been encountered." + (regexp (concat "namespace" (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (forward-char (length start-token)) + (phpinspect-parse-with-handler-list + (current-buffer) + :namespace + max-point + (lambda () (not (looking-at (phpinspect-handler-regexp 'namespace)))) + #'phpinspect-block-p)) + +(phpinspect-defhandler const-keyword (start-token max-point) + "Handler for the const keyword." + (regexp (concat "const" (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (forward-char (length start-token)) + (let* ((parser (phpinspect-get-parser-create + :const + '(word comment assignment-operator string array + terminator) + #'phpinspect-end-of-token-p)) + (token (funcall parser (current-buffer) max-point))) + (when (phpinspect-incomplete-token-p (car (last token))) + (setcar token :incomplete-const)) + token)) + +(phpinspect-defhandler string (start-token &rest _ignored) + "Handler for strings" + (regexp "\"\\|'") + (list :string (phpinspect--munch-string start-token))) + +(phpinspect-defhandler block-without-scopes (start-token max-point) + "Handler for code blocks that cannot contain scope, const or +static keywords with the same meaning as in a class block." + (regexp "{") + (forward-char (length start-token)) + (let* ((complete-block nil) + (parser (phpinspect-get-parser-create + :block + '(array tag equals list comma + attribute-reference variable + assignment-operator whitespace + function-keyword word terminator here-doc + string comment block-without-scopes))) + (continue-condition (lambda () + (not (and (char-equal (char-after) ?}) + (setq complete-block t))))) + (parsed (funcall parser (current-buffer) max-point continue-condition))) + (if complete-block + (forward-char) + (setcar parsed :incomplete-block)) + parsed)) + + +(phpinspect-defhandler class-block (start-token max-point) + "Handler for code blocks that cannot contain classes" + (regexp "{") + (forward-char (length start-token)) + (let* ((complete-block nil) + (parser (phpinspect-get-parser-create + :block + '(array tag equals list comma + attribute-reference variable + assignment-operator whitespace scope-keyword + static-keyword const-keyword use-keyword + function-keyword word terminator here-doc + string comment block))) + (continue-condition (lambda () + (not (and (char-equal (char-after) ?}) + (setq complete-block t))))) + (parsed (funcall parser (current-buffer) max-point continue-condition))) + (if complete-block + (forward-char) + (setcar parsed :incomplete-block)) + parsed)) + +(phpinspect-defhandler block (start-token max-point) + "Handler for code blocks" + (regexp "{") + (forward-char (length start-token)) + (let* ((complete-block nil) + (continue-condition (lambda () + ;; When we encounter a closing brace for this + ;; block, we can mark the block as complete. + (not (and (char-equal (char-after) ?}) + (setq complete-block t))))) + (parsed (phpinspect-parse-with-handler-list + (current-buffer) :block max-point continue-condition))) + (if complete-block + ;; After meeting the char-after requirement above, we need to move + ;; one char forward to prevent parent-blocks from exiting because + ;; of the same char. + (forward-char) + (setcar parsed :incomplete-block)) + parsed)) + +(phpinspect-defhandler here-doc (start-token &rest _ignored) + "Handler for heredocs. Discards their contents." + (regexp "<<<") + (forward-char (length start-token)) + (if (looking-at "[A-Za-z0-9'\"\\_]+") + (re-search-forward (concat "^" (regexp-quote (match-string 0))) nil t)) + (list :here-doc)) + + +(defun phpinspect--munch-string (start-token) + "Consume text at point until a non-escaped `START-TOKEN` is found. + +Returns the consumed text string without face properties." + (forward-char (length start-token)) + (let ((start-point (point))) + (cond ((looking-at start-token) + (forward-char) + "") + ((looking-at (concat "\\([\\][\\]\\)+" (regexp-quote start-token))) + (let ((match (match-string 0))) + (forward-char (length match)) + (buffer-substring-no-properties start-point + (+ start-point (- (length match) + (length start-token)))))) + (t + (re-search-forward (format "\\([^\\]\\([\\][\\]\\)+\\|[^\\]\\)%s" + (regexp-quote start-token)) + nil t) + (buffer-substring-no-properties start-point (- (point) 1)))))) + +(phpinspect-defhandler list (start-token max-point) + "Handler for php syntactic lists (Note: this does not include +datatypes like arrays, merely lists that are of a syntactic +nature like argument lists" + (regexp "(") + (forward-char (length start-token)) + (let* ((complete-list nil) + (php-list (funcall + (phpinspect-get-parser-create + :list + '(array tag equals list comma + attribute-reference variable + assignment-operator whitespace + function-keyword word terminator here-doc + string comment block-without-scopes)) + (current-buffer) + max-point + (lambda () (not (and (char-equal (char-after) ?\)) (setq complete-list t))))))) + + (if complete-list + ;; Prevent parent-lists (if any) from exiting by skipping over the + ;; ")" character + (forward-char) + (setcar php-list :incomplete-list)) + php-list)) + +;; TODO: Look into using different names for function and class :declaration tokens. They +;; don't necessarily require the same handlers to parse. +(defsubst phpinspect-get-or-create-declaration-parser () + (phpinspect-get-parser-create :declaration + '(comment word list terminator tag) + #'phpinspect-end-of-token-p)) + + +(phpinspect-defhandler function-keyword (start-token max-point) + "Handler for the function keyword and tokens that follow to give it meaning" + (regexp (concat "function" (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (let* ((parser (phpinspect-get-or-create-declaration-parser)) + (continue-condition (lambda () (not (char-equal (char-after) ?{)))) + (declaration (funcall parser (current-buffer) max-point continue-condition))) + (if (phpinspect-end-of-token-p (car (last declaration))) + (list :function declaration) + (list :function + declaration + (funcall (phpinspect-handler 'block-without-scopes) + (char-to-string (char-after)) max-point))))) + +(phpinspect-defhandler scope-keyword (start-token max-point) + "Handler for scope keywords" + (regexp (mapconcat (lambda (word) + (concat word (phpinspect--word-end-regex))) + (list "public" "private" "protected") + "\\|")) + (setq start-token (phpinspect--strip-last-char start-token)) + (forward-char (length start-token)) + (funcall (phpinspect-get-parser-create + (cond ((string= start-token "public") :public) + ((string= start-token "private") :private) + ((string= start-token "protected") :protected)) + '(function-keyword static-keyword const-keyword + variable here-doc string terminator tag comment) + #'phpinspect--scope-terminator-p) + (current-buffer) + max-point)) + +(phpinspect-defhandler static-keyword (start-token max-point) + "Handler for the static keyword" + (regexp (concat "static" (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (forward-char (length start-token)) + (funcall (phpinspect-get-parser-create + :static + '(comment function-keyword variable array word + terminator tag) + #'phpinspect--static-terminator-p) + (current-buffer) + max-point)) + +(phpinspect-defhandler fat-arrow (arrow &rest _ignored) + "Handler for the \"fat arrow\" in arrays and foreach expressions" + (regexp "=>") + (phpinspect-munch-token-without-attribs arrow :fat-arrow)) + +(phpinspect-defhandler array (start-token max-point) + "Handler for arrays, in the bracketet as well as the list notation" + (regexp "\\[\\|array(") + (forward-char (length start-token)) + (let* ((end-char (cond ((string= start-token "[") ?\]) + ((string= start-token "array(") ?\)))) + (end-char-reached nil) + (token (funcall (phpinspect-get-parser-create + :array + '(comment comma list here-doc string + array variable attribute-reference + word fat-arrow)) + (current-buffer) + max-point + (lambda () (not (and (char-equal (char-after) end-char) + (setq end-char-reached t))))))) + + ;; Skip over the end char to prevent enclosing arrays or lists + ;; from terminating. + (if end-char-reached + (forward-char) + ;; Signal incompleteness when terminated because of max-point + (setcar token :incomplete-array)) + token)) + +(phpinspect-defhandler class-keyword (start-token max-point) + "Handler for the class keyword, and tokens that follow to define +the properties of the class" + (regexp (concat "\\(abstract\\|final\\|class\\|interface\\|trait\\)" + (phpinspect--word-end-regex))) + (setq start-token (phpinspect--strip-last-char start-token)) + (list :class (funcall (phpinspect-get-or-create-declaration-parser) + (current-buffer) + max-point + (lambda () (not (char-equal (char-after) ?{)))) + (funcall (phpinspect-handler 'class-block) + (char-to-string (char-after)) max-point))) + +(defun phpinspect-parse-with-handler-list + (buffer tree-type max-point &optional continue-condition delimiter-predicate) + "Parse BUFFER for TREE-TYPE tokens until MAX-POINT. + +Stop at CONTINUE-CONDITION or DELIMITER-PREDICATE. + +This just calls `phpinspect-get-parser-create` to make a parser +that contains all handlers necessary to parse code." + (let ((parser (phpinspect-get-parser-create + tree-type + '(array tag equals list comma + attribute-reference variable + assignment-operator whitespace scope-keyword + static-keyword const-keyword use-keyword + class-keyword function-keyword word terminator + here-doc string comment block) + delimiter-predicate))) + (funcall parser buffer max-point continue-condition))) + + +(defun phpinspect-parse-buffer-until-point (buffer point) + (with-current-buffer buffer + (save-excursion + (goto-char (point-min)) + (re-search-forward "<\\?php\\|<\\?" nil t) + (funcall (phpinspect-get-parser-create + :root + '(namespace array equals list comma + attribute-reference variable assignment-operator + whitespace scope-keyword static-keyword + const-keyword use-keyword class-keyword + function-keyword word terminator here-doc string + comment tag block)) + (current-buffer) + point)))) + +(provide 'phpinspect-parser) +;;; phpinspect-parser.el ends here diff --git a/phpinspect-project.el b/phpinspect-project.el new file mode 100644 index 0000000..2dfa5ef --- /dev/null +++ b/phpinspect-project.el @@ -0,0 +1,92 @@ +;;; phpinspect-project.el --- PHP parsing and completion package -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(require 'phpinspect-class) +(require 'phpinspect-type) + +(cl-defstruct (phpinspect--project (:constructor phpinspect--make-project-cache)) + (class-index (make-hash-table :test 'eq :size 100 :rehash-size 40) + :type hash-table + :documentation + "A `hash-table` that contains all of the currently +indexed classes in the project")) + +(cl-defgeneric phpinspect--project-add-class + ((project phpinspect--project) (class (head phpinspect--indexed-class))) + "Add an indexed CLASS to PROJECT.") + +(cl-defmethod phpinspect--project-add-class + ((project phpinspect--project) (indexed-class (head phpinspect--indexed-class))) + (let* ((class-name (phpinspect--type-name-symbol + (alist-get 'class-name (cdr indexed-class)))) + (existing-class (gethash class-name + (phpinspect--project-class-index project)))) + (if existing-class + (phpinspect--class-set-index existing-class indexed-class) + (let ((new-class (phpinspect--make-class-generated :project project))) + (phpinspect--class-set-index new-class indexed-class) + (puthash class-name new-class (phpinspect--project-class-index project)))))) + +(cl-defgeneric phpinspect--project-get-class + ((project phpinspect--project) (class-fqn phpinspect--type)) + "Get indexed class by name of CLASS-FQN stored in PROJECT.") + +(cl-defmethod phpinspect--project-get-class-create + ((project phpinspect--project) (class-fqn phpinspect--type)) + (let ((class (phpinspect--project-get-class project class-fqn))) + (unless class + (setq class (phpinspect--make-class-generated :project project)) + (puthash (phpinspect--type-name-symbol class-fqn) + class + (phpinspect--project-class-index project)) + + (let* ((class-file (phpinspect-class-filepath class-fqn)) + (visited-buffer (when class-file (find-buffer-visiting class-file))) + (new-index) + (class-index)) + + (phpinspect--log "No existing index for FQN: %s" class-fqn) + (phpinspect--log "filepath: %s" class-file) + (when class-file + (if visited-buffer + (setq new-index (with-current-buffer visited-buffer + (phpinspect--index-current-buffer))) + (setq new-index (phpinspect-index-file class-file))) + (setq class-index + (alist-get class-fqn (alist-get 'classes new-index) + nil + nil + #'phpinspect--type=)) + (when class-index + (phpinspect--class-set-index class class-index))))) + class)) + +(cl-defmethod phpinspect--project-get-class + ((project phpinspect--project) (class-fqn phpinspect--type)) + (gethash (phpinspect--type-name-symbol class-fqn) + (phpinspect--project-class-index project))) + +(provide 'phpinspect-project) +;;; phpinspect-project.el ends here diff --git a/phpinspect-type.el b/phpinspect-type.el new file mode 100644 index 0000000..0816fa0 --- /dev/null +++ b/phpinspect-type.el @@ -0,0 +1,187 @@ +;;; phpinspect-type.el --- Data structures that represent phpinspect types -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(require 'phpinspect-util) + +(cl-defstruct (phpinspect--type + (:constructor phpinspect--make-type-generated)) + "Represents an instance of a PHP type in the phpinspect syntax tree." + (name-symbol nil + :type symbol + :documentation + "Symbol representation of the type name.") + (collection nil + :type bool + :documentation + "Whether or not the type is a collection") + (contains nil + :type phpinspect--type + :documentation + "When the type is a collection, this attribute is set to the type that the collection is expected to contain") + (fully-qualified nil + :type bool + :documentation + "Whether or not the type name is fully qualified")) + +(defmacro phpinspect--make-type (&rest property-list) + `(phpinspect--make-type-generated + ,@(phpinspect--wrap-plist-name-in-symbol property-list))) + +(defconst phpinspect--null-type (phpinspect--make-type :name "\\null")) + +(cl-defmethod phpinspect--type-set-name ((type phpinspect--type) (name string)) + (setf (phpinspect--type-name-symbol type) (phpinspect-intern-name name))) + +(cl-defmethod phpinspect--type-name ((type phpinspect--type)) + (symbol-name (phpinspect--type-name-symbol type))) + +(defun phpinspect--get-bare-class-name-from-fqn (fqn) + (car (last (split-string fqn "\\\\")))) + +(cl-defmethod phpinspect--type-bare-name ((type phpinspect--type)) + (phpinspect--get-bare-class-name-from-fqn (phpinspect--type-name type))) + +(cl-defmethod phpinspect--type= ((type1 phpinspect--type) (type2 phpinspect--type)) + (eq (phpinspect--type-name-symbol type1) (phpinspect--type-name-symbol type2))) + +(defun phpinspect--resolve-type-name (types namespace type) + "Get the FQN for TYPE, using TYPES and NAMESPACE as context. + +TYPES must be an alist with class names as cars and FQNs as cdrs. +NAMESPACE may be nil, or a string with a namespace FQN." + (phpinspect--log "Resolving %s from namespace %s" type namespace) + ;; Absolute FQN + (cond ((string-match "^\\\\" type) + type) + + ;; Native type + ((member type phpinspect-native-types) + (concat "\\" type)) + + ;; Relative FQN + ((and namespace (string-match "\\\\" type)) + (concat "\\" namespace "\\" type)) + + ;; Clas|interface|trait name + (t (let ((from-types (assoc-default (phpinspect-intern-name type) types #'eq))) + (concat "\\" (cond (from-types + (phpinspect--type-name from-types)) + (namespace + (concat namespace "\\" type)) + (t type))))))) + +(cl-defmethod phpinspect--type-resolve (types namespace (type phpinspect--type)) + (unless (phpinspect--type-fully-qualified type) + (phpinspect--type-set-name + type + (phpinspect--resolve-type-name types namespace (phpinspect--type-name type))) + (setf (phpinspect--type-fully-qualified type) t)) + type) + +(defun phpinspect--make-type-resolver (types &optional token-tree namespace) + "Little wrapper closure to pass around and resolve types with." + + (let* ((inside-class + (if token-tree (or (phpinspect--find-innermost-incomplete-class token-tree) + (phpinspect--find-class-token token-tree)))) + (inside-class-name (if inside-class (phpinspect--get-class-name-from-token + inside-class))) + (self-type (phpinspect--make-type :name "self")) + (static-type (phpinspect--make-type :name "static"))) + (lambda (type) + (phpinspect--type-resolve + types + namespace + (if (and inside-class-name (or (phpinspect--type= type self-type) + (phpinspect--type= type static-type))) + (progn + (phpinspect--log "Returning inside class name for %s : %s" + type inside-class-name) + (phpinspect--make-type :name inside-class-name)) + ;; else + type))))) + +(cl-defgeneric phpinspect--format-type-name (name) + (string-remove-prefix "\\" name)) + +(cl-defmethod phpinspect--format-type-name ((type phpinspect--type)) + (phpinspect--format-type-name (phpinspect--type-name type))) + +(cl-defstruct (phpinspect--function (:constructor phpinspect--make-function-generated)) + "A PHP function." + (name-symbol nil + :type symbol + :documentation + "A symbol associated with the name of the function") + (scope nil + :type phpinspect-scope + :documentation + "When the function is a method, this should contain the +scope of the function as returned by `phpinspect-parse-scope`.") + (arguments nil + :type list + :documentation + "A simple list with function arguments and their +types in tuples. Each list should have the name of the variable +as first element and the type as second element.") + (return-type nil + :type phpinspect--type + :documentation + "A phpinspect--type object representing the the +return type of the function.")) + +(defmacro phpinspect--make-function (&rest property-list) + `(phpinspect--make-function-generated + ,@(phpinspect--wrap-plist-name-in-symbol property-list))) + +(cl-defmethod phpinspect--function-set-name ((func phpinspect--function) (name string)) + (setf (phpinspect--function-name-symbol func) (intern name phpinspect-name-obarray))) + +(cl-defgeneric phpinspect--function-name ((func phpinspect--function))) + +(cl-defmethod phpinspect--function-name ((func phpinspect--function)) + (symbol-name (phpinspect--function-name-symbol func))) + + +(cl-defstruct (phpinspect--variable (:constructor phpinspect--make-variable)) + "A PHP Variable." + (name nil + :type string + :documentation + "A string containing the name of the variable.") + (scope nil + :type phpinspect-scope + :documentation + "When the variable is an object attribute, this should +contain the scope of the variable as returned by +`phpinspect-parse-scope`") + (type nil + :type string + :documentation + "A string containing the FQN of the variable's type")) + + +(provide 'phpinspect-type) +;;; phpinspect-type.el ends here diff --git a/phpinspect-util.el b/phpinspect-util.el new file mode 100644 index 0000000..56920f5 --- /dev/null +++ b/phpinspect-util.el @@ -0,0 +1,66 @@ +;;; phpinspect-util.el --- PHP parsing and completion package -*- lexical-binding: t; -*- + +;; Copyright (C) 2021 Free Software Foundation, Inc + +;; Author: Hugo Thunnissen +;; 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 . + +;;; Commentary: + +;;; Code: + +(defvar phpinspect-name-obarray (obarray-make) + "An obarray containing symbols for all encountered names in +PHP. Used to optimize string comparison.") + +(defvar phpinspect--debug nil + "Enable debug logs for phpinspect by setting this variable to true") + +(defsubst phpinspect-intern-name (name) + (intern name phpinspect-name-obarray)) + +(defsubst phpinspect--wrap-plist-name-in-symbol (property-list) + (let ((new-plist) + (wrap-value)) + (dolist (item property-list) + (when wrap-value + (setq item `(phpinspect-intern-name ,item)) + (setq wrap-value nil)) + (when (eq item :name) + (setq item :name-symbol) + (setq wrap-value t)) + (push item new-plist)) + (nreverse new-plist))) + +(defun phpinspect-toggle-logging () + (interactive) + (if (setq phpinspect--debug (not phpinspect--debug)) + (message "Enabled phpinspect logging.") + (message "Disabled phpinspect logging."))) + + +(defsubst phpinspect--log (&rest args) + (when phpinspect--debug + (with-current-buffer (get-buffer-create "**phpinspect-logs**") + (unless window-point-insertion-type + (set (make-local-variable 'window-point-insertion-type) t)) + (goto-char (buffer-end 1)) + (insert (concat "[" (format-time-string "%H:%M:%S") "]: " + (apply #'format args) "\n"))))) + +(provide 'phpinspect-util) +;;; phpinspect-util.el ends here diff --git a/phpinspect.el b/phpinspect.el index d691696..1ceb74e 100644 --- a/phpinspect.el +++ b/phpinspect.el @@ -29,23 +29,18 @@ (require 'json) (require 'obarray) -(defvar phpinspect-parser-obarray (obarray-make) - "An obarray containing symbols for all phpinspect (sub)parsers.") - -(defvar phpinspect-handler-obarray (obarray-make) - "An obarray containing symbols for all phpinspect parser handlers.") - -(defvar phpinspect-name-obarray (obarray-make) - "An obarray containing symbols for all encountered names in -PHP. Used to optimize string comparison.") +;; internal dependencies +(require 'phpinspect-cache) +(require 'phpinspect-parser) +(require 'phpinspect-project) +(require 'phpinspect-util) +(require 'phpinspect-type) +(require 'phpinspect-index) (defvar-local phpinspect--buffer-index nil "The result of the last successfull parse + index action executed by phpinspect for the current buffer") -(defvar phpinspect--debug nil - "Enable debug logs for phpinspect by setting this variable to true") - (defvar phpinspect-cache () "In-memory nested key-value store used for caching by phpinspect") @@ -88,905 +83,6 @@ Should normally be set to \"phpinspect-index.bash\" in the source '("\\array" "\\iterable" "\\SplObjectCollection" "\\mixed") "FQNs of types that should be treated as collecitons when inferring types.") -(defsubst phpinspect-intern-name (name) - (intern name phpinspect-name-obarray)) - -(eval-when-compile - (define-inline phpinspect--word-end-regex () - (inline-quote "\\([[:blank:]]\\|[^0-9a-zA-Z_]\\)"))) - -(defun phpinspect-list-handlers () - (let ((handlers)) - (mapatoms (lambda (handler) - (push (symbol-name handler) handlers)) - phpinspect-handler-obarray) - handlers)) - -(defun phpinspect-describe-handler (handler-name) - "Display a buffer containing HANDLER-NAMEs docstring and attribute-plist." - (interactive (list (completing-read "Pick a handler:" (phpinspect-list-handlers)))) - (with-current-buffer (get-buffer-create "phpinspect-handler-description") - (insert (concat - (pp (symbol-value (intern handler-name phpinspect-handler-obarray))) - "\n" - (documentation (intern handler-name phpinspect-handler-obarray)))) - (pop-to-buffer (current-buffer)))) - -(defsubst phpinspect--strip-last-char (string) - (substring string 0 (- (length string) 1))) - -(defsubst phpinspect-munch-token-without-attribs (string token-keyword) - "Return a token of type TOKEN-KEYWORD with STRING as value. -If STRING has text properties, they are stripped." - (let ((value (copy-sequence string)) - (length (length string))) - (forward-char length) - (set-text-properties 0 length nil value) - (list token-keyword value))) - - -(defsubst phpinspect-token-type-p (object type) - "Returns t if OBJECT is a token of type TYPE. -Type can be any of the token types returned by -`phpinspect-parse-buffer-until-point`" - (and (listp object) (eq (car object) type))) - -(defsubst phpinspect-object-attrib-p (token) - (phpinspect-token-type-p token :object-attrib)) - -(defsubst phpinspect-static-attrib-p (token) - (phpinspect-token-type-p token :static-attrib)) - -(defsubst phpinspect-attrib-p (token) - (or (phpinspect-object-attrib-p token) - (phpinspect-static-attrib-p token))) - -(defun phpinspect-html-p (token) - (phpinspect-token-type-p token :html)) - -(defun phpinspect-comma-p (token) - (phpinspect-token-type-p token :comma)) - -(defsubst phpinspect-terminator-p (token) - (phpinspect-token-type-p token :terminator)) - -(defsubst phpinspect-end-of-token-p (token) - (or (phpinspect-terminator-p token) - (phpinspect-comma-p token) - (phpinspect-html-p token))) - -(defsubst phpinspect-end-of-statement-p (token) - (or (phpinspect-end-of-token-p token) - (phpinspect-block-p token))) - -(defsubst phpinspect-incomplete-block-p (token) - (phpinspect-token-type-p token :incomplete-block)) - -(defsubst phpinspect-block-p (token) - (or (phpinspect-token-type-p token :block) - (phpinspect-incomplete-block-p token))) - -(defun phpinspect-end-of-use-p (token) - (or (phpinspect-block-p token) - (phpinspect-end-of-token-p token))) - -(defun phpinspect-static-p (token) - (phpinspect-token-type-p token :static)) - -(defsubst phpinspect-incomplete-const-p (token) - (phpinspect-token-type-p token :incomplete-const)) - -(defsubst phpinspect-const-p (token) - (or (phpinspect-token-type-p token :const) - (phpinspect-incomplete-const-p token))) - -(defsubst phpinspect-scope-p (token) - (or (phpinspect-token-type-p token :public) - (phpinspect-token-type-p token :private) - (phpinspect-token-type-p token :protected))) - -(defsubst phpinspect-namespace-p (object) - (phpinspect-token-type-p object :namespace)) - -(defun phpinspect-incomplete-class-p (token) - (and (phpinspect-class-p token) - (phpinspect-incomplete-block-p (car (last token))))) - -(defun phpinspect-incomplete-namespace-p (token) - (and (phpinspect-namespace-p token) - (or (phpinspect-incomplete-block-p (car (last token))) - (phpinspect-incomplete-class-p (car (last token)))))) - -(defun phpinspect-function-p (token) - (phpinspect-token-type-p token :function)) - - -(defun phpinspect-class-p (token) - (phpinspect-token-type-p token :class)) - -(defun phpinspect-incomplete-method-p (token) - (or (phpinspect-incomplete-function-p token) - (and (phpinspect-scope-p token) - (phpinspect-incomplete-function-p (car (last token)))) - (and (phpinspect-scope-p token) - (phpinspect-static-p (car (last token))) - (phpinspect-incomplete-function-p (car (last (car (last token)))))) - (and (phpinspect-scope-p token) - (phpinspect-function-p (car (last token)))))) - -(defun phpinspect-incomplete-function-p (token) - (and (phpinspect-function-p token) - (phpinspect-incomplete-block-p (car (last token))))) - -(defsubst phpinspect-incomplete-list-p (token) - (phpinspect-token-type-p token :incomplete-list)) - -(defsubst phpinspect-list-p (token) - (or (phpinspect-token-type-p token :list) - (phpinspect-incomplete-list-p token))) - -(defun phpinspect-declaration-p (token) - (phpinspect-token-type-p token :declaration)) - -(defsubst phpinspect-assignment-p (token) - (phpinspect-token-type-p token :assignment)) - -(defun phpinspect-function-argument-list (php-func) - "Get the argument list of a function" - (seq-find #'phpinspect-list-p (seq-find #'phpinspect-declaration-p php-func nil) nil)) - -(defsubst phpinspect-variable-p (token) - (phpinspect-token-type-p token :variable)) - -(defsubst phpinspect-word-p (token) - (phpinspect-token-type-p token :word)) - -(defsubst phpinspect-incomplete-array-p (token) - (phpinspect-token-type-p token :incomplete-array)) - -(defsubst phpinspect-array-p (token) - (or (phpinspect-token-type-p token :array) - (phpinspect-incomplete-array-p token))) - -(defsubst phpinspect-incomplete-token-p (token) - (or (phpinspect-incomplete-class-p token) - (phpinspect-incomplete-block-p token) - (phpinspect-incomplete-list-p token) - (phpinspect-incomplete-array-p token) - (phpinspect-incomplete-const-p token) - (phpinspect-incomplete-function-p token) - (phpinspect-incomplete-method-p token) - (phpinspect-incomplete-namespace-p token))) - -(defun phpinspect--static-terminator-p (token) - (or (phpinspect-function-p token) - (phpinspect-end-of-token-p token))) - -(defun phpinspect--scope-terminator-p (token) - (or (phpinspect-function-p token) - (phpinspect-end-of-token-p token) - (phpinspect-const-p token) - (phpinspect-static-p token))) - -(defun phpinspect-namespace-keyword-p (token) - (and (phpinspect-word-p token) (string= (car (last token)) "namespace"))) - -(defun phpinspect-use-keyword-p (token) - (and (phpinspect-word-p token) (string= (car (last token)) "use"))) - - -(defsubst phpinspect-root-p (object) - (phpinspect-token-type-p object :root)) - -(defsubst phpinspect-namespace-or-root-p (object) - (or (phpinspect-namespace-p object) - (phpinspect-root-p object))) - -(defun phpinspect-use-p (object) - (phpinspect-token-type-p object :use)) - -(defun phpinspect-comment-p (token) - (phpinspect-token-type-p token :comment)) - -(defsubst phpinspect-class-block (class) - (caddr class)) - -(defsubst phpinspect-namespace-block (namespace) - (when (and (= (length namespace) 3) - (phpinspect-block-p (caddr namespace))) - (caddr namespace))) - -(defsubst phpinspect-function-block (php-func) - (caddr php-func)) - -(defsubst phpinspect-not-class-p (token) - "Apply inverse of `phpinspect-class-p' to TOKEN." - (not (phpinspect-class-p token))) - -(defmacro phpinspect-defhandler (name arguments docstring attribute-plist &rest body) - "Define a parser handler that becomes available for use with phpinspect-parse. - -A parser handler is a function that is able to identify and parse -tokens from PHP code at `point` in the current buffer. It's -return value must be the resulting token. Aside from parsing it -has to manage the state of `point` in a way that it skips over -the tokens it has parsed. That way the next handler can -correctly pick up from where it has left off. - -Parser handlers are unrolled in a `cond` statement by -`phpinspect-make-parser`. The resulting code is something akin -to the following: - -(while ... - (cond (((looking-at \"{\") - (funcall block-handler (match-string 0) max-point) - ((looking-at \"\\$\") - (funcall variable-handler ... -etc. - -NAME must be a symbol. It does not need to be prefixed with a -\"namespace\" because parser handlers are stored in their own -obarray (`phpinspect-handler-obarray`). - -ARGUMENTS must an argument list as accepted by `lambda`. A -handler must be able to accept 2 arguments: START-TOKEN and -MAX-POINT. START-TOKEN is the match string that resulted from -the comparison of the handlers' `regexp` attribute with the text -at `point`. MAX-POINT is the point in the current buffer up -until which the parser is supposed to parse. For some tokens you -may not want/need to respect MAX-POINT, in which case you can -ignore it. - -DOCSTRING is mandatory. It should contain a description of the -tokens the handler is able to process and (if present) any -particularities of the handler. - -ATTRIBUTE-PLIST is a plist that must contain at least a `regexp` key. - Possible keys: - - regexp: The regular expression that marks the start of the token. - -BODY is a function body as accepted by `lambda` that parses the -text at point and returns the resulting token. - -When altering/adding handlers during runtime, make sure to purge -the parser cache to make sure that your new handler functions are used. -You can purge the parser cache with \\[phpinspect-purge-parser-cache]." - (declare (indent defun)) - (when (not (symbolp name)) - (error "In definition of phpinspect handler %s: NAME bust be a symbol" name)) - - (when (not (plist-member attribute-plist 'regexp)) - (error "In definition of phpinspect handler %s ATTRIBUTE-PLIST must contain key `regexp`" - name)) - - ;; Eval regexp. It might be a `concat` statement and we don't want to be executing that - ;; every time the parser advances one character and has to check for the regexp - ;; occurence. - (setq attribute-plist (plist-put attribute-plist 'regexp - (eval (plist-get attribute-plist 'regexp) - t))) - (let ((name (symbol-name name))) - `(progn - (set (intern ,name phpinspect-handler-obarray) (quote ,attribute-plist)) - (defalias (intern ,name phpinspect-handler-obarray) - #'(lambda (,@arguments) - ,docstring - ,@body)) - (unless (byte-code-function-p - (symbol-function - (intern ,name phpinspect-handler-obarray))) - (byte-compile (intern ,name phpinspect-handler-obarray)))))) - -(defun phpinspect-get-parser-create (tree-type &rest parser-parameters) - "Retrieve a parser for TREE-TYPE from `phpinspect-parser-obarray'. - -TREE-TYPE must be a symbol or keyword representing the type of -the token the parser is able to parse. - -If a parser by TREE-TYPE doesn't exist, it is created by callng -`phpinspect-make-parser` with TREE-TYPE as first argument and -PARSER-PARAMETERS as the rest of the arguments. The resulting -parser function is then returned in byte-compiled form." - (let* ((parser-name (symbol-name tree-type)) - (parser-symbol (intern-soft parser-name phpinspect-parser-obarray))) - (or (and parser-symbol (symbol-function parser-symbol)) - (defalias (intern parser-name phpinspect-parser-obarray) - (byte-compile (apply #'phpinspect-make-parser - `(,tree-type ,@parser-parameters))))))) - -(defun phpinspect-purge-parser-cache () - "Empty `phpinspect-parser-obarray`. - -This is useful when you need to change parser handlers or parsers -during runtime. Parsers are implemented with macros, so changing -handler functions without calling this function will often not -have any effect." - (interactive) - (setq phpinspect-parser-obarray (obarray-make))) - -(defun phpinspect-make-parser (tree-type handler-list &optional delimiter-predicate) - "Create a parser function using the handlers by names defined in HANDLER-LIST. - -See also `phpinspect-defhandler`. - -TREE-TYPE must be a symbol or a keyword representing the token -type. - -HANDLER-LIST must be a list of either symbol or string -representation of handler symbols which can be found in -`phpinspect-handler-obarray`. - -DELIMITER-PREDICATE must be a function. It is passed the last -parsed token after every handler iteration. If it evaluates to -something other than nil, parsing is deemed completed and the -loop exits. An example use case of this is to determine the end -of a statement. You can use `phpinspect-terminator-p` as -delimiter predicate and have parsing stop when the last parsed -token is \";\", which marks the end of a statement in PHP." - (let ((handlers (mapcar - (lambda (handler-name) - (let* ((handler-name (symbol-name handler-name)) - (handler (intern-soft handler-name phpinspect-handler-obarray))) - (if handler - handler - (error "No handler found by name \"%s\"" handler-name)))) - handler-list)) - (delimiter-predicate (if (symbolp delimiter-predicate) - `(quote ,delimiter-predicate) - delimiter-predicate))) - `(lambda (buffer max-point &optional continue-condition) - (with-current-buffer buffer - (let ((tokens) - (delimiter-predicate (when (functionp ,delimiter-predicate) ,delimiter-predicate))) - (while (and (< (point) max-point) - (if continue-condition (funcall continue-condition) t) - (not (if delimiter-predicate - (funcall delimiter-predicate (car (last tokens))) - nil))) - (cond ,@(mapcar - (lambda (handler) - `((looking-at ,(plist-get (symbol-value handler) 'regexp)) - (let ((token (funcall ,(symbol-function handler) - (match-string 0) - max-point))) - (when token - (if (null tokens) - (setq tokens (list token)) - (nconc tokens (list token))))))) - handlers) - (t (forward-char)))) - (push ,tree-type tokens)))))) - -(phpinspect-defhandler comma (comma &rest _ignored) - "Handler for comma tokens" - (regexp ",") - (phpinspect-munch-token-without-attribs comma :comma)) - -(phpinspect-defhandler word (word &rest _ignored) - "Handler for bareword tokens" - (regexp "[A-Za-z_\\][\\A-Za-z_0-9]*") - (let ((length (length word))) - (forward-char length) - (set-text-properties 0 length nil word) - (list :word word))) - -(defsubst phpinspect-handler (handler-name) - (intern-soft (symbol-name handler-name) phpinspect-handler-obarray)) - -(defsubst phpinspect-handler-regexp (handler-name) - (plist-get (symbol-value (phpinspect-handler handler-name)) 'regexp)) - -(defsubst phpinspect--parse-annotation-parameters (parameter-amount) - (let* ((words) - (word-handler (phpinspect-handler 'word)) - (word-regexp (phpinspect-handler-regexp 'word)) - (variable-handler (phpinspect-handler 'variable)) - (variable-regexp (phpinspect-handler-regexp 'variable))) - (while (not (or (looking-at "\\*/") (= (length words) parameter-amount))) - (forward-char) - (cond ((looking-at word-regexp) - (push (funcall word-handler (match-string 0)) words)) - ((looking-at variable-regexp) - (push (funcall variable-handler (match-string 0)) words)))) - (nreverse words))) - -(phpinspect-defhandler annotation (start-token &rest _ignored) - "Handler for in-comment @annotations" - (regexp "@") - (forward-char (length start-token)) - (if (looking-at (phpinspect-handler-regexp 'word)) - (let ((annotation-name (match-string 0))) - (forward-char (length annotation-name)) - (cond ((string= annotation-name "var") - ;; The @var annotation accepts 2 parameters: - ;; the type and the $variable name - (append (list :var-annotation) - (phpinspect--parse-annotation-parameters 2))) - ((string= annotation-name "return") - ;; The @return annotation only accepts 1 word as parameter: - ;; The return type - (append (list :return-annotation) - (phpinspect--parse-annotation-parameters 1))) - ((string= annotation-name "param") - ;; The @param annotation accepts 2 parameters: - ;; The type of the param, and the param's $name - (append (list :param-annotation) - (phpinspect--parse-annotation-parameters 2))) - (t - (list :annotation annotation-name)))) - (list :annotation nil))) - -(phpinspect-defhandler tag (start-token max-point) - "Handler that discards any inline HTML it encounters" - (regexp "\\?>") - (forward-char (length start-token)) - (or (re-search-forward "<\\?php\\|<\\?" nil t) - (goto-char max-point)) - (list :html)) - -(phpinspect-defhandler comment (start-token max-point) - "Handler for comments and doc blocks" - (regexp "#\\|//\\|/\\*") - (forward-char (length start-token)) - - (cond ((string-match "/\\*" start-token) - (let* ((continue-condition (lambda () (not (looking-at "\\*/")))) - (parser (phpinspect-get-parser-create - :doc-block - '(annotation whitespace))) - (doc-block (funcall parser (current-buffer) max-point continue-condition))) - (forward-char 2) - doc-block)) - (t - (let ((parser (phpinspect-get-parser-create :comment '(tag) #'phpinspect-html-p)) - (end-position (line-end-position))) - (funcall parser (current-buffer) end-position))))) - -(phpinspect-defhandler variable (start-token &rest _ignored) - "Handler for tokens indicating reference to a variable" - (regexp "\\$") - (forward-char (length start-token)) - (if (looking-at (phpinspect-handler-regexp 'word)) - (phpinspect-munch-token-without-attribs (match-string 0) :variable) - (list :variable nil))) - -(phpinspect-defhandler whitespace (whitespace &rest _ignored) - "Handler that discards whitespace" - (regexp "[[:blank:]]+") - (forward-char (length whitespace))) - -(phpinspect-defhandler equals (equals &rest _ignored) - "Handler for strict and unstrict equality comparison tokens." - (regexp "===?") - (phpinspect-munch-token-without-attribs equals :equals)) - -(phpinspect-defhandler assignment-operator (operator &rest _ignored) - "Handler for tokens indicating that an assignment is taking place" - (regexp "[+-]?=") - (phpinspect-munch-token-without-attribs operator :assignment)) - -(phpinspect-defhandler terminator (terminator &rest _ignored) - "Handler for statement terminators." - (regexp ";") - (phpinspect-munch-token-without-attribs terminator :terminator)) - -(phpinspect-defhandler use-keyword (start-token max-point) - "Handler for the use keyword and tokens that might follow to give it meaning" - (regexp (concat "use" (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (forward-char (length start-token)) - - (let ((parser (phpinspect-get-parser-create - :use - '(word tag block-without-scopes terminator) - #'phpinspect-end-of-use-p))) - (funcall parser (current-buffer) max-point))) - -(phpinspect-defhandler attribute-reference (start-token &rest _ignored) - "Handler for references to object attributes, or static class attributes." - (regexp "->\\|::") - (forward-char (length start-token)) - (looking-at (phpinspect-handler-regexp 'word)) - (let ((name (if (looking-at (phpinspect-handler-regexp 'word)) - (funcall (phpinspect-handler 'word) (match-string 0)) - nil))) - (cond - ((string= start-token "::") - (list :static-attrib name)) - ((string= start-token "->") - (list :object-attrib name))))) - -(phpinspect-defhandler namespace (start-token max-point) - "Handler for the namespace keyword. This is a special one - because it is not always delimited by a block like classes or - functions. This handler parses the namespace declaration, and - then continues to parse subsequent tokens, only stopping when - either a block has been parsed or another namespace keyword has - been encountered." - (regexp (concat "namespace" (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (forward-char (length start-token)) - (phpinspect-parse-with-handler-list - (current-buffer) - :namespace - max-point - (lambda () (not (looking-at (phpinspect-handler-regexp 'namespace)))) - #'phpinspect-block-p)) - -(phpinspect-defhandler const-keyword (start-token max-point) - "Handler for the const keyword." - (regexp (concat "const" (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (forward-char (length start-token)) - (let* ((parser (phpinspect-get-parser-create - :const - '(word comment assignment-operator string array - terminator) - #'phpinspect-end-of-token-p)) - (token (funcall parser (current-buffer) max-point))) - (when (phpinspect-incomplete-token-p (car (last token))) - (setcar token :incomplete-const)) - token)) - -(phpinspect-defhandler string (start-token &rest _ignored) - "Handler for strings" - (regexp "\"\\|'") - (list :string (phpinspect--munch-string start-token))) - -(phpinspect-defhandler block-without-scopes (start-token max-point) - "Handler for code blocks that cannot contain scope, const or -static keywords with the same meaning as in a class block." - (regexp "{") - (forward-char (length start-token)) - (let* ((complete-block nil) - (parser (phpinspect-get-parser-create - :block - '(array tag equals list comma - attribute-reference variable - assignment-operator whitespace - function-keyword word terminator here-doc - string comment block-without-scopes))) - (continue-condition (lambda () - (not (and (char-equal (char-after) ?}) - (setq complete-block t))))) - (parsed (funcall parser (current-buffer) max-point continue-condition))) - (if complete-block - (forward-char) - (setcar parsed :incomplete-block)) - parsed)) - - -(phpinspect-defhandler class-block (start-token max-point) - "Handler for code blocks that cannot contain classes" - (regexp "{") - (forward-char (length start-token)) - (let* ((complete-block nil) - (parser (phpinspect-get-parser-create - :block - '(array tag equals list comma - attribute-reference variable - assignment-operator whitespace scope-keyword - static-keyword const-keyword use-keyword - function-keyword word terminator here-doc - string comment block))) - (continue-condition (lambda () - (not (and (char-equal (char-after) ?}) - (setq complete-block t))))) - (parsed (funcall parser (current-buffer) max-point continue-condition))) - (if complete-block - (forward-char) - (setcar parsed :incomplete-block)) - parsed)) - -(phpinspect-defhandler block (start-token max-point) - "Handler for code blocks" - (regexp "{") - (forward-char (length start-token)) - (let* ((complete-block nil) - (continue-condition (lambda () - ;; When we encounter a closing brace for this - ;; block, we can mark the block as complete. - (not (and (char-equal (char-after) ?}) - (setq complete-block t))))) - (parsed (phpinspect-parse-with-handler-list - (current-buffer) :block max-point continue-condition))) - (if complete-block - ;; After meeting the char-after requirement above, we need to move - ;; one char forward to prevent parent-blocks from exiting because - ;; of the same char. - (forward-char) - (setcar parsed :incomplete-block)) - parsed)) - -(phpinspect-defhandler here-doc (start-token &rest _ignored) - "Handler for heredocs. Discards their contents." - (regexp "<<<") - (forward-char (length start-token)) - (if (looking-at "[A-Za-z0-9'\"\\_]+") - (re-search-forward (concat "^" (regexp-quote (match-string 0))) nil t)) - (list :here-doc)) - - -(defun phpinspect--munch-string (start-token) - "Consume text at point until a non-escaped `START-TOKEN` is found. - -Returns the consumed text string without face properties." - (forward-char (length start-token)) - (let ((start-point (point))) - (cond ((looking-at start-token) - (forward-char) - "") - ((looking-at (concat "\\([\\][\\]\\)+" (regexp-quote start-token))) - (let ((match (match-string 0))) - (forward-char (length match)) - (buffer-substring-no-properties start-point - (+ start-point (- (length match) - (length start-token)))))) - (t - (re-search-forward (format "\\([^\\]\\([\\][\\]\\)+\\|[^\\]\\)%s" - (regexp-quote start-token)) - nil t) - (buffer-substring-no-properties start-point (- (point) 1)))))) - -(phpinspect-defhandler list (start-token max-point) - "Handler for php syntactic lists (Note: this does not include -datatypes like arrays, merely lists that are of a syntactic -nature like argument lists" - (regexp "(") - (forward-char (length start-token)) - (let* ((complete-list nil) - (php-list (funcall - (phpinspect-get-parser-create - :list - '(array tag equals list comma - attribute-reference variable - assignment-operator whitespace - function-keyword word terminator here-doc - string comment block-without-scopes)) - (current-buffer) - max-point - (lambda () (not (and (char-equal (char-after) ?\)) (setq complete-list t))))))) - - (if complete-list - ;; Prevent parent-lists (if any) from exiting by skipping over the - ;; ")" character - (forward-char) - (setcar php-list :incomplete-list)) - php-list)) - -;; TODO: Look into using different names for function and class :declaration tokens. They -;; don't necessarily require the same handlers to parse. -(defsubst phpinspect-get-or-create-declaration-parser () - (phpinspect-get-parser-create :declaration - '(comment word list terminator tag) - #'phpinspect-end-of-token-p)) - - -(phpinspect-defhandler function-keyword (start-token max-point) - "Handler for the function keyword and tokens that follow to give it meaning" - (regexp (concat "function" (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (let* ((parser (phpinspect-get-or-create-declaration-parser)) - (continue-condition (lambda () (not (char-equal (char-after) ?{)))) - (declaration (funcall parser (current-buffer) max-point continue-condition))) - (if (phpinspect-end-of-token-p (car (last declaration))) - (list :function declaration) - (list :function - declaration - (funcall (phpinspect-handler 'block-without-scopes) - (char-to-string (char-after)) max-point))))) - -(phpinspect-defhandler scope-keyword (start-token max-point) - "Handler for scope keywords" - (regexp (mapconcat (lambda (word) - (concat word (phpinspect--word-end-regex))) - (list "public" "private" "protected") - "\\|")) - (setq start-token (phpinspect--strip-last-char start-token)) - (forward-char (length start-token)) - (funcall (phpinspect-get-parser-create - (cond ((string= start-token "public") :public) - ((string= start-token "private") :private) - ((string= start-token "protected") :protected)) - '(function-keyword static-keyword const-keyword - variable here-doc string terminator tag comment) - #'phpinspect--scope-terminator-p) - (current-buffer) - max-point)) - -(phpinspect-defhandler static-keyword (start-token max-point) - "Handler for the static keyword" - (regexp (concat "static" (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (forward-char (length start-token)) - (funcall (phpinspect-get-parser-create - :static - '(comment function-keyword variable array word - terminator tag) - #'phpinspect--static-terminator-p) - (current-buffer) - max-point)) - -(phpinspect-defhandler fat-arrow (arrow &rest _ignored) - "Handler for the \"fat arrow\" in arrays and foreach expressions" - (regexp "=>") - (phpinspect-munch-token-without-attribs arrow :fat-arrow)) - -(phpinspect-defhandler array (start-token max-point) - "Handler for arrays, in the bracketet as well as the list notation" - (regexp "\\[\\|array(") - (forward-char (length start-token)) - (let* ((end-char (cond ((string= start-token "[") ?\]) - ((string= start-token "array(") ?\)))) - (end-char-reached nil) - (token (funcall (phpinspect-get-parser-create - :array - '(comment comma list here-doc string - array variable attribute-reference - word fat-arrow)) - (current-buffer) - max-point - (lambda () (not (and (char-equal (char-after) end-char) - (setq end-char-reached t))))))) - - ;; Skip over the end char to prevent enclosing arrays or lists - ;; from terminating. - (if end-char-reached - (forward-char) - ;; Signal incompleteness when terminated because of max-point - (setcar token :incomplete-array)) - token)) - -(phpinspect-defhandler class-keyword (start-token max-point) - "Handler for the class keyword, and tokens that follow to define -the properties of the class" - (regexp (concat "\\(abstract\\|final\\|class\\|interface\\|trait\\)" - (phpinspect--word-end-regex))) - (setq start-token (phpinspect--strip-last-char start-token)) - (list :class (funcall (phpinspect-get-or-create-declaration-parser) - (current-buffer) - max-point - (lambda () (not (char-equal (char-after) ?{)))) - (funcall (phpinspect-handler 'class-block) - (char-to-string (char-after)) max-point))) - -(defun phpinspect-parse-with-handler-list - (buffer tree-type max-point &optional continue-condition delimiter-predicate) - "Parse BUFFER for TREE-TYPE tokens until MAX-POINT. - -Stop at CONTINUE-CONDITION or DELIMITER-PREDICATE. - -This just calls `phpinspect-get-parser-create` to make a parser -that contains all handlers necessary to parse code." - (let ((parser (phpinspect-get-parser-create - tree-type - '(array tag equals list comma - attribute-reference variable - assignment-operator whitespace scope-keyword - static-keyword const-keyword use-keyword - class-keyword function-keyword word terminator - here-doc string comment block) - delimiter-predicate))) - (funcall parser buffer max-point continue-condition))) - - -(defun phpinspect-parse-buffer-until-point (buffer point) - (with-current-buffer buffer - (save-excursion - (goto-char (point-min)) - (re-search-forward "<\\?php\\|<\\?" nil t) - (funcall (phpinspect-get-parser-create - :root - '(namespace array equals list comma - attribute-reference variable assignment-operator - whitespace scope-keyword static-keyword - const-keyword use-keyword class-keyword - function-keyword word terminator here-doc string - comment tag block)) - (current-buffer) - point)))) - -(cl-defstruct (phpinspect--type - (:constructor phpinspect--make-type-generated)) - "Represents an instance of a PHP type in the phpinspect syntax tree." - (name-symbol nil - :type symbol - :documentation - "Symbol representation of the type name.") - (collection nil - :type bool - :documentation - "Whether or not the type is a collection") - (contains nil - :type phpinspect--type - :documentation - "When the type is a collection, this attribute is set to the type that the collection is expected to contain") - (fully-qualified nil - :type bool - :documentation - "Whether or not the type name is fully qualified")) - -(defsubst phpinspect--wrap-plist-name-in-symbol (property-list) - (let ((new-plist) - (wrap-value)) - (dolist (item property-list) - (when wrap-value - (setq item `(phpinspect-intern-name ,item)) - (setq wrap-value nil)) - (when (eq item :name) - (setq item :name-symbol) - (setq wrap-value t)) - (push item new-plist)) - (nreverse new-plist))) - -(defmacro phpinspect--make-type (&rest property-list) - `(phpinspect--make-type-generated - ,@(phpinspect--wrap-plist-name-in-symbol property-list))) - -(defconst phpinspect--null-type (phpinspect--make-type :name "\\null")) - -(cl-defmethod phpinspect--type-set-name ((type phpinspect--type) (name string)) - (setf (phpinspect--type-name-symbol type) (phpinspect-intern-name name))) - -(cl-defmethod phpinspect--type-name ((type phpinspect--type)) - (symbol-name (phpinspect--type-name-symbol type))) - -(cl-defmethod phpinspect--type= ((type1 phpinspect--type) (type2 phpinspect--type)) - (eq (phpinspect--type-name-symbol type1) (phpinspect--type-name-symbol type2))) - -(cl-defstruct (phpinspect--function (:constructor phpinspect--make-function-generated)) - "A PHP function." - (name-symbol nil - :type symbol - :documentation - "A symbol associated with the name of the function") - (scope nil - :type phpinspect-scope - :documentation - "When the function is a method, this should contain the -scope of the function as returned by `phpinspect-parse-scope`.") - (arguments nil - :type list - :documentation - "A simple list with function arguments and their -types in tuples. Each list should have the name of the variable -as first element and the type as second element.") - (return-type nil - :type phpinspect--type - :documentation - "A phpinspect--type object representing the the -return type of the function.")) - -(defmacro phpinspect--make-function (&rest property-list) - `(phpinspect--make-function-generated - ,@(phpinspect--wrap-plist-name-in-symbol property-list))) - -(cl-defmethod phpinspect--function-set-name ((func phpinspect--function) (name string)) - (setf (phpinspect--function-name-symbol func) (intern name phpinspect-name-obarray))) - -(cl-defgeneric phpinspect--function-name ((func phpinspect--function))) - -(cl-defmethod phpinspect--function-name ((func phpinspect--function)) - (symbol-name (phpinspect--function-name-symbol func))) - - -(cl-defstruct (phpinspect--variable (:constructor phpinspect--make-variable)) - "A PHP Variable." - (name nil - :type string - :documentation - "A string containing the name of the variable.") - (scope nil - :type phpinspect-scope - :documentation - "When the variable is an object attribute, this should -contain the scope of the variable as returned by -`phpinspect-parse-scope`") - (type nil - :type string - :documentation - "A string containing the FQN of the variable's type")) - (cl-defstruct (phpinspect--completion (:constructor phpinspect--construct-completion)) "Contains a possible completion value with all it's attributes." @@ -995,11 +91,6 @@ contain the scope of the variable as returned by (annotation nil :type string) (kind nil :type symbol)) -(cl-defgeneric phpinspect--format-type-name (name) - (string-remove-prefix "\\" name)) - -(cl-defmethod phpinspect--format-type-name ((type phpinspect--type)) - (phpinspect--format-type-name (phpinspect--type-name type))) (cl-defgeneric phpinspect--make-completion (completion-candidate) "Creates a `phpinspect--completion` for a possible completion @@ -1078,21 +169,6 @@ accompanied by all of its enclosing tokens." resolvecontext)) resolvecontext))) -(defun phpinspect-toggle-logging () - (interactive) - (if (setq phpinspect--debug (not phpinspect--debug)) - (message "Enabled phpinspect logging.") - (message "Disabled phpinspect logging."))) - - -(defsubst phpinspect--log (&rest args) - (when phpinspect--debug - (with-current-buffer (get-buffer-create "**phpinspect-logs**") - (unless window-point-insertion-type - (set (make-local-variable 'window-point-insertion-type) t)) - (goto-char (buffer-end 1)) - (insert (concat "[" (format-time-string "%H:%M:%S") "]: " - (apply #'format args) "\n"))))) (defsubst phpinspect-cache-project-class (project-root indexed-class) (when project-root @@ -1126,33 +202,6 @@ accompanied by all of its enclosing tokens." (seq-uniq classes #'eq))) - -;; (defun phpinspect-get-cached-project-class-methods -;; (project-root class-fqn &optional static) -;; (phpinspect--log "Getting cached project class methods for %s (%s)" -;; project-root class-fqn) -;; (when project-root -;; (let ((index (phpinspect-get-or-create-cached-project-class -;; project-root -;; class-fqn))) -;; (when index -;; (phpinspect--log "Retrieved class index, starting method collection %s (%s)" -;; project-root class-fqn) - -;; (let ((methods-key (if static 'static-methods 'methods)) -;; (methods)) - -;; (dolist (class (phpinspect-get-project-class-inherit-classes -;; project-root -;; index)) -;; (dolist (method (alist-get methods-key class)) -;; (push method methods))) - -;; (dolist (method (alist-get methods-key index)) -;; (push method methods)) - -;; (nreverse methods)))))) - (defun phpinspect-get-cached-project-class-methods (project-root class-fqn &optional static) (phpinspect--log "Getting cached project class methods for %s (%s)" project-root class-fqn) @@ -1175,19 +224,6 @@ accompanied by all of its enclosing tokens." ,method-name-sym) (throw (quote ,break-sym) func))))))) -;; (defsubst phpinspect-get-cached-project-class-method-type -;; (project-root class-fqn method-name) -;; (when project-root -;; (phpinspect--log "Getting cached project class method type for %s (%s::%s)" -;; project-root class-fqn method-name) -;; (let ((found-method -;; (phpinspect-find-function-in-list -;; method-name -;; (phpinspect-get-cached-project-class-methods project-root class-fqn)))) -;; (when found-method -;; (phpinspect--log "Found method: %s" found-method) -;; (phpinspect--function-return-type found-method))))) - (defsubst phpinspect-get-cached-project-class-method-type (project-root class-fqn method-name) (when project-root @@ -1636,427 +672,6 @@ resolve types of function argument variables." (phpinspect-function-argument-list enclosing-token)))))) type)) -(defun phpinspect--function-from-scope (scope) - (cond ((and (phpinspect-static-p (cadr scope)) - (phpinspect-function-p (caddr scope))) - (caddr scope)) - ((phpinspect-function-p (cadr scope)) - (cadr scope)) - (t nil))) - -(defun phpinspect--make-type-resolver (types &optional token-tree namespace) - "Little wrapper closure to pass around and resolve types with." - - (let* ((inside-class - (if token-tree (or (phpinspect--find-innermost-incomplete-class token-tree) - (phpinspect--find-class-token token-tree)))) - (inside-class-name (if inside-class (phpinspect--get-class-name-from-token - inside-class))) - (self-type (phpinspect--make-type :name "self")) - (static-type (phpinspect--make-type :name "static"))) - (lambda (type) - (phpinspect--type-resolve - types - namespace - (if (and inside-class-name (or (phpinspect--type= type self-type) - (phpinspect--type= type static-type))) - (progn - (phpinspect--log "Returning inside class name for %s : %s" - type inside-class-name) - (phpinspect--make-type :name inside-class-name)) - ;; else - type))))) - - -(defun phpinspect--resolve-type-name (types namespace type) - "Get the FQN for TYPE, using TYPES and NAMESPACE as context. - -TYPES must be an alist with class names as cars and FQNs as cdrs. -NAMESPACE may be nil, or a string with a namespace FQN." - (phpinspect--log "Resolving %s from namespace %s" type namespace) - ;; Absolute FQN - (cond ((string-match "^\\\\" type) - type) - - ;; Native type - ((member type phpinspect-native-types) - (concat "\\" type)) - - ;; Relative FQN - ((and namespace (string-match "\\\\" type)) - (concat "\\" namespace "\\" type)) - - ;; Clas|interface|trait name - (t (let ((from-types (assoc-default (phpinspect-intern-name type) types #'eq))) - (concat "\\" (cond (from-types - (phpinspect--type-name from-types)) - (namespace - (concat namespace "\\" type)) - (t type))))))) - -(cl-defmethod phpinspect--type-resolve (types namespace (type phpinspect--type)) - (unless (phpinspect--type-fully-qualified type) - (phpinspect--type-set-name - type - (phpinspect--resolve-type-name types namespace (phpinspect--type-name type))) - (setf (phpinspect--type-fully-qualified type) t)) - type) - -(defun phpinspect-var-annotation-p (token) - (phpinspect-token-type-p token :var-annotation)) - -(defun phpinspect-return-annotation-p (token) - (phpinspect-token-type-p token :return-annotation)) - -(defun phpinspect--index-function-arg-list (type-resolver arg-list) - (let ((arg-index) - (current-token) - (arg-list (cl-copy-list arg-list))) - (while (setq current-token (pop arg-list)) - (cond ((and (phpinspect-word-p current-token) - (phpinspect-variable-p (car arg-list))) - (push `(,(cadr (pop arg-list)) - ,(funcall type-resolver (phpinspect--make-type :name (cadr current-token)))) - arg-index)) - ((phpinspect-variable-p (car arg-list)) - (push `(,(cadr (pop arg-list)) - nil) - arg-index)))) - (nreverse arg-index))) - -(defun phpinspect--index-function-from-scope (type-resolver scope comment-before) - (let* ((php-func (cadr scope)) - (declaration (cadr php-func)) - (type (if (phpinspect-word-p (car (last declaration))) - (phpinspect--make-type :name (cadar (last declaration)))))) - - ;; @return annotation. Has precedence over typehint when dealing with a collection. - (let* ((is-collection - (when type - (member (phpinspect--type-name - (funcall type-resolver type)) phpinspect-collection-types))) - (return-annotation-type - (when (or (not type) is-collection) - (cadadr - (seq-find #'phpinspect-return-annotation-p - comment-before))))) - (phpinspect--log "found return annotation %s" return-annotation-type) - - (when return-annotation-type - (cond ((not type) - (setq type (funcall type-resolver - (phpinspect--make-type :name return-annotation-type)))) - (is-collection - (phpinspect--log "Detected collection type in: %s" scope) - (setf (phpinspect--type-contains type) - (funcall type-resolver - (phpinspect--make-type :name return-annotation-type))) - (setf (phpinspect--type-collection type) t))))) - - (phpinspect--make-function - :scope `(,(car scope)) - :name (cadadr (cdr declaration)) - :return-type (if type (funcall type-resolver type) - phpinspect--null-type) - :arguments (phpinspect--index-function-arg-list - type-resolver - (phpinspect-function-argument-list php-func))))) - -(defun phpinspect--index-const-from-scope (scope) - (phpinspect--make-variable - :scope `(,(car scope)) - :name (cadr (cadr (cadr scope))))) - -(defun phpinspect--var-annotations-from-token (token) - (seq-filter #'phpinspect-var-annotation-p token)) - -(defun phpinspect--index-variable-from-scope (type-resolver scope comment-before) - "Index the variable inside `scope`." - (let* ((var-annotations (phpinspect--var-annotations-from-token comment-before)) - (variable-name (cadr (cadr scope))) - (type (if var-annotations - ;; Find the right annotation by variable name - (or (cadr (cadr (seq-find (lambda (annotation) - (string= (cadr (caddr annotation)) variable-name)) - var-annotations))) - ;; Give up and just use the last one encountered - (cadr (cadr (car (last var-annotations)))))))) - (phpinspect--log "calling resolver from index-variable-from-scope") - (phpinspect--make-variable - :name variable-name - :scope `(,(car scope)) - :type (if type (funcall type-resolver (phpinspect--make-type :name type)))))) - -(defun phpinspect-doc-block-p (token) - (phpinspect-token-type-p token :doc-block)) - -(defun phpinspect--get-class-name-from-token (class-token) - (let ((subtoken (seq-find (lambda (word) - (and (phpinspect-word-p word) - (not (string-match - (concat "^" (phpinspect-handler-regexp 'class-keyword)) - (concat (cadr word) " "))))) - (cadr class-token)))) - (cadr subtoken))) - -(defun phpinspect--index-class (type-resolver class) - "Create an alist with relevant attributes of a parsed class." - (phpinspect--log "INDEXING CLASS") - (let ((methods) - (static-methods) - (static-variables) - (variables) - (constants) - (extends) - (implements) - (class-name (phpinspect--get-class-name-from-token class)) - ;; Keep track of encountered comments to be able to use type - ;; annotations. - (comment-before)) - - ;; Find out what the class extends or implements - (let ((enc-extends nil) - (enc-implements nil)) - (dolist (word (cadr class)) - (if (phpinspect-word-p word) - (cond ((string= (cadr word) "extends") - (phpinspect--log "Class %s extends other classes" class-name) - (setq enc-extends t)) - ((string= (cadr word) "implements") - (setq enc-extends nil) - (phpinspect--log "Class %s implements in interface" class-name) - (setq enc-implements t)) - (t - (phpinspect--log "Calling Resolver from index-class on %s" (cadr word)) - (cond (enc-extends - (push (funcall type-resolver (phpinspect--make-type - :name (cadr word))) - extends)) - (enc-implements - (push (funcall type-resolver (phpinspect--make-type - :name (cadr word))) - implements)))))))) - - (dolist (token (caddr class)) - (cond ((phpinspect-scope-p token) - (cond ((phpinspect-const-p (cadr token)) - (push (phpinspect--index-const-from-scope token) constants)) - - ((phpinspect-variable-p (cadr token)) - (push (phpinspect--index-variable-from-scope type-resolver - token - comment-before) - variables)) - - ((phpinspect-static-p (cadr token)) - (cond ((phpinspect-function-p (cadadr token)) - (push (phpinspect--index-function-from-scope type-resolver - (list (car token) - (cadadr token)) - comment-before) - static-methods)) - - ((phpinspect-variable-p (cadadr token)) - (push (phpinspect--index-variable-from-scope type-resolver - (list (car token) - (cadadr token)) - comment-before) - static-variables)))) - (t - (phpinspect--log "comment-before is: %s" comment-before) - (push (phpinspect--index-function-from-scope type-resolver - token - comment-before) - methods)))) - ((phpinspect-static-p token) - (cond ((phpinspect-function-p (cadr token)) - (push (phpinspect--index-function-from-scope type-resolver - `(:public - ,(cadr token)) - comment-before) - static-methods)) - - ((phpinspect-variable-p (cadr token)) - (push (phpinspect--index-variable-from-scope type-resolver - `(:public - ,(cadr token)) - comment-before) - static-variables)))) - ((phpinspect-const-p token) - ;; Bare constants are always public - (push (phpinspect--index-const-from-scope (list :public token)) - constants)) - ((phpinspect-function-p token) - ;; Bare functions are always public - (push (phpinspect--index-function-from-scope type-resolver - (list :public token) - comment-before) - methods)) - ((phpinspect-doc-block-p token) - (phpinspect--log "setting comment-before %s" token) - (setq comment-before token)) - - ;; Prevent comments from sticking around too long - (t - (phpinspect--log "Unsetting comment-before") - (setq comment-before nil)))) - - ;; Dirty hack that assumes the constructor argument names to be the same as the object - ;; attributes' names. - ;;; - ;; TODO: actually check the types of the variables assigned to object attributes - (let* ((constructor-sym (phpinspect-intern-name "__construct")) - (constructor (seq-find (lambda (method) - (eq (phpinspect--function-name-symbol method) - constructor-sym)) - methods))) - (when constructor - (phpinspect--log "Constructor was found") - (dolist (variable variables) - (when (not (phpinspect--variable-type variable)) - (phpinspect--log "Looking for variable type in constructor arguments (%s)" - variable) - (let ((constructor-parameter-type - (car (alist-get (phpinspect--variable-name variable) - (phpinspect--function-arguments constructor) - nil nil #'string=)))) - (if constructor-parameter-type - (setf (phpinspect--variable-type variable) - (funcall type-resolver constructor-parameter-type)))))))) - - (let ((class-name (funcall type-resolver (phpinspect--make-type :name class-name)))) - `(,class-name . - (phpinspect--indexed-class - (methods . ,methods) - (class-name . ,class-name) - (static-methods . ,static-methods) - (static-variables . ,static-variables) - (variables . ,variables) - (constants . ,constants) - (extends . ,extends) - (implements . ,implements)))))) - -(defsubst phpinspect-namespace-body (namespace) - "Return the nested tokens in NAMESPACE tokens' body. -Accounts for namespaces that are defined with '{}' blocks." - (if (phpinspect-block-p (caddr namespace)) - (cdaddr namespace) - (cdr namespace))) - -(defun phpinspect--index-classes (types classes &optional namespace indexed) - "Index the class tokens in `classes`, using the types in `types` -as Fully Qualified names. `namespace` will be assumed the root -namespace if not provided" - (if classes - (let ((class (pop classes))) - (push (phpinspect--index-class - (phpinspect--make-type-resolver types class namespace) - class) - indexed) - (phpinspect--index-classes types classes namespace indexed)) - (nreverse indexed))) - -(defun phpinspect--use-to-type (use) - (let* ((fqn (cadr (cadr use))) - (type (phpinspect--make-type :name fqn :fully-qualified t)) - (type-name (if (and (phpinspect-word-p (caddr use)) - (string= "as" (cadr (caddr use)))) - (cadr (cadddr use)) - (progn (string-match "[^\\]+$" fqn) - (match-string 0 fqn))))) - (cons (phpinspect-intern-name type-name) type))) - -(defun phpinspect--uses-to-types (uses) - (mapcar #'phpinspect--use-to-type uses)) - -(defun phpinspect--index-namespace (namespace) - (phpinspect--index-classes - (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p namespace)) - (seq-filter #'phpinspect-class-p namespace) - (cadadr namespace))) - -(defun phpinspect--index-namespaces (namespaces &optional indexed) - (if namespaces - (progn - (push (phpinspect--index-namespace (pop namespaces)) indexed) - (phpinspect--index-namespaces namespaces indexed)) - (apply #'append (nreverse indexed)))) - -(defun phpinspect--index-functions (&rest _args) - "TODO: implement function indexation. This is a stub function.") - -(defun phpinspect--index-tokens (tokens) - "Index TOKENS as returned by `phpinspect--parse-current-buffer`." - `(phpinspect--root-index - ,(append - (append '(classes) - (phpinspect--index-namespaces (seq-filter #'phpinspect-namespace-p tokens)) - (phpinspect--index-classes - (phpinspect--uses-to-types (seq-filter #'phpinspect-use-p tokens)) - (seq-filter #'phpinspect-class-p tokens)))) - (functions)) - ;; TODO: Implement function indexation - ) - -;; (defun phpinspect--get-or-create-index-for-class-file (class-fqn) -;; (phpinspect--log "Getting or creating index for %s" class-fqn) -;; (phpinspect-get-or-create-cached-project-class -;; (phpinspect-project-root) -;; class-fqn)) - -(defun phpinspect-index-file (file-name) - (phpinspect--index-tokens (phpinspect-parse-file file-name))) - -(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-create project class-fqn)))) - - ;; (let ((existing-index (phpinspect-get-cached-project-class - ;; project-root - ;; class-fqn))) - ;; (or - ;; existing-index - ;; (progn - ;; (let* ((class-file (phpinspect-class-filepath class-fqn)) - ;; (visited-buffer (when class-file (find-buffer-visiting class-file))) - ;; (new-index)) - - ;; (phpinspect--log "No existing index for FQN: %s" class-fqn) - ;; (phpinspect--log "filepath: %s" class-file) - ;; (when class-file - ;; (if visited-buffer - ;; (setq new-index (with-current-buffer visited-buffer - ;; (phpinspect--index-current-buffer))) - ;; (setq new-index (phpinspect-index-file class-file))) - ;; (dolist (class (alist-get 'classes new-index)) - ;; (when class - ;; (phpinspect-cache-project-class - ;; project-root - ;; (cdr class)))) - ;; (alist-get class-fqn (alist-get 'classes new-index) - ;; nil - ;; nil - ;; #'phpinspect--type=)))))))) - - -(defun phpinspect--index-current-buffer () - (phpinspect--index-tokens (phpinspect-parse-current-buffer))) - -(defun phpinspect-index-current-buffer () - "Index a PHP file for classes and the methods they have" - (phpinspect--index-tokens (phpinspect-parse-current-buffer))) - -;; (defun phpinspect--get-variables-for-class (buffer-classes class &optional static) -;; (let ((class-index (or (assoc-default class buffer-classes #'phpinspect--type=) -;; (phpinspect--get-or-create-index-for-class-file class)))) -;; (when class-index -;; (if static -;; (append (alist-get 'static-variables class-index) -;; (alist-get 'constants class-index)) -;; (alist-get 'variables class-index))))) (defun phpinspect--get-variables-for-class (buffer-classes class-name &optional static) (let ((class (phpinspect-get-or-create-cached-project-class @@ -2261,12 +876,6 @@ level of a token. Nested variables are ignored." (defsubst phpinspect-not-variable-p (token) (not (phpinspect-variable-p token))) -(defun phpinspect--get-bare-class-name-from-fqn (fqn) - (car (last (split-string fqn "\\\\")))) - -(cl-defmethod phpinspect--type-bare-name ((type phpinspect--type)) - (phpinspect--get-bare-class-name-from-fqn (phpinspect--type-name type))) - (cl-defmethod phpinspect--make-completion ((completion-candidate phpinspect--variable)) (phpinspect--construct-completion @@ -2488,242 +1097,6 @@ static variables and static methods." (phpinspect--completion-meta (phpinspect--completion-list-get-metadata phpinspect--last-completion-list arg))))) - -(cl-defstruct (phpinspect--cache (:constructor phpinspect--make-cache)) - (active-projects nil - :type alist - :documentation - "An `alist` that contains the root directory - paths of all currently active phpinspect - projects") - (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.")) - -(cl-defstruct (phpinspect--project (:constructor phpinspect--make-project-cache)) - (class-index (make-hash-table :test 'eq :size 100 :rehash-size 40) - :type hash-table - :documentation - "A `hash-table` that contains all of the currently -indexed classes in the project")) - -(cl-defstruct (phpinspect--class (:constructor phpinspect--make-class-generated)) - (project nil - :type phpinspect--project - :documentaton - "The project that this class belongs to") - (index nil - :type phpinspect--indexed-class - :documentation - "The index that this class is derived from") - (methods (make-hash-table :test 'eq :size 20 :rehash-size 20) - :type hash-table - :documentation - "All methods, including those from extended classes.") - (static-methods (make-hash-table :test 'eq :size 20 :rehash-size 20) - :type hash-table - :documentation - "All static methods this class provides, - including those from extended classes.") - (variables nil - :type list - :documentation - "Variables that belong to this class.") - (extended-classes (make-hash-table :test 'eq) - :type hash-table - :documentation - "All extended/implemented classes.") - (subscriptions nil - :type list - :documentation - "A list of subscription functions that should be - called whenever anything about this class is - updated")) - -(cl-defmethod phpinspect--class-trigger-update ((class phpinspect--class)) - (dolist (sub (phpinspect--class-subscriptions class)) - (funcall sub class))) - -(cl-defmethod phpinspect--class-set-index ((class phpinspect--class) - (index (head phpinspect--indexed-class))) - (setf (phpinspect--class-index class) index) - (dolist (method (alist-get 'methods index)) - (phpinspect--class-update-method class method)) - - (dolist (method (alist-get 'static-methods index)) - (phpinspect--class-update-static-method class method)) - - (setf (phpinspect--class-variables class) - (alist-get 'variables index)) - - (setf (phpinspect--class-extended-classes class) - (seq-filter - #'phpinspect--class-p - (mapcar - (lambda (class-name) - (phpinspect--project-get-class-create (phpinspect--class-project class) - class-name)) - `(,@(alist-get 'implements index) ,@(alist-get 'extends index))))) - - (dolist (extended (phpinspect--class-extended-classes class)) - (phpinspect--class-incorporate class extended) - (phpinspect--class-subscribe class extended)) - - (phpinspect--class-trigger-update class)) - -(cl-defmethod phpinspect--class-get-method ((class phpinspect--class) method-name) - (gethash method-name (phpinspect--class-methods class))) - -(cl-defmethod phpinspect--class-get-static-method ((class phpinspect--class) method-name) - (gethash method-name (phpinspect--class-static-methods class))) - -(cl-defmethod phpinspect--class-set-method ((class phpinspect--class) - (method phpinspect--function)) - (phpinspect--log "Adding method by name %s to class" - (phpinspect--function-name method)) - (puthash (phpinspect--function-name-symbol method) - method - (phpinspect--class-methods class))) - -(cl-defmethod phpinspect--class-set-static-method ((class phpinspect--class) - (method phpinspect--function)) - (puthash (phpinspect--function-name-symbol method) - method - (phpinspect--class-static-methods class))) - -(cl-defmethod phpinspect--class-get-method-return-type - ((class phpinspect--class) (method-name symbol)) - (let ((method (phpinspect--class-get-method class method-name))) - (when method - (phpinspect--function-return-type method)))) - -(cl-defmethod phpinspect--class-get-method-list ((class phpinspect--class)) - (let ((methods)) - (maphash (lambda (key method) - (push method methods)) - (phpinspect--class-methods class)) - methods)) - -(cl-defmethod phpinspect--class-update-static-method ((class phpinspect--class) - (method phpinspect--function)) - (let ((existing (gethash (phpinspect--function-name-symbol method) - (phpinspect--class-static-methods class)))) - (if existing - (progn - (unless (eq (phpinspect--function-return-type method) - phpinspect--null-type) - (setf (phpinspect--function-return-type existing) - (phpinspect--function-return-type method)) - (setf (phpinspect--function-arguments existing) - (phpinspect--function-arguments method)))) - (phpinspect--class-set-static-method class method)))) - -(cl-defmethod phpinspect--class-update-method ((class phpinspect--class) - (method phpinspect--function)) - (let ((existing (gethash (phpinspect--function-name-symbol method) - (phpinspect--class-methods class)))) - (if existing - (progn - (unless (eq (phpinspect--function-return-type method) - phpinspect--null-type) - (setf (phpinspect--function-return-type existing) - (phpinspect--function-return-type method)) - (setf (phpinspect--function-arguments existing) - (phpinspect--function-arguments method)))) - (phpinspect--class-set-method class method)))) - -(cl-defmethod phpinspect--class-incorporate ((class phpinspect--class) - (other-class phpinspect--class)) - (maphash (lambda (k method) - (phpinspect--class-update-method class method)) - (phpinspect--class-methods other-class))) - -(cl-defmethod phpinspect--class-subscribe ((class phpinspect--class) - (subscription-class phpinspect--class)) - (let ((update-function - (lambda (new-class) - (phpinspect--class-incorporate class new-class) - (phpinspect--class-trigger-update class)))) - (push update-function (phpinspect--class-subscriptions subscription-class)))) -1 -(cl-defgeneric phpinspect--cache-getproject - ((cache phpinspect--cache) (project-name string)) - "Get project by PROJECT-NAME that is located in CACHE.") - -(cl-defmethod phpinspect--cache-getproject - ((cache phpinspect--cache) (project-root string)) - (gethash project-root (phpinspect--cache-projects cache))) - -(cl-defgeneric 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.") - -(cl-defmethod phpinspect--cache-get-project-create - ((cache phpinspect--cache) (project-root string)) - (or (phpinspect--cache-getproject cache project-root) - (puthash project-root - (phpinspect--make-project-cache) - (phpinspect--cache-projects cache)))) - -(cl-defgeneric phpinspect--project-add-class - ((project phpinspect--project) (class (head phpinspect--indexed-class))) - "Add an indexed CLASS to PROJECT.") - -(cl-defmethod phpinspect--project-add-class - ((project phpinspect--project) (indexed-class (head phpinspect--indexed-class))) - (let* ((class-name (phpinspect--type-name-symbol - (alist-get 'class-name (cdr indexed-class)))) - (existing-class (gethash class-name - (phpinspect--project-class-index project)))) - (if existing-class - (phpinspect--class-set-index existing-class indexed-class) - (let ((new-class (phpinspect--make-class-generated :project project))) - (phpinspect--class-set-index new-class indexed-class) - (puthash class-name new-class (phpinspect--project-class-index project)))))) - -(cl-defgeneric phpinspect--project-get-class - ((project phpinspect--project) (class-fqn phpinspect--type)) - "Get indexed class by name of CLASS-FQN stored in PROJECT.") - -(cl-defmethod phpinspect--project-get-class-create - ((project phpinspect--project) (class-fqn phpinspect--type)) - (let ((class (phpinspect--project-get-class project class-fqn))) - (unless class - (setq class (phpinspect--make-class-generated :project project)) - (puthash (phpinspect--type-name-symbol class-fqn) - class - (phpinspect--project-class-index project)) - - (let* ((class-file (phpinspect-class-filepath class-fqn)) - (visited-buffer (when class-file (find-buffer-visiting class-file))) - (new-index) - (class-index)) - - (phpinspect--log "No existing index for FQN: %s" class-fqn) - (phpinspect--log "filepath: %s" class-file) - (when class-file - (if visited-buffer - (setq new-index (with-current-buffer visited-buffer - (phpinspect--index-current-buffer))) - (setq new-index (phpinspect-index-file class-file))) - (setq class-index - (alist-get class-fqn (alist-get 'classes new-index) - nil - nil - #'phpinspect--type=)) - (when class-index - (phpinspect--class-set-index class class-index))))) - class)) - -(cl-defmethod phpinspect--project-get-class - ((project phpinspect--project) (class-fqn phpinspect--type)) - (gethash (phpinspect--type-name-symbol class-fqn) - (phpinspect--project-class-index project))) - (defun phpinspect--get-or-create-global-cache () "Get `phpinspect-cache'. If its value is nil, it is created and then returned."