;;; phpinspect-bmap.el --- PHP parsing and completion package -*- lexical-binding: t; -*- ;; Copyright (C) 2021-2023 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-splayt) (require 'phpinspect-meta) (require 'phpinspect-changeset) (require 'phpinspect-parse-context) (require 'phpinspect-util) (require 'compat) (require 'phpinspect-token-predicates) (eval-when-compile (defvar phpinspect-parse-context nil "dummy for compilation") (declare-function phpinspect-pctx-register-changeset "phpinspect-parse-context" (pctx changeset)) (phpinspect--declare-log-group 'bmap)) (cl-defstruct (phpinspect-bmap (:constructor phpinspect-make-bmap)) (starts (make-hash-table :test #'eql :size (floor (/ (point-max) 2)) :rehash-size 1.5)) (ends (make-hash-table :test #'eql :size (floor (/ (point-max) 2)) :rehash-size 1.5)) (meta (make-hash-table :test #'eq :size (floor (/ (point-max) 2)) :rehash-size 1.5)) (token-stack nil :type list) (overlays (phpinspect-make-splayt) :type phpinspect-splayt) (declarations (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The declaration tokens encountered.") (imports (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The import statements encountered.") (functions (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The function definitions encountered.") (classes (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The classes encountered.") (class-variables (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The class attribute variables encountered") (namespaces (phpinspect-make-splayt) :type phpinspect-splayt :documentation "The namespaces encountered") (-root-meta nil :type phpinspect-meta) (last-token-start nil :type integer)) (define-inline phpinspect-bmap-root-meta (bmap) (inline-letevals (bmap) (inline-quote (with-memoization (phpinspect-bmap--root-meta ,bmap) (phpinspect-bmap-token-starting-at ,bmap (phpinspect-bmap-last-token-start ,bmap)))))) (defsubst phpinspect-make-region (start end) (list start end)) (defalias 'phpinspect-region-start #'car) (defalias 'phpinspect-region-end #'cadr) (defsubst phpinspect-region-size (region) (- (phpinspect-region-end region) (phpinspect-region-start region))) (defsubst phpinspect-region> (reg1 reg2) (> (phpinspect-region-size reg1) (phpinspect-region-size reg2))) (defsubst phpinspect-region< (reg1 reg2) (< (phpinspect-region-size reg1) (phpinspect-region-size reg2))) (defsubst phpinspect-region-overlaps-point (reg point) (and (> (phpinspect-region-end reg) point) (<= (phpinspect-region-start reg) point))) (defsubst phpinspect-region-overlaps (reg1 reg2) (or (phpinspect-region-overlaps-point reg1 (phpinspect-region-start reg2)) (phpinspect-region-overlaps-point reg1 (- (phpinspect-region-end reg2) 1)) (phpinspect-region-overlaps-point reg2 (phpinspect-region-start reg1)) (phpinspect-region-overlaps-point reg2 (- (phpinspect-region-end reg1) 1)))) (defsubst phpinspect-region-encloses (reg1 reg2) (and (<= (phpinspect-region-start reg1) (phpinspect-region-start reg2)) (>= (phpinspect-region-end reg1) (phpinspect-region-end reg2)))) (define-inline phpinspect-overlay-bmap (overlay) (inline-quote (car (nthcdr 4 ,overlay)))) (define-inline phpinspect-overlay-delta (overlay) (inline-quote (cadddr ,overlay))) (define-inline phpinspect-overlay-start (overlay) (inline-quote (cadr ,overlay))) (define-inline phpinspect-overlay-end (overlay) (inline-quote (caddr ,overlay))) (define-inline phpinspect-overlay-overlaps-point (overlay point) (inline-letevals (overlay point) (inline-quote (and (> (phpinspect-overlay-end ,overlay) ,point) (<= (phpinspect-overlay-start ,overlay) ,point))))) (defsubst phpinspect-bmap-register (bmap start end token &optional whitespace-before overlay) (let* ((starts (phpinspect-bmap-starts bmap)) (ends (phpinspect-bmap-ends bmap)) (meta (phpinspect-bmap-meta bmap)) (last-token-start (phpinspect-bmap-last-token-start bmap)) (existing-end (gethash end ends)) (token-meta (or overlay (phpinspect-make-meta nil start end whitespace-before token)))) (when (< end start) (error "Token %s ends before it starts. Start: %s, end: %s" token start end)) (unless whitespace-before (setq whitespace-before "")) (puthash start token-meta starts) (cond ((phpinspect-use-p (phpinspect-meta-token token-meta)) (phpinspect-splayt-insert (phpinspect-bmap-imports bmap) (phpinspect-meta-start token-meta) token-meta)) ((phpinspect-class-p (phpinspect-meta-token token-meta)) (phpinspect-splayt-insert (phpinspect-bmap-classes bmap) (phpinspect-meta-start token-meta) token-meta)) ((phpinspect-declaration-p (phpinspect-meta-token token-meta)) (phpinspect-splayt-insert (phpinspect-bmap-declarations bmap) (phpinspect-meta-start token-meta) token-meta)) ((phpinspect-function-p (phpinspect-meta-token token-meta)) (phpinspect-splayt-insert (phpinspect-bmap-functions bmap) (phpinspect-meta-start token-meta) token-meta)) ((phpinspect-namespace-p (phpinspect-meta-token token-meta)) (phpinspect-splayt-insert (phpinspect-bmap-namespaces bmap) (phpinspect-meta-start token-meta) token-meta)) ((or (phpinspect-const-p (phpinspect-meta-token token-meta)) (phpinspect-class-variable-p (phpinspect-meta-token token-meta))) (phpinspect-splayt-insert (phpinspect-bmap-class-variables bmap) (phpinspect-meta-start token-meta) token-meta))) (if existing-end (push token existing-end) (puthash end (list token-meta) ends)) (puthash token token-meta meta) (when (and last-token-start (<= start last-token-start)) (let ((child) (stack (phpinspect-bmap-token-stack bmap))) (while (and (car stack) (>= (phpinspect-meta-start (car stack)) start)) (setq child (pop stack)) (phpinspect-meta-set-parent child token-meta)) (setf (phpinspect-bmap-token-stack bmap) stack))) (setf (phpinspect-bmap-last-token-start bmap) start) (push token-meta (phpinspect-bmap-token-stack bmap)))) (define-inline phpinspect-pctx-register-token (pctx token start end) (inline-letevals (pctx) (inline-quote (phpinspect-bmap-register (phpinspect-pctx-bmap ,pctx) ,start ,end ,token (phpinspect-pctx-consume-whitespace ,pctx))))) (defsubst phpinspect-overlay-p (overlay) (and (listp overlay) (eq 'overlay (car overlay)))) (defsubst phpinspect-bmap-overlay-at-point (bmap point) (let ((overlay (phpinspect-splayt-find-largest-before (phpinspect-bmap-overlays bmap) point))) (when (and overlay (phpinspect-overlay-overlaps-point overlay point)) overlay))) (cl-defmethod phpinspect-bmap-token-starting-at ((overlay (head overlay)) point) (phpinspect-bmap-token-starting-at (phpinspect-overlay-bmap overlay) (- point (phpinspect-overlay-delta overlay)))) (cl-defmethod phpinspect-bmap-token-starting-at ((bmap phpinspect-bmap) point) (let ((overlay (phpinspect-bmap-overlay-at-point bmap point))) (if overlay (phpinspect-bmap-token-starting-at overlay point) (gethash point (phpinspect-bmap-starts bmap))))) (cl-defmethod phpinspect-bmap-tokens-ending-at ((overlay (head overlay)) point) (phpinspect-bmap-tokens-ending-at (phpinspect-overlay-bmap overlay) (- point (phpinspect-overlay-delta overlay)))) (cl-defmethod phpinspect-bmap-tokens-ending-at ((bmap phpinspect-bmap) point) (let ((overlay (phpinspect-bmap-overlay-at-point bmap point))) (if overlay (phpinspect-bmap-tokens-ending-at overlay point) (gethash point (phpinspect-bmap-ends bmap))))) (defsubst phpinspect-bmap-tokens-overlapping (bmap point) (sort (phpinspect-meta-find-overlapping-children (phpinspect-bmap-root-meta bmap) point) #'phpinspect-meta-sort-width)) (defsubst phpinspect-overlay-encloses-meta (overlay meta) (and (>= (phpinspect-meta-start meta) (phpinspect-overlay-start overlay)) (<= (phpinspect-meta-end meta) (phpinspect-overlay-end overlay)))) (cl-defmethod phpinspect-bmap-token-meta ((overlay (head overlay)) token) (phpinspect-bmap-token-meta (phpinspect-overlay-bmap overlay) token)) (cl-defmethod phpinspect-bmap-token-meta ((bmap phpinspect-bmap) token) (unless (phpinspect-probably-token-p token) (error "Unexpected argument, expected `phpinspect-token-p'. Got invalid token %s" token)) (or (gethash token (phpinspect-bmap-meta bmap)) (let ((found?)) (catch 'found (phpinspect-splayt-traverse (overlay (phpinspect-bmap-overlays bmap)) (when (setq found? (phpinspect-bmap-token-meta overlay token)) ;; Hit overlay's node to rebalance tree (phpinspect-splayt-find (phpinspect-bmap-overlays bmap) (phpinspect-overlay-end overlay)) (throw 'found found?))))))) (cl-defmethod phpinspect-bmap-last-token-before-point ((bmap phpinspect-bmap) point) "Search backward in BMAP for last token ending before POINT." (phpinspect-meta-find-child-before-recursively (phpinspect-bmap-root-meta bmap) point)) (defsubst phpinspect-bmap-overlay (bmap bmap-overlay token-meta pos-delta &optional whitespace-before) (let* ((overlays (phpinspect-bmap-overlays bmap)) (start (+ (phpinspect-meta-start token-meta) pos-delta)) (end (+ (phpinspect-meta-end token-meta) pos-delta)) overlay (last-overlay (phpinspect-splayt-node-value (phpinspect-splayt-root-node overlays)))) (phpinspect-meta-with-changeset token-meta (phpinspect-meta-detach-parent token-meta) (phpinspect-meta-shift token-meta pos-delta) (if (and last-overlay (= (- start (length whitespace-before)) (phpinspect-overlay-end last-overlay)) (= pos-delta (phpinspect-overlay-delta last-overlay))) (progn (phpinspect--log "Expanding previous overlay from (%d,%d) to (%d,%d)" (phpinspect-overlay-start last-overlay) (phpinspect-overlay-end last-overlay) (phpinspect-overlay-start last-overlay) end) (setf (phpinspect-overlay-end last-overlay) end) (setf (phpinspect-meta-overlay token-meta) last-overlay)) (phpinspect--log "Inserting new overlay at (%d,%d)" start end) (setq overlay `(overlay ,start ,end ,pos-delta ,bmap-overlay ,token-meta)) (setf (phpinspect-meta-overlay token-meta) overlay) (phpinspect-splayt-insert (phpinspect-bmap-overlays bmap) (phpinspect-overlay-start overlay) overlay)) (phpinspect-bmap-register bmap start end (phpinspect-meta-token token-meta) whitespace-before token-meta)))) (defun phpinspect-bmap-make-location-resolver (bmap) (lambda (token) (let ((meta (phpinspect-bmap-token-meta bmap token))) (if meta (phpinspect-make-region (phpinspect-meta-start meta) (phpinspect-meta-end meta)) (phpinspect-make-region 0 0))))) (provide 'phpinspect-bmap) ;;; phpinspect-bmap.el ends here