branch: externals/phps-mode commit c782b18dc04c72442dd8bea1d572787247c65ae8 Merge: 610358e2ef a42252b2b0 Author: Christian Johansson <christ...@cvj.se> Commit: Christian Johansson <christ...@cvj.se>
Merge branch 'master' into feature/full-sdt --- TODO.md | 25 ++++++ phps-mode-indent.el | 6 +- phps-mode-lexer.el | 191 ++++++++++++++++++++++++++++-------------- phps-mode-syntax-color.el | 2 +- phps-mode.el | 4 +- test/phps-mode-test-indent.el | 4 + test/phps-mode-test-lexer.el | 33 ++++++++ 7 files changed, 200 insertions(+), 65 deletions(-) diff --git a/TODO.md b/TODO.md index 805f5f8299..c2c57ef2fb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,30 @@ # TODO +## Indentation + +### Alternative switch case + +switch($case) { + case 1: + case 2; + echo 'here'; +} + +### Multi-line function call with named arguments + +function myFunction( + $arg1, + $arg2 +) { +} +myFunction( + arg1: + $var1, + arg2: + $var2, +); + + ## Code intelligence * Bookkeeping of chained object operators like WC()->cart->subtotal diff --git a/phps-mode-indent.el b/phps-mode-indent.el index 220cd95ade..f0ea149d44 100644 --- a/phps-mode-indent.el +++ b/phps-mode-indent.el @@ -1126,7 +1126,8 @@ ;; default: ((and (not previous-line-ends-with-opening-bracket) - (not (string-match-p ":[\t ]*$" previous-line-string)) + (not (string-match-p ")[\t ]*:[\t ]*$" previous-line-string)) + (not (string-match-p "^[\t ]*case[\t ]*" previous-line-string)) (or (string-match-p "^[\t ]*case[\t ]+.*\\(;\\|:\\)[\t ]*$" @@ -1312,6 +1313,9 @@ ;; LINE AFTER ALTERNATIVE CASE DEFINITION ;; switch ($array): ;; case 'Something'; + ;; or + ;; switch ($array): + ;; case 'Something': ((and (string-match-p "^[\t ]*\\(case.+\\|default\\)\\(;\\|:\\)[\t ]*$" diff --git a/phps-mode-lexer.el b/phps-mode-lexer.el index 8daae5d1b8..ecbf76cc94 100644 --- a/phps-mode-lexer.el +++ b/phps-mode-lexer.el @@ -12,6 +12,11 @@ ;; * Defines the lexer for this grammar based on the Zend PHP 8.1 Lexer at ;; https://raw.githubusercontent.com/php/php-src/PHP-8.1/Zend/zend_language_scanner.l ;; which is using re2c. +;; +;; Instructions on how to generate new lexer rules +;; 1. Make edits in lexer rules in function `phps-mode-lexer--generate-lexer-rules' +;; 2. Run `eval-buffer' and then `phps-mode-lexer--generate-lexer-rules' +;; 3. Update inline value of `phps-mode-lexer--lambdas-by-state' by running code "(insert (format "%S" phps-mode-lexer--lambdas-by-state))" ;;; Code: @@ -52,20 +57,17 @@ 2147483648 "Limit for 32-bit integer.") -(defconst phps-mode-lexer--bnum - "0b[01]+" - "Boolean number.") - -(defconst phps-mode-lexer--hnum - "0x[0-9a-fA-F]+" - "Hexadecimal number.") - (defconst phps-mode-lexer--lnum - "[0-9]+" + "[0-9]+\\(_[0-9]+\\)*" "Long number.") (defconst phps-mode-lexer--dnum - "\\([0-9]*\\.[0-9]+\\)\\|\\([0-9]+\\.[0-9]*\\)" + (format + "\\(%s?\\.%s\\|%s\\.%s?\\)" + phps-mode-lexer--lnum + phps-mode-lexer--lnum + phps-mode-lexer--lnum + phps-mode-lexer--lnum) "Double number.") (defconst phps-mode-lexer--exponent-dnum @@ -75,6 +77,18 @@ phps-mode-lexer--lnum) "Exponent double number.") +(defconst phps-mode-lexer--hnum + "0x[0-9a-fA-F]+\\(_[0-9a-fA-F]+\\)*" + "Hexadecimal number.") + +(defconst phps-mode-lexer--bnum + "0b[01]+\\(_[01]+\\)*" + "Boolean number.") + +(defconst phps-mode-lexer--onum + "0o[0-7]+\\(_[0-7]+\\)*" + "Octal number.") + (defconst phps-mode-lexer--label "[A-Za-z_[:nonascii:]][0-9A-Za-z_[:nonascii:]]*" "Labels are used for names.") @@ -107,18 +121,17 @@ ;; VARIABLES -(defconst - phps-mode-lexer--lambdas-by-state - #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data (ST_IN_SCRIPTING ((lambda nil (when (looking-at "exit") (let ((match-end (match-end 0)) (match-beginning (match-beginning 0))) (let ((matching-length (- match-end match-beginning))) (when (> matching-length 0) (when (or (not phps-mode-lexer--match-length) (> matching-length phps-mode-lexer--match-length)) (setq phps-mode-lexer--match-length matching-length) (setq phps-mode-lexer--match-body (lambda nil (phps- [...] -]*" "\\(\\$\\|\\.\\.\\.\\)")) (let ((match-end (match-end 0)) (match-beginning (match-beginning 0))) (let ((matching-length (- match-end match-beginning))) (when (> matching-length 0) (when (or (not phps-mode-lexer--match-length) (> matching-length phps-mode-lexer--match-length)) (setq phps-mode-lexer--match-length matching-length) (setq phps-mode-lexer--match-body (lambda nil (phps-mode-lexer--yyless 1) (phps-mode-lexer--return-token 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG (match-beginni [...] +(defvar phps-mode-lexer--lambdas-by-state #s(hash-table size 65 test equal rehash-size 1.5 rehash-threshold 0.8125 data (ST_IN_SCRIPTING (((lambda nil (looking-at "exit")) (lambda nil (phps-mode-lexer--return-token-with-indent 'T_EXIT))) ((lambda nil (looking-at "die")) (lambda nil (phps-mode-lexer--return-token-with-indent 'T_EXIT))) ((lambda nil (looking-at "fn")) (lambda nil (phps-mode-lexer--return-token-with-indent 'T_FN))) ((lambda nil (looking-at "function")) (lambda nil (phps-mod [...] + ]*("))) (lambda nil (phps-mode-lexer--yyless (length "readonly")) (phps-mode-lexer--return-token-with-str 'T_STRING 0))) ((lambda nil (looking-at "unset")) (lambda nil (phps-mode-lexer--return-token-with-indent 'T_UNSET))) ((lambda nil (looking-at "=>")) (lambda nil (phps-mode-lexer--return-token 'T_DOUBLE_ARROW))) ((lambda nil (looking-at "list")) (lambda nil (phps-mode-lexer--return-token-with-indent 'T_LIST))) ((lambda nil (looking-at "array")) (lambda nil (phps-mode-lexer--return-tok [...] +]*" "\\(\\$\\|\\.\\.\\.\\)"))) (lambda nil (phps-mode-lexer--yyless 1) (phps-mode-lexer--return-token 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG (match-beginning 0) (- (match-end 0) 1)))) ((lambda nil (looking-at "&")) (lambda nil (phps-mode-lexer--return-token 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG))) ((lambda nil (looking-at (concat "\\(" "]" "\\|" ")" "\\)"))) (lambda nil (phps-mode-lexer--return-exit-nesting-token))) ((lambda nil (looking-at (concat "\\(" "\\[" "\\|" "(" "\\)"))) (la [...] " phps-mode-lexer--heredoc-label ";? \\|\\$" phps-mode-lexer--label "\\|{\\$" phps-mode-lexer--label "\\|\\${" phps-mode-lexer--label "\\)") nil t))) (if string-start (let* ((start (match-beginning 0)) (end (match-end 0)) (data (buffer-substring-no-properties start end))) (cond ((string-match (concat " " phps-mode-lexer--heredoc-label ";? -") data) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE old-end start) (phps-mode-lexer--begin 'ST_END_HEREDOC)) (t (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE old-end start)))) (progn (signal 'phps-lexer-error (list (format "Found no ending of heredoc starting at %d" old-start) old-start)))))))) (setq phps-mode-lexer--match-data (match-data))))))))) ST_LOOKING_FOR_VARNAME ((lambda nil (when (looking-at (concat phps-mode-lexer--label "[\\[}]")) [...] - '#]")) (let ((match-end (match-end 0)) (match-beginning (match-beginning 0))) (let ((matching-length (- match-end match-beginning))) (when (> matching-length 0) (when (or (not phps-mode-lexer--match-length) (> matching-length phps-mode-lexer--match-length)) (setq phps-mode-lexer--match-length matching-length) (setq phps-mode-lexer--match-body (lambda nil (phps-mode-lexer--yyless 0) (phps-mode-lexer--yy-pop-state) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE))) (setq [...] +") data) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE old-end start) (phps-mode-lexer--begin 'ST_END_HEREDOC)) (t (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE old-end start)))) (progn (signal 'phps-lexer-error (list (format "Found no ending of heredoc starting at %d" old-start) old-start))))))))) ST_LOOKING_FOR_VARNAME (((lambda nil (looking-at (concat phps-mode-lexer--label "[\\[}]"))) (lambda nil (let* ((start (match-beginning 0)) (end (1- (m [...] + '#]"))) (lambda nil (phps-mode-lexer--yyless 0) (phps-mode-lexer--yy-pop-state) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE))) ((lambda nil (looking-at phps-mode-lexer--label)) (lambda nil (phps-mode-lexer--return-token-with-str 'T_STRING 0))) ((lambda nil (looking-at phps-mode-lexer--any-char)) (lambda nil (signal 'phps-lexer-error (list (format "Unexpected character at %d" (match-beginning 0)) (match-beginning 0)))))) quote (((lambda nil (looking-at (concat "#!.* [...] " phps-mode-lexer--heredoc-label ";?\\ -") nil t))) (if string-start (let* ((start (match-beginning 0)) (end (match-end 0)) (_data (buffer-substring-no-properties start end))) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE phps-mode-lexer--generated-new-tokens-index start) (phps-mode-lexer--begin 'ST_END_HEREDOC)) (progn (signal 'phps-lexer-error (list (format "Found no ending of nowdoc starting at %d" start) start)))))))) (setq phps-mode-lexer--match-data (match-data))))))))))) - "Hash-map of lambdas by state.") +") nil t))) (if string-start (let* ((start (match-beginning 0)) (end (match-end 0)) (_data (buffer-substring-no-properties start end))) (phps-mode-lexer--return-token-with-val 'T_ENCAPSED_AND_WHITESPACE phps-mode-lexer--generated-new-tokens-index start) (phps-mode-lexer--begin 'ST_END_HEREDOC)) (progn (signal 'phps-lexer-error (list (format "Found no ending of nowdoc starting at %d" start) start))))))))))) + "Hash-table of lex-analyzer rules organized by state.") (defvar-local phps-mode-lexer--generated-tokens nil "List of current generated tokens.") @@ -360,25 +373,8 @@ (setq old-lambdas (reverse old-lambdas))) - (push - `(lambda() - (when ,conditions - (let ((match-end (match-end 0)) - (match-beginning (match-beginning 0))) - (let ((matching-length (- match-end match-beginning))) - (when (> matching-length 0) - (when (or (not phps-mode-lexer--match-length) - (> matching-length phps-mode-lexer--match-length)) - (setq - phps-mode-lexer--match-length matching-length) - (setq - phps-mode-lexer--match-body (lambda() ,@body)) - (setq - phps-mode-lexer--match-data (match-data)))))))) - old-lambdas) - (setq - old-lambdas - (reverse old-lambdas)) + (push `((lambda() ,conditions) (lambda() ,@body)) old-lambdas) + (setq old-lambdas (reverse old-lambdas)) (puthash state old-lambdas @@ -486,13 +482,10 @@ ;; Setup lexer rules -(defun phps-mode-lxer--generate-lexer-rules () - "Generate lexer match rules." - +(defun phps-mode-lexer--generate-lexer-rules () + "Generate lexer rules." (eval-when-compile - (setq - phps-mode-lexer--lambdas-by-state - (make-hash-table :test 'equal))) + (setq phps-mode-lexer--lambdas-by-state (make-hash-table :test 'equal))) (phps-mode-lexer--match-macro ST_IN_SCRIPTING @@ -705,6 +698,12 @@ (looking-at "trait") (phps-mode-lexer--return-token-with-indent 'T_TRAIT)) + (phps-mode-lexer--match-macro + ST_IN_SCRIPTING + (looking-at (concat "\\(enum\\)" phps-mode-lexer--whitespace "[a-zA-Z_\x80-\xff]")) + (phps-mode-lexer--yyless 4) + (phps-mode-lexer--return-token-with-indent 'T_ENUM (match-beginning 1) (match-end 1))) + (phps-mode-lexer--match-macro ST_IN_SCRIPTING (looking-at "extends") @@ -971,6 +970,18 @@ (looking-at "public") (phps-mode-lexer--return-token-with-indent 'T_PUBLIC)) + (phps-mode-lexer--match-macro + ST_IN_SCRIPTING + (looking-at "readonly") + (phps-mode-lexer--return-token-with-indent 'T_READONLY)) + + ;; Don't treat "readonly(" as a keyword, to allow using it as a function name. + (phps-mode-lexer--match-macro + ST_IN_SCRIPTING + (looking-at (concat "readonly" "[ \n\r\t]*(")) + (phps-mode-lexer--yyless (length "readonly")) + (phps-mode-lexer--return-token-with-str 'T_STRING 0)) + (phps-mode-lexer--match-macro ST_IN_SCRIPTING (looking-at "unset") @@ -1058,13 +1069,13 @@ (phps-mode-lexer--match-macro ST_IN_SCRIPTING - (looking-at "\\*\\\\\\*=") - (phps-mode-lexer--return-token 'T_POW_EQUAL)) + (looking-at "\\*\\\\\\*") + (phps-mode-lexer--return-token 'T_POW)) (phps-mode-lexer--match-macro ST_IN_SCRIPTING - (looking-at "\\*\\\\\\*") - (phps-mode-lexer--return-token 'T_POW)) + (looking-at "\\*\\\\\\*=") + (phps-mode-lexer--return-token 'T_POW_EQUAL)) (phps-mode-lexer--match-macro ST_IN_SCRIPTING @@ -1224,19 +1235,42 @@ (looking-at phps-mode-lexer--bnum) (let* ((start (match-beginning 0)) (end (match-end 0)) - (data (buffer-substring-no-properties (+ start 2) end)) + (data + (replace-regexp-in-string + "_" + "" + (buffer-substring-no-properties (+ start 2) end))) (long-number (string-to-number data 2))) ;; (message "Binary number %s from %s" long-number data) (if (> long-number phps-mode-lexer--long-limit) (phps-mode-lexer--return-token 'T_DNUMBER) (phps-mode-lexer--return-token 'T_LNUMBER)))) + (phps-mode-lexer--match-macro + ST_IN_SCRIPTING + (looking-at phps-mode-lexer--onum) + (let* ((start (match-beginning 0)) + (end (match-end 0)) + (data (string-to-number + (replace-regexp-in-string + "_" + "" + (buffer-substring-no-properties start end)) + 8))) + (if (> data phps-mode-lexer--long-limit) + (phps-mode-lexer--return-token 'T_DNUMBER) + (phps-mode-lexer--return-token 'T_LNUMBER)))) + (phps-mode-lexer--match-macro ST_IN_SCRIPTING (looking-at phps-mode-lexer--lnum) (let* ((start (match-beginning 0)) (end (match-end 0)) - (data (string-to-number (buffer-substring-no-properties start end)))) + (data (string-to-number + (replace-regexp-in-string + "_" + "" + (buffer-substring-no-properties start end))))) ;; (message "Long number: %d" data) (if (> data phps-mode-lexer--long-limit) (phps-mode-lexer--return-token 'T_DNUMBER) @@ -1247,7 +1281,11 @@ (looking-at phps-mode-lexer--hnum) (let* ((start (match-beginning 0)) (end (match-end 0)) - (data (buffer-substring-no-properties (+ start 2) end)) + (data + (replace-regexp-in-string + "_" + "" + (buffer-substring-no-properties (+ start 2) end))) (long-number (string-to-number data 16))) ;; (message "Hexadecimal number %s from %s" long-number data) (if (> long-number phps-mode-lexer--long-limit) @@ -1265,7 +1303,8 @@ (concat "\\(" phps-mode-lexer--lnum "\\|" phps-mode-lexer--hnum "\\|" - phps-mode-lexer--bnum "\\)")) + phps-mode-lexer--bnum "\\|" + phps-mode-lexer--onum "\\)")) (phps-mode-lexer--return-token 'T_NUM_STRING)) (phps-mode-lexer--match-macro @@ -1356,13 +1395,14 @@ (let ((start (match-beginning 0)) (end (match-end 0))) - ;; Allow <?php followed by end of file. (cond + ;; Allow <?php followed by end of file. ((equal end (point-max)) (phps-mode-lexer--begin 'ST_IN_SCRIPTING) (phps-mode-lexer--return-or-skip-token 'T_OPEN_TAG)) + ;; Degenerate case: <?phpX is interpreted as <? phpX with short tags ((phps-mode-lexer--CG 'short-tags) (phps-mode-lexer--yyless 2) (setq end (- end 2)) @@ -1993,21 +2033,50 @@ ;; Run rules based on state (when-let ((lambdas - (gethash - phps-mode-lexer--state - phps-mode-lexer--lambdas-by-state))) - (dolist (lambd lambdas) - (funcall lambd))) - - (when (fboundp 'thread-yield) - (thread-yield)) + (gethash + phps-mode-lexer--state + phps-mode-lexer--lambdas-by-state))) + (let ((lambda-i 0) + (lambda-length (length lambdas))) + (phps-mode-debug-message + (message "Found %d lexer rules in state" lambda-length)) + + (while (< lambda-i lambda-length) + (let ((lambd (nth lambda-i lambdas))) + + (let ((lambda-result + (funcall (nth 0 lambd)))) + (when lambda-result + (let ((match-end (match-end 0)) + (match-beginning (match-beginning 0))) + (let ((matching-length (- match-end match-beginning))) + (when (> matching-length 0) + (when (or + (not phps-mode-lexer--match-length) + (> matching-length phps-mode-lexer--match-length)) + (setq + phps-mode-lexer--match-length matching-length) + (setq + phps-mode-lexer--match-body (nth 1 lambd)) + (setq + phps-mode-lexer--match-data (match-data)) + ;; Debug new matches + (phps-mode-debug-message + (message + "Found new match (%d) %s" + phps-mode-lexer--match-length + phps-mode-lexer--match-body)))))))) + + (when (fboundp 'thread-yield) + (thread-yield))) + (setq lambda-i (1+ lambda-i))))) ;; Did we find a match? (if phps-mode-lexer--match-length (progn (phps-mode-debug-message (message - "Found match %s" + "Found final match %s" phps-mode-lexer--match-body)) (phps-mode-lexer--re2c-execute) diff --git a/phps-mode-syntax-color.el b/phps-mode-syntax-color.el index 5f622b2c08..2615bc8d0b 100644 --- a/phps-mode-syntax-color.el +++ b/phps-mode-syntax-color.el @@ -11,7 +11,7 @@ (defvar phps-mode-syntax-color--token-font-face - #s(hash-table size 149 test equal rehash-size 1.5 rehash-threshold 0.8125 data (T_ERROR font-lock-warning-face T_OPEN_TAG font-lock-constant-face T_OPEN_TAG_WITH_ECHO font-lock-constant-face T_CLOSE_TAG font-lock-constant-face T_START_HEREDOC font-lock-constant-face T_END_HEREDOC font-lock-constant-face T_ELLIPSIS font-lock-constant-face T_COALESCE font-lock-constant-face T_DOUBLE_ARROW font-lock-constant-face T_INC font-lock-constant-face T_DEC font-lock-constant-face T_IS_IDENTICAL f [...] + #s(hash-table size 151 test equal rehash-size 1.5 rehash-threshold 0.8125 data (T_ERROR font-lock-warning-face T_OPEN_TAG font-lock-constant-face T_OPEN_TAG_WITH_ECHO font-lock-constant-face T_CLOSE_TAG font-lock-constant-face T_START_HEREDOC font-lock-constant-face T_END_HEREDOC font-lock-constant-face T_ELLIPSIS font-lock-constant-face T_COALESCE font-lock-constant-face T_DOUBLE_ARROW font-lock-constant-face T_INC font-lock-constant-face T_DEC font-lock-constant-face T_IS_IDENTICAL f [...] "Syntax color table for tokens") (defvar diff --git a/phps-mode.el b/phps-mode.el index e4599d226e..7085f8e654 100644 --- a/phps-mode.el +++ b/phps-mode.el @@ -5,8 +5,8 @@ ;; Author: Christian Johansson <christ...@cvj.se> ;; Maintainer: Christian Johansson <christ...@cvj.se> ;; Created: 3 Mar 2018 -;; Modified: 23 May 2022 -;; Version: 0.4.22 +;; Modified: 26 Jul 2022 +;; Version: 0.4.23 ;; Keywords: tools, convenience ;; URL: https://github.com/cjohansson/emacs-phps-mode diff --git a/test/phps-mode-test-indent.el b/test/phps-mode-test-indent.el index 7fc32fb5d0..dbee99a611 100644 --- a/test/phps-mode-test-indent.el +++ b/test/phps-mode-test-indent.el @@ -792,6 +792,10 @@ "<?php\nif (true) {\n /*\n was here\n */\n echo 'there';\n}" "Line after closing multi-row comment") + (phps-mode-test-indent--should-equal + "<?php\nenum Suit\n{\n case Hearts;\n case Diamonds;\n case Clubs;\n case Spades;\n}" + "Basic Enumeration") + ) (defun phps-mode-test-indent--get-lines-indent-psr-2 () diff --git a/test/phps-mode-test-lexer.el b/test/phps-mode-test-lexer.el index a403f0cbbb..db9655da0f 100644 --- a/test/phps-mode-test-lexer.el +++ b/test/phps-mode-test-lexer.el @@ -284,6 +284,39 @@ phps-mode-lex-analyzer--tokens '((T_OPEN_TAG 1 . 7) (T_STRING 7 . 10) (";" 10 . 11) (T_COMMENT 12 . 31) (T_COMMENT 32 . 51) (T_COMMENT 52 . 70) (T_NAME_QUALIFIED 73 . 80) (";" 80 . 81) (T_COMMENT 82 . 125) (T_COMMENT 126 . 153) (T_COMMENT 154 . 185) (T_NAME_FULLY_QUALIFIED 188 . 192) (";" 192 . 193) (T_COMMENT 194 . 228) (T_COMMENT 229 . 262) (T_COMMENT 263 . 287) (T_NAME_RELATIVE 290 . 303) (";" 303 . 304) (T_COMMENT 305 . 351) (T_COMMENT 352 . 378) (T_COMMENT 379 . 414))))) + (phps-mode-test--with-buffer + "<?php\nenum Suit\n{\n case Hearts;\n case Diamonds;\n case Clubs;\n case Spades;\n}" + "Basic Enumerations" + (should + (equal + phps-mode-lex-analyzer--tokens + '((T_OPEN_TAG 1 . 7) (T_ENUM 7 . 11) (T_STRING 12 . 16) ("{" 17 . 18) (T_CASE 23 . 27) (T_STRING 28 . 34) (";" 34 . 35) (T_CASE 40 . 44) (T_STRING 45 . 53) (";" 53 . 54) (T_CASE 59 . 63) (T_STRING 64 . 69) (";" 69 . 70) (T_CASE 75 . 79) (T_STRING 80 . 86) (";" 86 . 87) ("}" 88 . 89))))) + + + (phps-mode-test--with-buffer + "<?php\n\n\nclass MyClass\n{\n public function __construct(private readonly type propertyName)\n {\n }\n}" + "Read-only auto-injected properties" + (should + (equal + phps-mode-lex-analyzer--tokens + '((T_OPEN_TAG 1 . 7) (T_CLASS 9 . 14) (T_STRING 15 . 22) ("{" 23 . 24) (T_PUBLIC 29 . 35) (T_FUNCTION 36 . 44) (T_STRING 45 . 56) ("(" 56 . 57) (T_PRIVATE 57 . 64) (T_READONLY 65 . 73) (T_STRING 74 . 78) (T_STRING 79 . 91) (")" 91 . 92) ("{" 97 . 98) ("}" 103 . 104) ("}" 105 . 106))))) + + (phps-mode-test--with-buffer + "<?php\nclass User {\n public readonly int $uid;\n\n public function __construct(int $uid) {\n $this->uid = $uid;\n }\n}" + "Read-only Properties" + (should + (equal + phps-mode-lex-analyzer--tokens + '((T_OPEN_TAG 1 . 7) (T_CLASS 7 . 12) (T_STRING 13 . 17) ("{" 18 . 19) (T_PUBLIC 24 . 30) (T_READONLY 31 . 39) (T_STRING 40 . 43) (T_VARIABLE 44 . 48) (";" 48 . 49) (T_PUBLIC 55 . 61) (T_FUNCTION 62 . 70) (T_STRING 71 . 82) ("(" 82 . 83) (T_STRING 83 . 86) (T_VARIABLE 87 . 91) (")" 91 . 92) ("{" 93 . 94) (T_VARIABLE 103 . 108) (T_OBJECT_OPERATOR 108 . 110) (T_STRING 110 . 113) ("=" 114 . 115) (T_VARIABLE 116 . 120) (";" 120 . 121) ("}" 126 . 127) ("}" 128 . 129))))) + + (phps-mode-test--with-buffer + "<?php\n$a = 1234; // decimal number\n$a = 0123; // octal number (equivalent to 83 decimal)\n$a = 0o123; // octal number (as of PHP 8.1.0)\n$a = 0x1A; // hexadecimal number (equivalent to 26 decimal)\n$a = 0b11111111; // binary number (equivalent to 255 decimal)\n$a = 1_234_567; // decimal number (as of PHP 7.4.0)\n?>\n" + "Integers with underscores" + (should + (equal + phps-mode-lex-analyzer--tokens + '((T_OPEN_TAG 1 . 7) (T_VARIABLE 7 . 9) ("=" 10 . 11) (T_LNUMBER 12 . 16) (";" 16 . 17) (T_COMMENT 18 . 35) (T_VARIABLE 36 . 38) ("=" 39 . 40) (T_LNUMBER 41 . 45) (";" 45 . 46) (T_COMMENT 47 . 89) (T_VARIABLE 90 . 92) ("=" 93 . 94) (T_LNUMBER 95 . 100) (";" 100 . 101) (T_COMMENT 102 . 135) (T_VARIABLE 136 . 138) ("=" 139 . 140) (T_LNUMBER 141 . 145) (";" 145 . 146) (T_COMMENT 147 . 195) (T_VARIABLE 196 . 198) ("=" 199 . 200) (T_LNUMBER 201 . 211) (";" 211 . 212) (T_COMMENT 213 . 257 [...] + ) (defun phps-mode-test-lexer--complex-tokens ()