aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Jörg2023-06-30 23:41:06 +0200
committerHarald Jörg2023-06-30 23:41:06 +0200
commita7ff8a76a52d316b27f05cd1267fe94cea9f35d1 (patch)
treed767d5ff79405a6fb4066c20592f4e88faef318e
parent361bf8a1132724516bf7e01d5f91510ffec83991 (diff)
downloademacs-a7ff8a76a52d316b27f05cd1267fe94cea9f35d1.tar.gz
emacs-a7ff8a76a52d316b27f05cd1267fe94cea9f35d1.zip
cperl-mode.el: Support subroutine signatures
Since Perl 5.20, subroutine signatures were available as an experimental feature. With Perl 5.38, they will be always enabled in the new object system. * test/lisp/progmodes/cperl-mode-resources/cperl-bug-64190.pl: * test/lisp/progmodes/cperl-mode-resources/cperl-bug-64364.pl: New test resources. * test/lisp/progmodes/cperl-mode-tests.el (cperl-test-fontify-attrs-and-signatures): Add tests for signatures. (cperl-test-attribute-rx, cperl-test-attribute-list-rx) (cperl-test-prototype-rx, cperl-test-signature-rx): Tests for the new rx sequences. (cperl-test-bug-64190): New test for multiline declarations. (cperl-test-bug-64364): New test for indentation of declarations. * lisp/progmodes/cperl-mode.el: (toplevel): New rx sequences to match Perl variables and attributes. (cperl-declaration-header-p): New function to identify declarations. (cperl-block-declaration-p): Use the new function. (cperl-mode): Use the rx sequences. (cperl-get-state): Use the new function. (cperl-sniff-for-indent): Use the new function. (cperl-find-sub-attrs): Improve fontification of subroutine prototypes and attributes while typing when jit-lock-mode is active. Detect signatures, and distinguish them from prototypes. (cperl-find-pods-heres): Use the rx sequences to detect subroutines. (cperl-init-faces): Use the rx sequences for fontification.
-rw-r--r--lisp/progmodes/cperl-mode.el388
-rw-r--r--test/lisp/progmodes/cperl-mode-resources/cperl-bug-64190.pl24
-rw-r--r--test/lisp/progmodes/cperl-mode-resources/cperl-bug-64364.pl26
-rw-r--r--test/lisp/progmodes/cperl-mode-tests.el143
4 files changed, 489 insertions, 92 deletions
diff --git a/lisp/progmodes/cperl-mode.el b/lisp/progmodes/cperl-mode.el
index 66f01109e3f..fb636d0fb78 100644
--- a/lisp/progmodes/cperl-mode.el
+++ b/lisp/progmodes/cperl-mode.el
@@ -1187,8 +1187,7 @@ The expansion is entirely correct because it uses the C preprocessor."
1187 "A regular expression for the name of a \"basic\" Perl variable. 1187 "A regular expression for the name of a \"basic\" Perl variable.
1188Neither namespace separators nor sigils are included. As is, 1188Neither namespace separators nor sigils are included. As is,
1189this regular expression applies to labels,subroutine calls where 1189this regular expression applies to labels,subroutine calls where
1190the ampersand sigil is not required, and names of subroutine 1190the ampersand sigil is not required, and names of attributes.")
1191attributes.")
1192 1191
1193 (defconst cperl--label-rx 1192 (defconst cperl--label-rx
1194 `(sequence symbol-start 1193 `(sequence symbol-start
@@ -1225,6 +1224,30 @@ is a legal variable name).")
1225 (in "!\"$%&'()+,-./:;<=>?@\\]^_`|~")) ; $., $|, $", ... but not $^ or ${ 1224 (in "!\"$%&'()+,-./:;<=>?@\\]^_`|~")) ; $., $|, $", ... but not $^ or ${
1226 "The list of Perl \"punctuation\" variables, as listed in perlvar.") 1225 "The list of Perl \"punctuation\" variables, as listed in perlvar.")
1227 1226
1227 (defconst cperl--basic-scalar-rx
1228 `(sequence "$" ,cperl--basic-identifier-rx)
1229 "Regular expression for a scalar (without package).
1230This regexp intentionally does not support spaces (nor newlines
1231and comments) between the sigil and the identifier, for
1232educational reasons. So \"$foo\" will be matched, but \"$ foo\"
1233or \"${ foo }\" will not.")
1234
1235 (defconst cperl--basic-array-rx
1236 `(sequence "@" ,cperl--basic-identifier-rx)
1237 "Regular expression for an array variable (without package).
1238This regexp intentionally does not support spaces (nor newlines
1239and comments) between the sigil and the identifier, for
1240educational reasons. So \"@foo\" will be matched, but \"@ foo\"
1241or \"@{ foo }\" will not.")
1242
1243 (defconst cperl--basic-hash-rx
1244 `(sequence "%" ,cperl--basic-identifier-rx)
1245 "Regular expression for a hash variable (without package).
1246This regexp intentionally does not support spaces (nor newlines
1247and comments) between the sigil and the identifier, for
1248educational reasons. So \"%foo\" will be matched, but \"% foo\"
1249or \"%{ foo }\" will not.")
1250
1228 (defconst cperl--ws-rx 1251 (defconst cperl--ws-rx
1229 '(sequence (or space "\n")) 1252 '(sequence (or space "\n"))
1230 "Regular expression for a single whitespace in Perl.") 1253 "Regular expression for a single whitespace in Perl.")
@@ -1246,6 +1269,27 @@ is a legal variable name).")
1246 `(1+ ,cperl--ws-or-comment-rx) 1269 `(1+ ,cperl--ws-or-comment-rx)
1247 "Regular expression for a sequence of whitespace and comments in Perl.") 1270 "Regular expression for a sequence of whitespace and comments in Perl.")
1248 1271
1272 (defconst cperl--basic-variable-rx
1273 `(sequence (in "$@%") ,cperl--basic-identifier-rx)
1274 "Regular expression for a Perl variable (scalar, array or hash).
1275This regexp intentionally does not support spaces (nor newlines
1276and comments) between the sigil and the identifier, for
1277educational reasons. So \"$foo\" will be matched, but \"$ foo\"
1278or \"${ foo }\" will not.")
1279
1280 (defconst cperl--variable-list-rx
1281 `(sequence "("
1282 (optional (sequence
1283 ,cperl--ws*-rx
1284 ,cperl--basic-variable-rx
1285 (0+ (sequence
1286 ,cperl--ws*-rx
1287 ","
1288 ,cperl--ws*-rx
1289 ,cperl--basic-variable-rx))
1290 ,cperl--ws*-rx)))
1291 "Regular expression for a list of Perl variables for declarations.")
1292
1249 ;; This is left as a string regexp. There are many version schemes in 1293 ;; This is left as a string regexp. There are many version schemes in
1250 ;; the wild, so people might want to fiddle with this variable. 1294 ;; the wild, so people might want to fiddle with this variable.
1251 (defconst cperl--version-regexp 1295 (defconst cperl--version-regexp
@@ -1260,6 +1304,54 @@ is a legal variable name).")
1260 (optional (sequence "_" (1+ word)))))) 1304 (optional (sequence "_" (1+ word))))))
1261 "A sequence for recommended version number schemes in Perl.") 1305 "A sequence for recommended version number schemes in Perl.")
1262 1306
1307 (defconst cperl--single-attribute-rx
1308 `(sequence ,cperl--basic-identifier-rx
1309 (optional (sequence "("
1310 (0+ (not (in ")")))
1311 ")")))
1312 "A regular expression for a single attribute, without leading colon.
1313It may have parameters in parens, but parens within the
1314parameter's value are not supported.. This regexp does not have
1315capture groups.")
1316
1317 (defconst cperl--attribute-list-rx
1318 `(sequence ":"
1319 (0+ (sequence
1320 ,cperl--ws*-rx
1321 ,cperl--single-attribute-rx
1322 ,cperl--ws*-rx
1323 (optional ":"))))
1324 "A regular expression for an attribute list.
1325Attribute lists may only occur in certain declarations. A colon
1326is required before the first attribute but optional between
1327subsequent attributes. This regexp does not have capture groups.")
1328
1329 (defconst cperl--prototype-rx
1330 `(sequence "("
1331 (0+ (any "$@%&*;\\[]"))
1332 ")")
1333 "A regular expression for a subroutine prototype. Not as strict as the actual prototype syntax, but good enough to distinguish prototypes from signatures.")
1334
1335 (defconst cperl--signature-rx
1336 `(sequence "("
1337 (optional
1338 (sequence
1339 (0+ (sequence ,cperl--ws*-rx
1340 ,cperl--basic-scalar-rx
1341 ,cperl--ws*-rx
1342 ","))
1343 ,cperl--ws*-rx
1344 (or ,cperl--basic-scalar-rx
1345 ,cperl--basic-array-rx
1346 ,cperl--basic-hash-rx)))
1347 (optional (sequence ,cperl--ws*-rx) "," )
1348 ,cperl--ws*-rx
1349 ")")
1350 "A regular expression for a subroutine signature.
1351These are a bit more restricted than \"my\" declaration lists
1352because they allow only one slurpy variable, and only in the last
1353place.")
1354
1263 (defconst cperl--package-rx 1355 (defconst cperl--package-rx
1264 `(sequence (group "package") 1356 `(sequence (group "package")
1265 ,cperl--ws+-rx 1357 ,cperl--ws+-rx
@@ -1327,6 +1419,15 @@ Covers packages, subroutines, and POD headings.")
1327) 1419)
1328 1420
1329 1421
1422(defun cperl-declaration-header-p (pos)
1423 "Return t if POS is in the header of a declaration.
1424Perl syntax can have various constructs between a
1425keyword (e.g. \"sub\") and its associated block of code, and
1426these can span several lines. These blocks are identified and
1427marked with a text-property in `cperl-find-pods-heres'. This
1428function tests that property."
1429 (equal (get-text-property pos 'syntax-type) 'sub-decl))
1430
1330(defun cperl-block-declaration-p () 1431(defun cperl-block-declaration-p ()
1331 "Test whether the following ?\\{ opens a declaration block. 1432 "Test whether the following ?\\{ opens a declaration block.
1332Returns the column where the declarating keyword is found, or nil 1433Returns the column where the declarating keyword is found, or nil
@@ -1345,6 +1446,9 @@ statement, so there's no semicolon."
1345 ((looking-at (rx (eval cperl--block-declaration-rx))) 1446 ((looking-at (rx (eval cperl--block-declaration-rx)))
1346 (setq is-block-declaration (current-column) 1447 (setq is-block-declaration (current-column)
1347 continue-searching nil)) 1448 continue-searching nil))
1449 ((cperl-declaration-header-p (point))
1450 (setq is-block-declaration (current-column)
1451 continue-searching nil))
1348 ;; Another brace means this is no block declaration 1452 ;; Another brace means this is no block declaration
1349 ((looking-at "{") 1453 ((looking-at "{")
1350 (setq continue-searching nil)) 1454 (setq continue-searching nil))
@@ -1710,6 +1814,8 @@ or as help on variables `cperl-tips', `cperl-problems',
1710 (concat "^[ \t]*\\(" 1814 (concat "^[ \t]*\\("
1711 cperl-sub-regexp 1815 cperl-sub-regexp
1712 (cperl-after-sub-regexp 'named 'attr-groups) 1816 (cperl-after-sub-regexp 'named 'attr-groups)
1817 (rx (eval cperl--ws*-rx))
1818 (rx (optional (eval cperl--signature-rx)))
1713 "\\|" ; per toke.c 1819 "\\|" ; per toke.c
1714 "\\(BEGIN\\|UNITCHECK\\|CHECK\\|INIT\\|END\\|AUTOLOAD\\|DESTROY\\)" 1820 "\\(BEGIN\\|UNITCHECK\\|CHECK\\|INIT\\|END\\|AUTOLOAD\\|DESTROY\\)"
1715 "\\)" 1821 "\\)"
@@ -2553,6 +2659,9 @@ PRESTART is the position basing on which START was found."
2553 (<= parse-start start-point)) 2659 (<= parse-start start-point))
2554 (goto-char parse-start) 2660 (goto-char parse-start)
2555 (beginning-of-defun) 2661 (beginning-of-defun)
2662 (when (cperl-declaration-header-p (point))
2663 (goto-char (cperl-beginning-of-property (point) 'syntax-type))
2664 (beginning-of-line))
2556 (setq start-state nil)) 2665 (setq start-state nil))
2557 (setq prestart (point)) 2666 (setq prestart (point))
2558 (if start-state nil 2667 (if start-state nil
@@ -2759,12 +2868,15 @@ Will not look before LIM."
2759 (if (not (or (eq (1- (point)) containing-sexp) 2868 (if (not (or (eq (1- (point)) containing-sexp)
2760 (and cperl-indent-parens-as-block 2869 (and cperl-indent-parens-as-block
2761 (not is-block)) 2870 (not is-block))
2762 (save-excursion (cperl-block-declaration-p)) 2871 (and (looking-at "{")
2872 (save-excursion (cperl-block-declaration-p)))
2763 (memq (preceding-char) 2873 (memq (preceding-char)
2764 (append (if is-block " ;{" " ,;{") '(nil))) 2874 (append (if is-block " ;{" " ,;{") '(nil)))
2765 (and (eq (preceding-char) ?\}) 2875 (and (eq (preceding-char) ?\})
2766 (cperl-after-block-and-statement-beg 2876 (cperl-after-block-and-statement-beg
2767 containing-sexp)) 2877 containing-sexp))
2878 (and (cperl-declaration-header-p indent-point)
2879 (not (cperl-declaration-header-p char-after-pos)))
2768 (get-text-property (point) 'first-format-line))) 2880 (get-text-property (point) 'first-format-line)))
2769 ;; This line is continuation of preceding line's statement; 2881 ;; This line is continuation of preceding line's statement;
2770 ;; indent `cperl-continued-statement-offset' more than the 2882 ;; indent `cperl-continued-statement-offset' more than the
@@ -2843,12 +2955,11 @@ Will not look before LIM."
2843 ;; anonymous sub in a hash. 2955 ;; anonymous sub in a hash.
2844 (if (and;; Is it a sub in group starting on this line? 2956 (if (and;; Is it a sub in group starting on this line?
2845 cperl-indent-subs-specially 2957 cperl-indent-subs-specially
2846 (cond ((get-text-property (point) 'attrib-group) 2958 (cond
2847 (goto-char (cperl-beginning-of-property 2959 ((cperl-declaration-header-p (point))
2848 (point) 'attrib-group))) 2960 (goto-char
2849 ((eq (preceding-char) ?b) 2961 (cperl-beginning-of-property (point)
2850 (forward-sexp -1) 2962 'syntax-type))))
2851 (looking-at (concat cperl-sub-regexp "\\>"))))
2852 (setq p (nth 1 ; start of innermost containing list 2963 (setq p (nth 1 ; start of innermost containing list
2853 (parse-partial-sexp 2964 (parse-partial-sexp
2854 (line-beginning-position) 2965 (line-beginning-position)
@@ -2992,6 +3103,9 @@ and closing parentheses and brackets."
2992 (goto-char (elt i 1)) ; statement-start 3103 (goto-char (elt i 1)) ; statement-start
2993 (+ (if (or (memq (elt i 2) (append "}])" nil)) ; char-after 3104 (+ (if (or (memq (elt i 2) (append "}])" nil)) ; char-after
2994 (eq 'continuation ; do not stagger continuations 3105 (eq 'continuation ; do not stagger continuations
3106 ;; FIXME: This clobbers the syntax state in parse-data
3107 ;; for the *following* lines and makes the state
3108 ;; useless for indent-region -- haj 2023-06-30
2995 (elt (cperl-sniff-for-indent parse-data) 0))) 3109 (elt (cperl-sniff-for-indent parse-data) 0)))
2996 0 ; Closing parenthesis or continuation of a continuation 3110 0 ; Closing parenthesis or continuation of a continuation
2997 cperl-continued-statement-offset) 3111 cperl-continued-statement-offset)
@@ -3467,22 +3581,37 @@ Should be called with the point before leading colon of an attribute."
3467 "L%d: attribute `%s': %s" 3581 "L%d: attribute `%s': %s"
3468 (count-lines (point-min) (point)) 3582 (count-lines (point-min) (point))
3469 (and start1 end1 (buffer-substring start1 end1)) b) 3583 (and start1 end1 (buffer-substring start1 end1)) b)
3470 (setq start nil))) 3584 ; (setq start nil) I'd like to keep trying -- haj 2023-06-26
3471 (and start 3585 ))
3472 (progn 3586 (cond
3473 (put-text-property start (point) 3587 ;; Allow for a complete signature and trailing spaces here
3474 'attrib-group (if (looking-at "{") t 0)) 3588 ((search-forward-regexp (rx (sequence point
3475 (and pos 3589 (eval cperl--ws*-rx)
3476 (< 1 (count-lines (+ 3 pos) (point))) ; end of `sub' 3590 (eval cperl--signature-rx)
3477 ;; Apparently, we do not need `multiline': faces added now 3591 (eval cperl--ws*-rx)))
3478 (put-text-property (+ 3 pos) (cperl-1+ (point)) 3592 nil
3479 'syntax-type 'sub-decl)) 3593 t)) ; NOERROR
3480 (and b-fname ; Fontify here: the following condition 3594 ((looking-at (rx "("))
3481 (cperl-postpone-fontification ; is too hard to determine by 3595 ;; We might be in the process of typing a prototype or
3482 b-fname e-fname 'face ; a REx, so do it here 3596 ;; signature. These start with a left paren, so we want this to
3483 (if (looking-at "{") 3597 ;; be included into the area marked as sub-decl.
3484 font-lock-function-name-face 3598 nil)
3485 font-lock-variable-name-face))))) 3599 ;; Else, we are in no mans land. Just keep trying.
3600 (t
3601 ))
3602 (when (looking-at (rx (in ";{")))
3603 ;; A semicolon ends the declaration, an opening brace begins the
3604 ;; BLOCK. Neither is part of the declaration.
3605 (backward-char))
3606 (when start
3607 (put-text-property start (point)
3608 'attrib-group (if (looking-at "{") t 0))
3609 (and pos
3610 (progn
3611 (< 1 (count-lines (+ 3 pos) (point))) ; end of `sub'
3612 ;; Apparently, we do not need `multiline': faces added now
3613 (put-text-property (+ 3 pos) (cperl-1+ (point))
3614 'syntax-type 'sub-decl))))
3486 ;; now restore the initial state 3615 ;; now restore the initial state
3487 (if st 3616 (if st
3488 (progn 3617 (progn
@@ -3773,8 +3902,10 @@ recursive calls in starting lines of here-documents."
3773 max)) 3902 max))
3774 (search 3903 (search
3775 (concat 3904 (concat
3776 "\\(\\`\n?\\|^\n\\)=" ; POD 3905 ;; -------- POD using capture group 1
3906 "\\(\\`\n?\\|^\n\\)="
3777 "\\|" 3907 "\\|"
3908 ;; -------- HERE-document capture groups 2-7
3778 ;; One extra () before this: 3909 ;; One extra () before this:
3779 "<<\\(~?\\)" ; HERE-DOC, indented-p = capture 2 3910 "<<\\(~?\\)" ; HERE-DOC, indented-p = capture 2
3780 "\\(" ; 2 + 1 3911 "\\(" ; 2 + 1
@@ -3790,38 +3921,49 @@ recursive calls in starting lines of here-documents."
3790 ;; "\\([^= \t0-9$@%&]\\|[ \t]+[^ \t\n0-9$@%&]\\)" ; 6 + 1 3921 ;; "\\([^= \t0-9$@%&]\\|[ \t]+[^ \t\n0-9$@%&]\\)" ; 6 + 1
3791 "\\)" 3922 "\\)"
3792 "\\|" 3923 "\\|"
3924 ;; -------- format capture groups 8-9
3793 ;; 1+6 extra () before this: 3925 ;; 1+6 extra () before this:
3794 "^[ \t]*\\(format\\)[ \t]*\\([a-zA-Z0-9_]+\\)?[ \t]*=[ \t]*$" ;FRMAT 3926 "^[ \t]*\\(format\\)[ \t]*\\([a-zA-Z0-9_]+\\)?[ \t]*=[ \t]*$"
3795 (if cperl-use-syntax-table-text-property 3927 (if cperl-use-syntax-table-text-property
3796 (concat 3928 (concat
3797 "\\|" 3929 "\\|"
3930 ;; -------- quoted constructs and regexps, group 10
3798 ;; 1+6+2=9 extra () before this: 3931 ;; 1+6+2=9 extra () before this:
3799 "\\<\\(q[wxqr]?\\|[msy]\\|tr\\)\\>" ; QUOTED CONSTRUCT 3932 "\\<\\(q[wxqr]?\\|[msy]\\|tr\\)\\>"
3800 "\\|" 3933 "\\|"
3934 ;; -------- "bare" regex or glob, group 11
3801 ;; 1+6+2+1=10 extra () before this: 3935 ;; 1+6+2+1=10 extra () before this:
3802 "\\([/<]\\)" ; /blah/ or <file*glob> 3936 "\\([/<]\\)" ; /blah/ or <file*glob>
3803 "\\|" 3937 "\\|"
3938 ;; -------- subroutine declarations, groups 12-17
3804 ;; 1+6+2+1+1=11 extra () before this 3939 ;; 1+6+2+1+1=11 extra () before this
3805 "\\<" cperl-sub-regexp "\\>" ; sub with proto/attr 3940 (rx (sequence
3806 "\\(" 3941 word-start
3807 cperl-white-and-comment-rex 3942 (group (regexp cperl-sub-regexp)) ; #12
3808 (rx (opt (group (eval cperl--normal-identifier-rx)))) 3943 (eval cperl--ws+-rx)
3809 "\\)" 3944 (opt (group (eval cperl--normal-identifier-rx))) ; #13
3810 "\\(" 3945 (eval cperl--ws*-rx)
3811 cperl-maybe-white-and-comment-rex 3946 (group (or (group (eval cperl--prototype-rx)) ; #14,#15
3812 "\\(([^()]*)\\|:[^:]\\)\\)" ; prototype or attribute start 3947 ;; (group (eval cperl--signature-rx)) ; #16
3948 (group unmatchable) ; #16
3949 (group (or anything buffer-end)))))) ; #17
3813 "\\|" 3950 "\\|"
3814 ;; 1+6+2+1+1+6=17 extra () before this: 3951 ;; -------- weird variables, capture group 18
3952 ;; FIXME: We don't need that group -- haj 2023-06-21
3953 ;; 1+6+2+1+1+6=17 extra () before this
3815 "\\$\\(['{]\\)" ; $' or ${foo} 3954 "\\$\\(['{]\\)" ; $' or ${foo}
3816 "\\|" 3955 "\\|"
3956 ;; -------- old-style ' as package separator, group 19
3817 ;; 1+6+2+1+1+6+1=18 extra () before this (old pack'var syntax; 3957 ;; 1+6+2+1+1+6+1=18 extra () before this (old pack'var syntax;
3818 ;; we do not support intervening comments...): 3958 ;; we do not support intervening comments...):
3819 "\\(\\<" cperl-sub-regexp "[ \t\n\f]+\\|[&*$@%]\\)[a-zA-Z0-9_]*'" 3959 "\\(\\<" cperl-sub-regexp "[ \t\n\f]+\\|[&*$@%]\\)[a-zA-Z0-9_]*'"
3820 ;; 1+6+2+1+1+6+1+1=19 extra () before this:
3821 "\\|" 3960 "\\|"
3961 ;; -------- __END__ and __DATA__ tokens, group 20
3962 ;; 1+6+2+1+1+6+1+1=19 extra () before this:
3822 "__\\(END\\|DATA\\)__" ; __END__ or __DATA__ 3963 "__\\(END\\|DATA\\)__" ; __END__ or __DATA__
3823 ;; 1+6+2+1+1+6+1+1+1=20 extra () before this: 3964 ;; 1+6+2+1+1+6+1+1+1=20 extra () before this:
3824 "\\|" 3965 "\\|"
3966 ;; -------- backslash-escaped stuff, don't interpret it
3825 "\\\\\\(['`\"($]\\)") ; BACKWACKED something-hairy 3967 "\\\\\\(['`\"($]\\)") ; BACKWACKED something-hairy
3826 ""))) 3968 "")))
3827 warning-message) 3969 warning-message)
@@ -4691,28 +4833,28 @@ recursive calls in starting lines of here-documents."
4691 'REx-part2 t))))) 4833 'REx-part2 t)))))
4692 (if (> (point) max) 4834 (if (> (point) max)
4693 (setq tmpend tb)))) 4835 (setq tmpend tb))))
4694 ((match-beginning 17) ; sub with prototype or attribute 4836 ((match-beginning 14) ; sub with prototype or attribute
4695 ;; 1+6+2+1+1=11 extra () before this (sub with proto/attr): 4837 ;; 1+6+2+1+1=11 extra () before this (sub with proto/attr):
4696 ;;"\\<sub\\>\\(" ;12 4838 ;; match-string 12: Keyword "sub"
4697 ;; cperl-white-and-comment-rex ;13 4839 ;; match-string 13: Name of the subroutine (optional)
4698 ;; "\\([a-zA-Z_:'0-9]+\\)\\)?" ; name ;14 4840 ;; match-string 14: Indicator for proto/attr/signature
4699 ;;"\\(" cperl-maybe-white-and-comment-rex ;15,16 4841 ;; match-string 15: Prototype
4700 ;; "\\(([^()]*)\\|:[^:]\\)\\)" ; 17:proto or attribute start 4842 ;; match-string 16: unused
4701 (setq b1 (match-beginning 14) e1 (match-end 14)) 4843 ;; match-string 17: Distinguish declaration/definition
4844 (setq b1 (match-beginning 13) e1 (match-end 13))
4702 (if (memq (char-after (1- b)) 4845 (if (memq (char-after (1- b))
4703 '(?\$ ?\@ ?\% ?\& ?\*)) 4846 '(?\$ ?\@ ?\% ?\& ?\*))
4704 nil 4847 nil ;; we found $sub or @sub etc
4705 (goto-char b) 4848 (goto-char b)
4706 (if (eq (char-after (match-beginning 17)) ?\( ) 4849 (if (match-beginning 15) ; a complete prototype
4707 (progn 4850 (progn
4708 (cperl-commentify ; Prototypes; mark as string 4851 (cperl-commentify ; Prototypes; mark as string
4709 (match-beginning 17) (match-end 17) t) 4852 (match-beginning 15) (match-end 15) t)
4710 (goto-char (match-end 0)) 4853 (goto-char (match-end 0))
4711 ;; Now look for attributes after prototype: 4854 ;; Now look for attributes after prototype:
4712 (forward-comment (buffer-size)) 4855 (forward-comment (buffer-size))
4713 (and (looking-at ":[^:]") 4856 (cperl-find-sub-attrs st-l b1 e1 b))
4714 (cperl-find-sub-attrs st-l b1 e1 b))) 4857 ;; treat attributes without prototype and incomplete stuff
4715 ;; treat attributes without prototype
4716 (goto-char (match-beginning 17)) 4858 (goto-char (match-beginning 17))
4717 (cperl-find-sub-attrs st-l b1 e1 b)))) 4859 (cperl-find-sub-attrs st-l b1 e1 b))))
4718 ;; 1+6+2+1+1+6+1=18 extra () before this: 4860 ;; 1+6+2+1+1+6+1=18 extra () before this:
@@ -5313,6 +5455,10 @@ conditional/loop constructs."
5313 (let ((comment-column new-comm-indent)) 5455 (let ((comment-column new-comm-indent))
5314 (indent-for-comment))) 5456 (indent-for-comment)))
5315 (progn 5457 (progn
5458 ;; FIXME: It would be nice to keep indent-info, but this
5459 ;; doesn not work if the region contains continuation
5460 ;; lines (see `cperl-calculate-indent') -- haj 2023-06-30
5461 (setq indent-info (list nil nil nil))
5316 (setq i (cperl-indent-line indent-info)) 5462 (setq i (cperl-indent-line indent-info))
5317 (or comm 5463 (or comm
5318 (not i) 5464 (not i)
@@ -5668,7 +5814,11 @@ default function."
5668 (setq 5814 (setq
5669 t-font-lock-keywords 5815 t-font-lock-keywords
5670 (list 5816 (list
5817 ;; -------- trailing spaces -> use invalid-face as a warning
5818 ;; (matcher subexp facespec)
5671 `("[ \t]+$" 0 ',cperl-invalid-face t) 5819 `("[ \t]+$" 0 ',cperl-invalid-face t)
5820 ;; -------- flow control
5821 ;; (matcher . subexp) font-lock-keyword-face by default
5672 (cons 5822 (cons
5673 (concat 5823 (concat
5674 "\\(^\\|[^$@%&\\]\\)\\<\\(" 5824 "\\(^\\|[^$@%&\\]\\)\\<\\("
@@ -5688,6 +5838,8 @@ default function."
5688 "\\)\\>") 2) ; was "\\)[ \n\t;():,|&]" 5838 "\\)\\>") 2) ; was "\\)[ \n\t;():,|&]"
5689 ; In what follows we use `type' style 5839 ; In what follows we use `type' style
5690 ; for overwritable builtins 5840 ; for overwritable builtins
5841 ;; -------- builtin functions
5842 ;; (matcher subexp facespec)
5691 (list 5843 (list
5692 (concat 5844 (concat
5693 "\\(^\\|[^$@%&\\]\\)\\<\\(" 5845 "\\(^\\|[^$@%&\\]\\)\\<\\("
@@ -5730,6 +5882,10 @@ default function."
5730 2 'font-lock-type-face) 5882 2 'font-lock-type-face)
5731 ;; In what follows we use `other' style 5883 ;; In what follows we use `other' style
5732 ;; for nonoverwritable builtins 5884 ;; for nonoverwritable builtins
5885 ;; This is a bit shaky because the status
5886 ;; "nonoverwritable" can change between Perl versions.
5887 ;; -------- "non overridable" functions
5888 ;; (matcher subexp facespec)
5733 (list 5889 (list
5734 (concat 5890 (concat
5735 "\\(^\\|[^$@%&\\]\\)\\<\\(" 5891 "\\(^\\|[^$@%&\\]\\)\\<\\("
@@ -5750,33 +5906,69 @@ default function."
5750 ;; '("#endif" "#else" "#ifdef" "#ifndef" "#if" 5906 ;; '("#endif" "#else" "#ifdef" "#ifndef" "#if"
5751 ;; "#include" "#define" "#undef") 5907 ;; "#include" "#define" "#undef")
5752 ;; "\\|") 5908 ;; "\\|")
5909 ;; -------- -X file tests
5910 ;; (matcher subexp facespec)
5753 '("-[rwxoRWXOezsfdlpSbctugkTBMAC]\\>\\([ \t]+_\\>\\)?" 0 5911 '("-[rwxoRWXOezsfdlpSbctugkTBMAC]\\>\\([ \t]+_\\>\\)?" 0
5754 font-lock-function-name-face keep) ; Not very good, triggers at "[a-z]" 5912 font-lock-function-name-face keep) ; Not very good, triggers at "[a-z]"
5755 ;; This highlights declarations and definitions differently. 5913 ;; This highlights declarations and definitions differently.
5756 ;; We do not try to highlight in the case of attributes: 5914 ;; We do not try to highlight in the case of attributes:
5757 ;; it is already done by `cperl-find-pods-heres' 5915 ;; it is already done by `cperl-find-pods-heres'
5916 ;; -------- function definition _and_ declaration
5917 ;; (matcher (subexp facespec))
5918 ;; facespec is evaluated depending on whether the
5919 ;; statement ends in a "{" (definition) or ";"
5920 ;; (declaration without body)
5758 (list (concat "\\<" cperl-sub-regexp 5921 (list (concat "\\<" cperl-sub-regexp
5759 cperl-white-and-comment-rex ; whitespace/comments 5922 (rx
5760 "\\([^ \n\t{;()]+\\)" ; 2=name (assume non-anonymous) 5923 (sequence (eval cperl--ws+-rx)
5761 "\\(" 5924 (group (optional (eval cperl--normal-identifier-rx)))))
5762 cperl-maybe-white-and-comment-rex ;whitespace/comments? 5925;; "\\([^ \n\t{;()]+\\)" ; 2=name (assume non-anonymous)
5763 "([^()]*)\\)?" ; prototype 5926 (rx
5764 cperl-maybe-white-and-comment-rex ; whitespace/comments? 5927 (optional
5928 (group (sequence (group (eval cperl--ws*-rx))
5929 (eval cperl--prototype-rx)))))
5930;; "\\("
5931;; cperl-maybe-white-and-comment-rex ;whitespace/comments?
5932 ;; "([^()]*)\\)?" ; prototype
5933 (rx (optional (sequence (eval cperl--ws*-rx)
5934 (eval cperl--attribute-list-rx))))
5935; cperl-maybe-white-and-comment-rex ; whitespace/comments?
5936 (rx (group-n 3
5937 (optional (sequence(eval cperl--ws*-rx)
5938 (eval cperl--signature-rx)))))
5939 (rx (eval cperl--ws*-rx))
5765 "[{;]") 5940 "[{;]")
5766 2 '(if (eq (char-after (cperl-1- (match-end 0))) ?\{ ) 5941 '(1 (if (eq (char-after (cperl-1- (match-end 0))) ?\{ )
5767 'font-lock-function-name-face 5942 'font-lock-function-name-face
5768 'font-lock-variable-name-face)) 5943 'font-lock-variable-name-face)
5944 t ;; override
5945 t) ;; laxmatch in case of anonymous subroutines
5946 ;; -------- anchored: Signature
5947 `(,(rx (or (eval cperl--basic-scalar-rx)
5948 (eval cperl--basic-array-rx)
5949 (eval cperl--basic-hash-rx)))
5950 (progn
5951 (goto-char (match-beginning 3)) ; pre-match: Back to sig
5952 (match-end 3))
5953
5954 nil
5955 (0 font-lock-variable-name-face)))
5956 ;; -------- various stuff calling for a package name
5957 ;; (matcher subexp facespec)
5769 `(,(rx (sequence symbol-start 5958 `(,(rx (sequence symbol-start
5770 (or "package" "require" "use" "import" 5959 (or "package" "require" "use" "import"
5771 "no" "bootstrap") 5960 "no" "bootstrap")
5772 (eval cperl--ws+-rx) 5961 (eval cperl--ws+-rx)
5773 (group-n 1 (eval cperl--normal-identifier-rx)) 5962 (group-n 1 (eval cperl--normal-identifier-rx))
5774 (any " \t;"))) ; require A if B; 5963 (any " \t\n;"))) ; require A if B;
5775 1 font-lock-function-name-face) 5964 1 font-lock-function-name-face)
5965 ;; -------- formats
5966 ;; (matcher subexp facespec)
5776 '("^[ \t]*format[ \t]+\\([a-zA-Z_][a-zA-Z_0-9:]*\\)[ \t]*=[ \t]*$" 5967 '("^[ \t]*format[ \t]+\\([a-zA-Z_][a-zA-Z_0-9:]*\\)[ \t]*=[ \t]*$"
5777 1 font-lock-function-name-face) 5968 1 font-lock-function-name-face)
5778 ;; bareword hash key: $foo{bar} 5969 ;; -------- bareword hash key: $foo{bar}, $foo[1]{bar}
5779 `(,(rx (or (in "]}\\%@>*&") ; What Perl is this? 5970 ;; (matcher (subexp facespec) ...
5971 `(,(rx (or (in "]}\\%@>*&")
5780 (sequence "$" (eval cperl--normal-identifier-rx))) 5972 (sequence "$" (eval cperl--normal-identifier-rx)))
5781 (0+ blank) "{" (0+ blank) 5973 (0+ blank) "{" (0+ blank)
5782 (group-n 1 (sequence (opt "-") 5974 (group-n 1 (sequence (opt "-")
@@ -5784,24 +5976,27 @@ default function."
5784 (0+ blank) "}") 5976 (0+ blank) "}")
5785;; '("\\([]}\\%@>*&]\\|\\$[a-zA-Z0-9_:]*\\)[ \t]*{[ \t]*\\(-?[a-zA-Z0-9_:]+\\)[ \t]*}" 5977;; '("\\([]}\\%@>*&]\\|\\$[a-zA-Z0-9_:]*\\)[ \t]*{[ \t]*\\(-?[a-zA-Z0-9_:]+\\)[ \t]*}"
5786 (1 font-lock-string-face t) 5978 (1 font-lock-string-face t)
5787 ;; anchored bareword hash key: $foo{bar}{baz} 5979 ;; -------- anchored bareword hash key: $foo{bar}{baz}
5980 ;; ... (anchored-matcher pre-form post-form subex-highlighters)
5788 (,(rx point 5981 (,(rx point
5789 (0+ blank) "{" (0+ blank) 5982 (0+ blank) "{" (0+ blank)
5790 (group-n 1 (sequence (opt "-") 5983 (group-n 1 (sequence (opt "-")
5791 (eval cperl--basic-identifier-rx))) 5984 (eval cperl--basic-identifier-rx)))
5792 (0+ blank) "}") 5985 (0+ blank) "}")
5793 ;; ("\\=[ \t]*{[ \t]*\\(-?[a-zA-Z0-9_:]+\\)[ \t]*}" 5986 ;; ("\\=[ \t]*{[ \t]*\\(-?[a-zA-Z0-9_:]+\\)[ \t]*}"
5794 nil nil 5987 nil nil
5795 (1 font-lock-string-face t))) 5988 (1 font-lock-string-face t)))
5796 ;; hash element assignments with bareword key => value 5989 ;; -------- hash element assignments with bareword key => value
5797 `(,(rx (in "[ \t{,()") 5990 ;; (matcher subexp facespec)
5798 (group-n 1 (sequence (opt "-") 5991 `(,(rx (in "[ \t{,()")
5799 (eval cperl--basic-identifier-rx))) 5992 (group-n 1 (sequence (opt "-")
5800 (0+ blank) "=>") 5993 (eval cperl--basic-identifier-rx)))
5801 1 font-lock-string-face t) 5994 (0+ blank) "=>")
5802;; '("[[ \t{,(]\\(-?[a-zA-Z0-9_:]+\\)[ \t]*=>" 1 5995 1 font-lock-string-face t)
5803;; font-lock-string-face t) 5996 ;; '("[[ \t{,(]\\(-?[a-zA-Z0-9_:]+\\)[ \t]*=>" 1
5804 ;; labels 5997 ;; font-lock-string-face t)
5998 ;; -------- labels
5999 ;; (matcher subexp facespec)
5805 `(,(rx 6000 `(,(rx
5806 (sequence 6001 (sequence
5807 (0+ space) 6002 (0+ space)
@@ -5812,7 +6007,8 @@ default function."
5812 (or "until" "while" "for" "foreach" "do") 6007 (or "until" "while" "for" "foreach" "do")
5813 word-end)))) 6008 word-end))))
5814 1 font-lock-constant-face) 6009 1 font-lock-constant-face)
5815 ;; labels as targets (no trailing colon!) 6010 ;; -------- labels as targets (no trailing colon!)
6011 ;; (matcher subexp facespec)
5816 `(,(rx 6012 `(,(rx
5817 (sequence 6013 (sequence
5818 symbol-start 6014 symbol-start
@@ -5824,10 +6020,12 @@ default function."
5824 ;;; '("[$*]{?\\(\\sw+\\)" 1 font-lock-variable-name-face) 6020 ;;; '("[$*]{?\\(\\sw+\\)" 1 font-lock-variable-name-face)
5825 ;;; '("\\([@%]\\|\\$#\\)\\(\\sw+\\)" 6021 ;;; '("\\([@%]\\|\\$#\\)\\(\\sw+\\)"
5826 ;;; (2 (cons font-lock-variable-name-face '(underline)))) 6022 ;;; (2 (cons font-lock-variable-name-face '(underline))))
5827 ;; 1=my_etc, 2=white? 3=(+white? 4=white? 5=var 6023 ;; 1=my_etc, 2=white? 3=(+white? 4=white? 5=var
6024 ;; -------- variable declarations
6025 ;; (matcher (subexp facespec) ...
5828 `(,(rx (sequence (or "state" "my" "local" "our")) 6026 `(,(rx (sequence (or "state" "my" "local" "our"))
5829 (eval cperl--ws*-rx) 6027 (eval cperl--ws*-rx)
5830 (opt (sequence "(" (eval cperl--ws*-rx))) 6028 (opt (group (sequence "(" (eval cperl--ws*-rx))))
5831 (group 6029 (group
5832 (in "$@%*") 6030 (in "$@%*")
5833 (or 6031 (or
@@ -5840,7 +6038,8 @@ default function."
5840 ;; "\\((" 6038 ;; "\\(("
5841 ;; cperl-maybe-white-and-comment-rex 6039 ;; cperl-maybe-white-and-comment-rex
5842 ;; "\\)?\\([$@%*]\\([a-zA-Z0-9_:]+\\|[^a-zA-Z0-9_]\\)\\)") 6040 ;; "\\)?\\([$@%*]\\([a-zA-Z0-9_:]+\\|[^a-zA-Z0-9_]\\)\\)")
5843 (1 font-lock-variable-name-face) 6041 (2 font-lock-variable-name-face)
6042 ;; ... (anchored-matcher pre-form post-form subex-highlighters)
5844 (,(rx (sequence point 6043 (,(rx (sequence point
5845 (eval cperl--ws*-rx) 6044 (eval cperl--ws*-rx)
5846 "," 6045 ","
@@ -5861,7 +6060,7 @@ default function."
5861 ;; Bug in font-lock: limit is used not only to limit 6060 ;; Bug in font-lock: limit is used not only to limit
5862 ;; searches, but to set the "extend window for 6061 ;; searches, but to set the "extend window for
5863 ;; facification" property. Thus we need to minimize. 6062 ;; facification" property. Thus we need to minimize.
5864 '(if (match-beginning 1) 6063 (if (match-beginning 1) ; list declaration
5865 (save-excursion 6064 (save-excursion
5866 (goto-char (match-beginning 1)) 6065 (goto-char (match-beginning 1))
5867 (condition-case nil 6066 (condition-case nil
@@ -5874,7 +6073,8 @@ default function."
5874 (forward-char -2)) ; disable continued expr 6073 (forward-char -2)) ; disable continued expr
5875 nil 6074 nil
5876 (1 font-lock-variable-name-face))) 6075 (1 font-lock-variable-name-face)))
5877 ;; foreach my $foo ( 6076 ;; ----- foreach my $foo (
6077 ;; (matcher subexp facespec)
5878 `(,(rx symbol-start "for" (opt "each") 6078 `(,(rx symbol-start "for" (opt "each")
5879 (opt (sequence (1+ blank) 6079 (opt (sequence (1+ blank)
5880 (or "state" "my" "local" "our"))) 6080 (or "state" "my" "local" "our")))
@@ -5885,12 +6085,18 @@ default function."
5885;; '("\\<for\\(each\\)?\\([ \t]+\\(state\\|my\\|local\\|our\\)\\)?[ \t]*\\(\\$[a-zA-Z_][a-zA-Z_0-9]*\\)[ \t]*(" 6085;; '("\\<for\\(each\\)?\\([ \t]+\\(state\\|my\\|local\\|our\\)\\)?[ \t]*\\(\\$[a-zA-Z_][a-zA-Z_0-9]*\\)[ \t]*("
5886 1 font-lock-variable-name-face) 6086 1 font-lock-variable-name-face)
5887 ;; Avoid $!, and s!!, qq!! etc. when not fontifying syntactically 6087 ;; Avoid $!, and s!!, qq!! etc. when not fontifying syntactically
6088 ;; -------- ! as a negation char like $false = !$true
6089 ;; (matcher subexp facespec)
5888 '("\\(?:^\\|[^smywqrx$]\\)\\(!\\)" 1 font-lock-negation-char-face) 6090 '("\\(?:^\\|[^smywqrx$]\\)\\(!\\)" 1 font-lock-negation-char-face)
6091 ;; -------- ^ as a negation char in character classes m/[^abc]/
6092 ;; (matcher subexp facespec)
5889 '("\\[\\(\\^\\)" 1 font-lock-negation-char-face prepend))) 6093 '("\\[\\(\\^\\)" 1 font-lock-negation-char-face prepend)))
5890 (setq 6094 (setq
5891 t-font-lock-keywords-1 6095 t-font-lock-keywords-1
5892 `( 6096 `(
5893 ;; arrays and hashes. Access to elements is fixed below 6097 ;; -------- arrays and hashes. Access to elements is fixed below
6098 ;; (matcher subexp facespec)
6099 ;; facespec is an expression to distinguish between arrays and hashes
5894 (,(rx (group-n 1 (group-n 2 (or (in "@%") "$#")) 6100 (,(rx (group-n 1 (group-n 2 (or (in "@%") "$#"))
5895 (eval cperl--normal-identifier-rx))) 6101 (eval cperl--normal-identifier-rx)))
5896 1 6102 1
@@ -5898,8 +6104,10 @@ default function."
5898 (if (eq (char-after (match-beginning 2)) ?%) 6104 (if (eq (char-after (match-beginning 2)) ?%)
5899 'cperl-hash-face 6105 'cperl-hash-face
5900 'cperl-array-face) 6106 'cperl-array-face)
5901 nil) ; arrays and hashes 6107 nil)
5902 ;; access to array/hash elements 6108 ;; -------- access to array/hash elements
6109 ;; (matcher subexp facespec)
6110 ;; facespec is an expression to distinguish between arrays and hashes
5903 (,(rx (group-n 1 (group-n 2 (in "$@%")) 6111 (,(rx (group-n 1 (group-n 2 (in "$@%"))
5904 (eval cperl--normal-identifier-rx)) 6112 (eval cperl--normal-identifier-rx))
5905 (0+ blank) 6113 (0+ blank)
@@ -5912,7 +6120,8 @@ default function."
5912 'cperl-array-face) ; arrays and hashes 6120 'cperl-array-face) ; arrays and hashes
5913 font-lock-variable-name-face) ; Just to put something 6121 font-lock-variable-name-face) ; Just to put something
5914 t) ; override previous 6122 t) ; override previous
5915 ;; @$ array dereferences, $#$ last array index 6123 ;; -------- @$ array dereferences, $#$ last array index
6124 ;; (matcher (subexp facespec) (subexp facespec))
5916 (,(rx (group-n 1 (or "@" "$#")) 6125 (,(rx (group-n 1 (or "@" "$#"))
5917 (group-n 2 (sequence "$" 6126 (group-n 2 (sequence "$"
5918 (or (eval cperl--normal-identifier-rx) 6127 (or (eval cperl--normal-identifier-rx)
@@ -5920,7 +6129,8 @@ default function."
5920 ;; ("\\(@\\|\\$#\\)\\(\\$+\\([a-zA-Z_:][a-zA-Z0-9_:]*\\|[^ \t\n]\\)\\)" 6129 ;; ("\\(@\\|\\$#\\)\\(\\$+\\([a-zA-Z_:][a-zA-Z0-9_:]*\\|[^ \t\n]\\)\\)"
5921 (1 'cperl-array-face) 6130 (1 'cperl-array-face)
5922 (2 font-lock-variable-name-face)) 6131 (2 font-lock-variable-name-face))
5923 ;; %$ hash dereferences 6132 ;; -------- %$ hash dereferences
6133 ;; (matcher (subexp facespec) (subexp facespec))
5924 (,(rx (group-n 1 "%") 6134 (,(rx (group-n 1 "%")
5925 (group-n 2 (sequence "$" 6135 (group-n 2 (sequence "$"
5926 (or (eval cperl--normal-identifier-rx) 6136 (or (eval cperl--normal-identifier-rx)
diff --git a/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64190.pl b/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64190.pl
new file mode 100644
index 00000000000..c7621e1c47b
--- /dev/null
+++ b/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64190.pl
@@ -0,0 +1,24 @@
1# Example 1
2
3my ($var1,
4 $var2,
5 $var3);
6
7# Example 2
8
9package Foo
10 0.1;
11
12# Example 3 (intentionally incomplete, body is inserted by test)
13
14sub do_stuff
15
16# Example 4
17
18sub do_more_stuff ($param1,
19$param2)
20{
21 ...;
22}
23
24sub oops { ...; }
diff --git a/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64364.pl b/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64364.pl
new file mode 100644
index 00000000000..af188cbedac
--- /dev/null
+++ b/test/lisp/progmodes/cperl-mode-resources/cperl-bug-64364.pl
@@ -0,0 +1,26 @@
1# This resource file can be run with cperl--run-testcases from
2# cperl-tests.el and works with both perl-mode and cperl-mode.
3
4# -------- Bug#64364: input -------
5package P {
6sub way { ...; }
7#
8sub bus
9:lvalue
10($sig,$na,@ture)
11{
12...;
13}
14}
15# -------- Bug#64364: expected output -------
16package P {
17 sub way { ...; }
18 #
19 sub bus
20 :lvalue
21 ($sig,$na,@ture)
22 {
23 ...;
24 }
25}
26# -------- Bug#64364: end -------
diff --git a/test/lisp/progmodes/cperl-mode-tests.el b/test/lisp/progmodes/cperl-mode-tests.el
index 9bd250a38b5..99d5a51b3ea 100644
--- a/test/lisp/progmodes/cperl-mode-tests.el
+++ b/test/lisp/progmodes/cperl-mode-tests.el
@@ -177,14 +177,18 @@ attributes, prototypes and signatures."
177 (should (equal (get-text-property (1+ (match-beginning 0)) 'face) 177 (should (equal (get-text-property (1+ (match-beginning 0)) 'face)
178 'font-lock-string-face))) 178 'font-lock-string-face)))
179 (goto-char start-of-sub) 179 (goto-char start-of-sub)
180 ;; Attributes with their optional parameters
180 (when (search-forward-regexp "\\(:[a-z]+\\)\\((.*?)\\)?" end-of-sub t) 181 (when (search-forward-regexp "\\(:[a-z]+\\)\\((.*?)\\)?" end-of-sub t)
181 (should (equal (get-text-property (match-beginning 1) 'face) 182 (should (equal (get-text-property (match-beginning 1) 'face)
182 'font-lock-constant-face)) 183 'font-lock-constant-face))
183 (when (match-beginning 2) 184 (when (match-beginning 2)
184 (should (equal (get-text-property (match-beginning 2) 'face) 185 (should (equal (get-text-property (match-beginning 2) 'face)
185 'font-lock-string-face)))) 186 'font-lock-string-face))))
186 (goto-char end-of-sub))) 187 (goto-char end-of-sub)
187 188 ;; Subroutine signatures
189 (when (search-forward "$bar" end-of-sub t)
190 (should (equal (get-text-property (match-beginning) 'face)
191 'font-lock-variable-name-face)))))
188 ;; Anonymous subroutines 192 ;; Anonymous subroutines
189 (while (search-forward-regexp "= sub" nil t) 193 (while (search-forward-regexp "= sub" nil t)
190 (let ((start-of-sub (match-beginning 0)) 194 (let ((start-of-sub (match-beginning 0))
@@ -201,7 +205,11 @@ attributes, prototypes and signatures."
201 (when (match-beginning 2) 205 (when (match-beginning 2)
202 (should (equal (get-text-property (match-beginning 2) 'face) 206 (should (equal (get-text-property (match-beginning 2) 'face)
203 'font-lock-string-face)))) 207 'font-lock-string-face))))
204 (goto-char end-of-sub)))))) 208 (goto-char end-of-sub)
209 ;; Subroutine signatures
210 (when (search-forward "$bar" end-of-sub t)
211 (should (equal (get-text-property (match-beginning) 'face)
212 'font-lock-variable-name-face))))))))
205 213
206(ert-deftest cperl-test-fontify-special-variables () 214(ert-deftest cperl-test-fontify-special-variables ()
207 "Test fontification of variables like $^T or ${^ENCODING}. 215 "Test fontification of variables like $^T or ${^ENCODING}.
@@ -428,6 +436,62 @@ Also includes valid cases with whitespace in strange places."
428 (cperl-test--validate-regexp (rx (eval cperl--basic-identifier-rx)) 436 (cperl-test--validate-regexp (rx (eval cperl--basic-identifier-rx))
429 valid invalid))) 437 valid invalid)))
430 438
439(ert-deftest cperl-test-attribute-rx ()
440 "Test attributes and attribute lists"
441 (skip-unless (eq cperl-test-mode #'cperl-mode))
442 (let ((valid
443 '("foo" "bar()" "baz(quux)"))
444 (invalid
445 '("+foo" ; not an identifier
446 "foo::bar" ; no package qualifiers allowed
447 "(no-identifier)" ; no attribute name
448 "baz (quux)"))) ; no space allowed before "("
449 (cperl-test--validate-regexp (rx (eval cperl--single-attribute-rx))
450 valid invalid)))
451
452(ert-deftest cperl-test-attribute-list-rx ()
453 "Test attributes and attribute lists"
454 (skip-unless (eq cperl-test-mode #'cperl-mode))
455 (let ((valid
456 '(":" ":foo" ": bar()" ":baz(quux):"
457 ":isa(Foo)does(Bar)" ":isa(Foo):does(Bar)" ":isa(Foo):does(Bar):"
458 ": isa(Foo::Bar) : does(Bar)"))
459 (invalid
460 '(":foo + bar" ; not an identifier
461 ": foo(bar : : baz" ; too many colons
462 ": baz (quux)"))) ; no space allowed before "("
463 (cperl-test--validate-regexp (rx (eval cperl--attribute-list-rx))
464 valid invalid)))
465
466(ert-deftest cperl-test-prototype-rx ()
467 "Test subroutine prototypes"
468 (skip-unless (eq cperl-test-mode #'cperl-mode))
469 (let ((valid
470 ;; Examples from perldoc perlsub
471 '("($$)" "($$$)" "($$;$)" "($$$;$)" "(@)" "($@)" "(\\@)" "(\\@$$@)"
472 "(\\[%@])" "(*;$)" "(**)" "(&@)" "(;$)" "()"))
473 (invalid
474 '("$" ; missing paren
475 "($self)" ; a variable, -> subroutine signature
476 "(!$)" ; not all punctuation is permitted
477 "{$$}"))) ; wrong type of paren
478 (cperl-test--validate-regexp (rx (eval cperl--prototype-rx))
479 valid invalid)))
480
481(ert-deftest cperl-test-signature-rx ()
482 "Test subroutine signatures."
483 (skip-unless (eq cperl-test-mode #'cperl-mode))
484 (let ((valid
485 '("()" "( )" "($self, %params)" "(@params)"))
486 (invalid
487 '("$self" ; missing paren
488 "($)" ; a subroutine signature
489 "($!)" ; globals not permitted in a signature
490 "(@par,%options)" ; two slurpy parameters
491 "{$self}"))) ; wrong type of paren
492 (cperl-test--validate-regexp (rx (eval cperl--signature-rx))
493 valid invalid)))
494
431;;; Test unicode identifier in various places 495;;; Test unicode identifier in various places
432 496
433(defun cperl--test-unicode-setup (code string) 497(defun cperl--test-unicode-setup (code string)
@@ -1145,6 +1209,79 @@ as a regex."
1145 (funcall cperl-test-mode) 1209 (funcall cperl-test-mode)
1146 (should-not (nth 3 (syntax-ppss 3))))) 1210 (should-not (nth 3 (syntax-ppss 3)))))
1147 1211
1212(ert-deftest cperl-test-bug-64190 ()
1213 "Verify correct fontification of multiline declarations"
1214 (skip-unless (eq cperl-test-mode #'cperl-mode))
1215 (let ((file (ert-resource-file "cperl-bug-64190.pl")))
1216 (with-temp-buffer
1217 (insert-file-contents file)
1218 (goto-char (point-min))
1219 (cperl-mode)
1220 (font-lock-ensure)
1221 ;; Example 1
1222 (while (search-forward "var" nil t)
1223 (should (equal (get-text-property (point) 'face)
1224 'font-lock-variable-name-face)))
1225 ;; Example 2
1226 (search-forward "package F")
1227 (should (equal (get-text-property (point) 'face)
1228 'font-lock-function-name-face))
1229
1230 ;; Example 3 and 4 can't be directly tested because jit-lock and
1231 ;; batch tests don't play together well. But we can approximate
1232 ;; the behavior by calling the the fontification for the same
1233 ;; region which would be used by jit-lock.
1234 ;; Example 3
1235 (search-forward "sub do_stuff")
1236 (let ((start-change (point)))
1237 (insert "\n{")
1238 (cperl-font-lock-fontify-region-function start-change
1239 (point-max)
1240 nil) ; silent
1241 (font-lock-ensure start-change (point-max))
1242 (goto-char (1- start-change)) ; between the "ff" in "stuff"
1243 (should (equal (get-text-property (point) 'face)
1244 'font-lock-function-name-face))
1245 (search-forward "{")
1246 (insert "}")) ; make it legal again
1247
1248 ;; Example 4
1249 (search-forward "$param2")
1250 (beginning-of-line)
1251 (let ((start-change (point)))
1252 (insert " ")
1253 (cperl-font-lock-fontify-region-function start-change
1254 (point-max)
1255 nil) ; silent
1256 (font-lock-ensure start-change (point-max))
1257 (goto-char (1+ start-change))
1258 (should (equal (get-text-property (point) 'face)
1259 'font-lock-variable-name-face))
1260 (re-search-forward (rx (group "sub") " " (group "oops")))
1261 (should (equal (get-text-property (match-beginning 1) 'face)
1262 'font-lock-keyword-face))
1263 (should (equal (get-text-property (match-beginning 2) 'face)
1264 'font-lock-function-name-face))))))
1265
1266(ert-deftest cperl-test-bug-64364 ()
1267 "Check that multi-line subroutine declarations indent correctly."
1268 (cperl-set-style "PBP") ; make cperl-mode use the same settings as perl-mode
1269 (cperl--run-test-cases
1270 (ert-resource-file "cperl-bug-64364.pl")
1271 (indent-region (point-min) (point-max)))
1272 (cperl--run-test-cases
1273 (ert-resource-file "cperl-bug-64364.pl")
1274 (let ((tab-function
1275 (if (equal cperl-test-mode 'perl-mode)
1276 #'indent-for-tab-command
1277 #'cperl-indent-command)))
1278 (goto-char (point-min))
1279 (while (null (eobp))
1280 (funcall tab-function)
1281 (forward-line 1))))
1282 (cperl-set-style-back))
1283
1284
1148(ert-deftest test-indentation () 1285(ert-deftest test-indentation ()
1149 (ert-test-erts-file (ert-resource-file "cperl-indents.erts"))) 1286 (ert-test-erts-file (ert-resource-file "cperl-indents.erts")))
1150 1287