From 07e6bbb9bc68f76773e9bdf8846d64d83f50b0ea Mon Sep 17 00:00:00 2001 From: Alan Mackenzie Date: Mon, 26 Sep 2022 19:16:33 +0000 Subject: [PATCH] CC Mode: Handle C++20 concepts * lisp/progmodes/cc-align.el (c-lineup-topmost-intro-cont): Amend so as not to indent lines following a requires line. * lisp/progmodes/cc-engine.el (c-forward-primary-expression) (c-forward-c++-requires-clause): New functions. (c-forward-declarator): Skip forward over any trailing requires clause. (c-forward-decl-or-cast-1): Skip requires clauses before and after the type. Amend the second element of the return list to include information on two consecutive identifiers in <...>. (c-looking-at-or-maybe-in-bracelist): Don't recognize braces in requires expressions as brace lists. (c-guess-basic-syntax): CASE 5D.7: New case to handle the continuation of a "concept foo = " line. * lisp/progmodes/cc-fonts.el (c-basic-matchers-before): Add a new clause to handle the declaration of a concept. (c-get-fontification-context): Treat the arglist of a requires construct as a declaration arglist. * lisp/progmodes/cc-langs.el (c-equals-nontype-decl-kwds/key) (c-fun-name-substitute-kwds/key, c-pre-concept-<>-kwds/key): New c-lang-consts/vars. (c-constant-key): New c-lang-var. (c-type-decl-suffix-key): Include "requires" in the keywords matched. * lisp/progmodes/cc-mode.el (c-fl-decl-start): Fix an off by one error. Use equal rather than eq to compare two syntax contexts. --- lisp/progmodes/cc-align.el | 13 ++-- lisp/progmodes/cc-engine.el | 149 +++++++++++++++++++++++++++++++++--- lisp/progmodes/cc-fonts.el | 39 +++++++--- lisp/progmodes/cc-langs.el | 49 +++++++++++- lisp/progmodes/cc-mode.el | 5 +- 5 files changed, 226 insertions(+), 29 deletions(-) diff --git a/lisp/progmodes/cc-align.el b/lisp/progmodes/cc-align.el index e14f5b9058f..7b45be3c5c1 100644 --- a/lisp/progmodes/cc-align.el +++ b/lisp/progmodes/cc-align.el @@ -85,11 +85,14 @@ statement-cont.) Works with: topmost-intro-cont." (save-excursion (beginning-of-line) - (c-backward-syntactic-ws (c-langelem-pos langelem)) - (if (and (memq (char-before) '(?} ?,)) - (not (and c-overloadable-operators-regexp - (c-after-special-operator-id)))) - c-basic-offset))) + (unless (re-search-forward c-fun-name-substitute-key + (c-point 'eol) t) + (beginning-of-line) + (c-backward-syntactic-ws (c-langelem-pos langelem)) + (if (and (memq (char-before) '(?} ?,)) + (not (and c-overloadable-operators-regexp + (c-after-special-operator-id)))) + c-basic-offset)))) (defun c-lineup-gnu-DEFUN-intro-cont (langelem) "Line up the continuation lines of a DEFUN macro in the Emacs C source. diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index 1127ffe2498..e0aef2b2ee6 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el @@ -9512,6 +9512,84 @@ point unchanged and return nil." ;; Handling of large scale constructs like statements and declarations. +(defun c-forward-primary-expression (&optional limit) + ;; Go over the primary expression (if any) at point, moving to the next + ;; token and return non-nil. If we're not at a primary expression leave + ;; point unchanged and return nil. + ;; + ;; Note that this function is incomplete, handling only those cases expected + ;; to be common in a C++20 requires clause. + (let ((here (point)) + (c-restricted-<>-arglists t) + (c-parse-and-markup-<>-arglists nil) + ) + (if (cond + ((looking-at c-constant-key) + (goto-char (match-end 1)) + (c-forward-syntactic-ws limit) + t) + ((eq (char-after) ?\() + (and (c-go-list-forward (point) limit) + (eq (char-before) ?\)) + (progn (c-forward-syntactic-ws limit) + t))) + ((c-forward-over-compound-identifier) + (c-forward-syntactic-ws limit) + (while (cond + ((looking-at "<") + (prog1 + (c-forward-<>-arglist nil) + (c-forward-syntactic-ws limit))) + ((looking-at c-opt-identifier-concat-key) + (and + (zerop (c-forward-token-2 1 nil limit)) + (prog1 + (c-forward-over-compound-identifier) + (c-forward-syntactic-ws limit)))))) + t) + ((looking-at c-fun-name-substitute-key) ; "requires" + (goto-char (match-end 1)) + (c-forward-syntactic-ws limit) + (and + (or (not (eq (char-after) ?\()) + (prog1 + (and (c-go-list-forward (point) limit) + (eq (char-before) ?\))) + (c-forward-syntactic-ws))) + (eq (char-after) ?{) + (and (c-go-list-forward (point) limit) + (eq (char-before) ?})) + (progn + (c-forward-syntactic-ws limit) + t)))) + t + (goto-char here) + nil))) + +(defun c-forward-c++-requires-clause (&optional limit) + ;; Point is at the keyword "requires". Move forward over the requires + ;; clause to the next token after it and return non-nil. If there is no + ;; valid requires clause at point, leave point unmoved and return nil. + (let ((here (point)) + final-point) + (or limit (setq limit (point-max))) + (if (and + (zerop (c-forward-token-2 1 nil limit)) ; over "requires". + (prog1 + (c-forward-primary-expression limit) + (setq final-point (point)) + (while + (and (looking-at "\\(?:&&\\|||\\)") + (progn (goto-char (match-end 0)) + (c-forward-syntactic-ws limit) + (and (< (point) limit) + (c-forward-primary-expression limit)))) + (setq final-point (point))))) + (progn (goto-char final-point) + t) + (goto-char here) + nil))) + (defun c-forward-declarator (&optional limit accept-anon) ;; Assuming point is at the start of a declarator, move forward over it, ;; leaving point at the next token after it (e.g. a ) or a ; or a ,), or at @@ -9565,7 +9643,7 @@ point unchanged and return nil." ((and (looking-at c-type-decl-prefix-key) (if (and (c-major-mode-is 'c++-mode) (match-beginning 4)) ; Was 3 - 2021-01-01 - ;; If the third submatch matches in C++ then + ;; If the fourth submatch matches in C++ then ;; we're looking at an identifier that's a ;; prefix only if it specifies a member pointer. (progn @@ -9621,6 +9699,11 @@ point unchanged and return nil." (while (cond ((looking-at c-decl-hangon-key) (c-forward-keyword-clause 1)) + ((looking-at c-type-decl-suffix-key) + (if (save-match-data + (looking-at c-fun-name-substitute-key)) + (c-forward-c++-requires-clause) + (c-forward-keyword-clause 1))) ((and c-opt-cpp-prefix (looking-at c-noise-macro-with-parens-name-re)) (c-forward-noise-clause)))) @@ -9890,13 +9973,13 @@ This function might do hidden buffer changes." ;; ;; ;; - ;; The second element of the return value is non-nil when a - ;; `c-typedef-decl-kwds' specifier is found in the declaration. - ;; Specifically it is a dotted pair (A . B) where B is t when a - ;; `c-typedef-kwds' ("typedef") is present, and A is t when some - ;; other `c-typedef-decl-kwds' (e.g. class, struct, enum) - ;; specifier is present. I.e., (some of) the declared - ;; identifier(s) are types. + ;; The second element of the return value is non-nil when something + ;; indicating the identifier is a type occurs in the declaration. + ;; Specifically it is nil, or a three element list (A B C) where C is t + ;; when context is '<> and the "identifier" is a found type, B is t when a + ;; `c-typedef-kwds' ("typedef") is present, and A is t when some other + ;; `c-typedef-declkwds' (e.g. class, struct, enum) specifier is present. + ;; I.e., (some of) the declared identifier(s) are types. ;; ;; The third element of the return value is non-nil when the declaration ;; parsed might be an expression. The fourth element is the position of @@ -9972,6 +10055,9 @@ This function might do hidden buffer changes." at-type-decl ;; Set if we've a "typedef" keyword. at-typedef + ;; Set if `context' is '<> and the identifier is definitely a type, or + ;; has already been recorded as a found type. + at-<>-type ;; Set if we've found a specifier that can start a declaration ;; where there's no type. maybe-typeless @@ -10050,6 +10136,11 @@ This function might do hidden buffer changes." (setq kwd-sym (c-keyword-sym (match-string 1))) (save-excursion (c-forward-keyword-clause 1) + (when (and (c-major-mode-is 'c++-mode) + (c-keyword-member kwd-sym 'c-<>-sexp-kwds) + (save-match-data + (looking-at c-fun-name-substitute-key))) + (c-forward-c++-requires-clause)) (setq kwd-clause-end (point)))) ((and c-opt-cpp-prefix (looking-at c-noise-macro-with-parens-name-re)) @@ -10089,6 +10180,11 @@ This function might do hidden buffer changes." (point)))) found-type-list)) + ;; Might we have a C++20 concept? i.e. template? + (setq at-<>-type + (and (eq context '<>) + (memq found-type '(t known prefix found)))) + ;; Signal a type declaration for "struct foo {". (when (and backup-at-type-decl (eq (char-after) ?{)) @@ -10377,8 +10473,11 @@ This function might do hidden buffer changes." t) (when (if (save-match-data (looking-at "\\s(")) (c-safe (c-forward-sexp 1) t) - (goto-char (match-end 1)) - t) + (if (save-match-data + (looking-at c-fun-name-substitute-key)) ; requires + (c-forward-c++-requires-clause) + (goto-char (match-end 1)) + t)) (when (and (not got-suffix-after-parens) (= paren-depth 0)) (setq got-suffix-after-parens (match-beginning 0))) @@ -10971,8 +11070,8 @@ This function might do hidden buffer changes." (c-forward-type)))) (list id-start - (and (or at-type-decl at-typedef) - (cons at-type-decl at-typedef)) + (and (or at-type-decl at-typedef at-<>-type) + (list at-type-decl at-typedef at-<>-type)) maybe-expression type-start (or (eq context 'top) make-top))) @@ -12429,6 +12528,8 @@ comment at the start of cc-engine.el for more info." in-paren 'in-paren)) ((looking-at c-pre-brace-non-bracelist-key) (setq braceassignp nil)) + ((looking-at c-fun-name-substitute-key) + (setq braceassignp nil)) ((looking-at c-return-key)) ((and (looking-at c-symbol-start) (not (looking-at c-keywords-regexp))) @@ -12439,6 +12540,11 @@ comment at the start of cc-engine.el for more info." (setq after-type-id-pos (point)))) ((eq (char-after) ?\() (setq parens-before-brace t) + ;; Have we a requires with a parenthesis list? + (when (save-excursion + (and (zerop (c-backward-token-2 1 nil lim)) + (looking-at c-fun-name-substitute-key))) + (setq braceassignp nil)) nil) (t nil)) (save-excursion @@ -14201,6 +14307,25 @@ comment at the start of cc-engine.el for more info." (goto-char placeholder) (c-add-syntax 'inher-cont (c-point 'boi))) + ;; CASE 5D.7: Continuation of a "concept foo =" line in C++20 (or + ;; similar). + ((and c-equals-nontype-decl-key + (save-excursion + (prog1 + (and (zerop (c-backward-token-2 1 nil lim)) + (looking-at c-operator-re) + (equal (match-string 0) "=") + (zerop (c-backward-token-2 1 nil lim)) + (looking-at c-symbol-start) + (not (looking-at c-keywords-regexp)) + (zerop (c-backward-token-2 1 nil lim)) + (looking-at c-equals-nontype-decl-key) + (eq (c-beginning-of-statement-1 lim) 'same)) + (setq placeholder (point))))) + (goto-char placeholder) + (c-add-stmt-syntax 'topmost-intro-cont nil nil containing-sexp + paren-state)) + ;; CASE 5D.5: Continuation of the "expression part" of a ;; top level construct. Or, perhaps, an unrecognized construct. (t diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index 5d80eb58e38..753ae480878 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el @@ -887,6 +887,23 @@ casts and declarations are fontified. Used on level 2 and higher." ,@(when (c-major-mode-is 'c++-mode) '(c-font-lock-c++-modules)) + ;; The next regexp is highlighted with narrowing. This is so that the + ;; final "context" bit of the regexp, "\\(?:[^=]\\|$\\)", which cannot + ;; match anything non-empty at LIMIT, will match "$" instead. + ,@(when (c-lang-const c-equals-nontype-decl-kwds) + `((,(byte-compile + `(lambda (limit) + (save-restriction + (narrow-to-region (point-min) limit) + ,(c-make-font-lock-search-form + (concat (c-lang-const c-equals-nontype-decl-key) ;no \\( + (c-lang-const c-simple-ws) "+\\(" + (c-lang-const c-symbol-key) "\\)" + (c-lang-const c-simple-ws) "*" + "=\\(?:[^=]\\|$\\)") + `((,(+ 1 (c-lang-const c-simple-ws-depth)) + font-lock-type-face t))))))))) + ;; Fontify the special declarations in Objective-C. ,@(when (c-major-mode-is 'objc-mode) `(;; Fontify class names in the beginning of message expressions. @@ -1278,15 +1295,19 @@ casts and declarations are fontified. Used on level 2 and higher." (or (memq type '(c-decl-arg-start c-decl-type-start)) (and (progn (c-backward-syntactic-ws) t) - (c-back-over-compound-identifier) - (progn - (c-backward-syntactic-ws) - (or (bobp) - (progn - (setq type (c-get-char-property (1- (point)) - 'c-type)) - (memq type '(c-decl-arg-start - c-decl-type-start)))))))))) + (or + (and + (c-back-over-compound-identifier) + (progn + (c-backward-syntactic-ws) + (or (bobp) + (progn + (setq type (c-get-char-property (1- (point)) + 'c-type)) + (memq type '(c-decl-arg-start + c-decl-type-start)))))) + (and (zerop (c-backward-token-2)) + (looking-at c-fun-name-substitute-key)))))))) (cons 'decl nil)) (t (cons 'arglist t))))) diff --git a/lisp/progmodes/cc-langs.el b/lisp/progmodes/cc-langs.el index d33ed4bcda5..85f43d6a26b 100644 --- a/lisp/progmodes/cc-langs.el +++ b/lisp/progmodes/cc-langs.el @@ -2593,6 +2593,35 @@ will be handled." t (c-make-keywords-re t (c-lang-const c-equals-type-clause-kwds))) (c-lang-defvar c-equals-type-clause-key (c-lang-const c-equals-type-clause-key)) +(c-lang-defconst c-equals-nontype-decl-kwds + "Keywords which are followed by an identifier then an \"=\" +sign, which declares the identifier to be something other than a +type." + t nil + c++ '("concept")) + +(c-lang-defconst c-equals-nontype-decl-key + ;; An unadorned regular expression which matches any member of + ;; `c-equals-decl-kwds', or nil if such don't exist in the current language. + t (when (c-lang-const c-equals-nontype-decl-kwds) + (c-make-keywords-re nil (c-lang-const c-equals-nontype-decl-kwds)))) +(c-lang-defvar c-equals-nontype-decl-key + (c-lang-const c-equals-nontype-decl-key)) + +(c-lang-defconst c-fun-name-substitute-kwds + "Keywords which take the place of type+declarator at the beginning +of a function-like structure, such as a C++20 \"requires\" +clause. An arglist may or may not follow such a keyword." + t nil + c++ '("requires")) + +(c-lang-defconst c-fun-name-substitute-key + ;; An adorned regular expression which matches any member of + ;; `c-fun-name-substitute-kwds'. + t (c-make-keywords-re t (c-lang-const c-fun-name-substitute-kwds))) +(c-lang-defvar c-fun-name-substitute-key + (c-lang-const c-fun-name-substitute-key)) + (c-lang-defconst c-modifier-kwds "Keywords that can prefix normal declarations of identifiers \(and typically act as flags). Things like argument declarations @@ -2938,6 +2967,17 @@ if this isn't nil." ;; In CORBA PSDL: "ref")) +(c-lang-defconst c-pre-concept-<>-kwds + "Keywords that may be followed by an angle bracket expression containing +uses of \"concepts\". This is currently (2022-09) used only by C++." + t nil + c++ '("template")) + +(c-lang-defconst c-pre-concept-<>-key + ;; Regexp matching any element of `c-pre-concept-<>-kwds'. + t (c-make-keywords-re t (c-lang-const c-pre-concept-<>-kwds))) +(c-lang-defvar c-pre-concept-<>-key (c-lang-const c-pre-concept-<>-key)) + (c-lang-defconst c-<>-arglist-kwds "Keywords that can be followed by a C++ style template arglist; see `c-recognize-<>-arglists' for details. That language constant is @@ -3146,6 +3186,10 @@ not really template operators." java '("true" "false" "null") ; technically "literals", not keywords pike '("UNDEFINED")) ;; Not a keyword, but practically works as one. +(c-lang-defconst c-constant-key + t (c-make-keywords-re t (c-lang-const c-constant-kwds))) +(c-lang-defvar c-constant-key (c-lang-const c-constant-key)) + (c-lang-defconst c-primary-expr-kwds "Keywords besides constants and operators that start primary expressions." t nil @@ -3781,7 +3825,10 @@ is in effect when this is matched (see `c-identifier-syntax-table')." ;; "throw" in `c-type-modifier-kwds' is followed ;; by a parenthesis list, but no extra measures ;; are necessary to handle that. - (regexp-opt (c-lang-const c-type-modifier-kwds) t) + (regexp-opt + (append (c-lang-const c-fun-name-substitute-kwds) + (c-lang-const c-type-modifier-kwds)) + t) "\\>") "") "\\)") diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index 15dfdc4b0e5..9dd5ddb4656 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el @@ -2403,7 +2403,7 @@ with // and /*, not more generic line and block comments." (setq pseudo (c-cheap-inside-bracelist-p (c-parse-state))))))) (goto-char pseudo)) t) - (> (point) bod-lim) + (>= (point) bod-lim) (progn (c-forward-syntactic-ws) ;; Have we got stuck in a comment at EOB? (not (and (eobp) @@ -2427,7 +2427,8 @@ with // and /*, not more generic line and block comments." (and (> (point) bod-lim) (or (memq (char-before) '(?\( ?\[)) (and (eq (char-before) ?\<) - (eq (c-get-char-property + (equal + (c-get-char-property (1- (point)) 'syntax-table) c-<-as-paren-syntax)) (and (eq (char-before) ?{) -- 2.39.2