WIP: Split code up into separate files

WIP-incremental-parsing
Hugo Thunnissen 2 years ago
parent 74bd0ad434
commit e07e1ed9e6

@ -0,0 +1,63 @@
;;; phpinspect.el --- PHP parsing and completion package -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(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

@ -0,0 +1,168 @@
;;; phpinspect-class.el --- PHP parsing module -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(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

@ -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 <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'phpinspect-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

@ -0,0 +1,828 @@
;;; phpinspect-parser.el --- PHP parsing module -*- lexical-binding: t; -*-
;; Copyright (C) 2021 Free Software Foundation, Inc
;; Author: Hugo Thunnissen <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(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

@ -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 <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'phpinspect-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

@ -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 <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'phpinspect-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

@ -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 <devel@hugot.nl>
;; Keywords: php, languages, tools, convenience
;; Version: 0
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(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

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save