cc-mode has trouble with parsing dialects of C that use the preprocessor heavily. Consider this example from the Linux kernel: static int perf_event_period(struct perf_event *event, u64 __user *arg) __user is defined to some GCC static analysis nonsense, but since cc-mode doesn't know that, we see __user fontified in font-lock-variable-name-face and *arg untouched. This example is fairly benign (if ugly), but there are other cases where variations in pre-processor C dialect confuse cc-mode in larger regions, leading to odd fontification and indentation. The patch below adds customizable options for additional C-family language "keywords". To add this feature, we have to change how cc-mode evaluates its language variables. Today, we use clever macros to hard-code the values of all cc-mode language variables into the mode functions of each cc-mode major mode function or into c-init-language-vars-for, but in order to allow users to customize cc-mode syntax, we have to be able to recompute language constants and variables at runtime. The new code simply evaluates cc-mode language setter forms at mode initialization instead. This approach is slower, but not by much: it takes 0.9ms to set up cc-mode's ~130 language variables using the precompiled function approach, while it takes 1.6ms to do the same work using dynamic evaluation. I can live with this performance regression. As implemented, the keyword list can only be customized globally, but it'd be nice to be able to do something buffer-local too. === modified file 'lisp/progmodes/cc-defs.el' --- lisp/progmodes/cc-defs.el 2014-02-09 12:34:25 +0000 +++ lisp/progmodes/cc-defs.el 2014-05-02 04:47:35 +0000 @@ -89,7 +89,7 @@ ;;; Variables also used at compile time. -(defconst c-version "5.32.5" +(defconst c-version "5.32.5.1" "CC Mode version number.") (defconst c-version-sym (intern c-version)) @@ -1812,8 +1812,6 @@ ;; and other miscellaneous data. The obarray might also contain ;; various other symbols, but those don't have any variable bindings. -(defvar c-lang-const-expansion nil) - (defsubst c-get-current-file () ;; Return the base name of the current file. (let ((file (cond @@ -1880,19 +1878,6 @@ constant. A file is identified by its base name." (let* ((sym (intern (symbol-name name) c-lang-constants)) - ;; Make `c-lang-const' expand to a straightforward call to - ;; `c-get-lang-constant' in `cl-macroexpand-all' below. - ;; - ;; (The default behavior, i.e. to expand to a call inside - ;; `eval-when-compile' should be equivalent, since that macro - ;; should only expand to its content if it's used inside a - ;; form that's already evaluated at compile time. It's - ;; however necessary to use our cover macro - ;; `cc-eval-when-compile' due to bugs in `eval-when-compile', - ;; and it expands to a bulkier form that in this case only is - ;; unnecessary garbage that we don't want to store in the - ;; language constant source definitions.) - (c-lang-const-expansion 'call) (c-langs-are-parametric t) bindings pre-files) @@ -2037,53 +2022,28 @@ "Unknown language %S since it got no `c-mode-prefix' property" (symbol-name lang)))) - (if (eq c-lang-const-expansion 'immediate) - ;; No need to find out the source file(s) when we evaluate - ;; immediately since all the info is already there in the - ;; `source' property. - `',(c-get-lang-constant name nil mode) - - (let ((file (c-get-current-file))) - (if file (setq file (intern file))) - ;; Get the source file(s) that must be loaded to get the value - ;; of the constant. If the symbol isn't defined yet we assume - ;; that its definition will come later in this file, and thus - ;; are no file dependencies needed. - (setq source-files (nreverse - ;; Reverse to get the right load order. - (apply 'nconc - (mapcar (lambda (elem) - (if (eq file (car elem)) - nil ; Exclude our own file. - (list (car elem)))) - (get sym 'source)))))) - - ;; Make some effort to do a compact call to - ;; `c-get-lang-constant' since it will be compiled in. - (setq args (and mode `(',mode))) - (if (or source-files args) - (setq args (cons (and source-files `',source-files) - args))) - - (if (or (eq c-lang-const-expansion 'call) - (and (not c-lang-const-expansion) - (not mode)) - load-in-progress - (not (boundp 'byte-compile-dest-file)) - (not (stringp byte-compile-dest-file))) - ;; Either a straight call is requested in the context, or - ;; we're in an "uncontrolled" context and got no language, - ;; or we're not being byte compiled so the compile time - ;; stuff below is unnecessary. - `(c-get-lang-constant ',name ,@args) - - ;; Being compiled. If the loading and compiling version is - ;; the same we use a value that is evaluated at compile time, - ;; otherwise it's evaluated at runtime. - `(if (eq c-version-sym ',c-version-sym) - (cc-eval-when-compile - (c-get-lang-constant ',name ,@args)) - (c-get-lang-constant ',name ,@args)))))) + (let ((file (c-get-current-file))) + (if file (setq file (intern file))) + ;; Get the source file(s) that must be loaded to get the value + ;; of the constant. If the symbol isn't defined yet we assume + ;; that its definition will come later in this file, and thus + ;; are no file dependencies needed. + (setq source-files (nreverse + ;; Reverse to get the right load order. + (apply 'nconc + (mapcar (lambda (elem) + (if (eq file (car elem)) + nil ; Exclude our own file. + (list (car elem)))) + (get sym 'source)))))) + + ;; Make some effort to do a compact call to `c-get-lang-constant' + ;; and omit unneeded arguments since this code will be compiled. + (setq args (and mode `(',mode))) + (if (or source-files args) + (setq args (cons (and source-files `',source-files) + args))) + `(c-get-lang-constant ',name ,@args))) (defvar c-lang-constants-under-evaluation nil) @@ -2262,6 +2222,18 @@ (setq buf-mode (get buf-mode 'c-fallback-mode)))) match)) +(defun c-clear-value-cache () + "Forget already-computed `c-lang-defvar' values. +Call this function to make changes to cc-mode language +variables take effect at the next mode initialization." + ;; Clear cached constant values + (mapatoms (lambda (sym) + (set sym nil)) + c-lang-constants) + ;; Recompute our font lock keyword constants + (when (featurep 'cc-fonts) + (load "cc-fonts" nil t))) + (cc-provide 'cc-defs) === modified file 'lisp/progmodes/cc-langs.el' --- lisp/progmodes/cc-langs.el 2014-01-01 07:43:34 +0000 +++ lisp/progmodes/cc-langs.el 2014-05-02 05:19:24 +0000 @@ -1921,15 +1921,13 @@ ;; declaration. Specifically, they aren't recognized in the middle ;; of multi-token types, inside declarators, and between the ;; identifier and the arglist paren of a function declaration. - ;; - ;; FIXME: This ought to be user customizable since compiler stuff - ;; like this usually is wrapped in project specific macros. (It'd - ;; of course be even better if we could cope without knowing this.) - t nil - (c c++) '(;; GCC extension. - "__attribute__" - ;; MSVC extension. - "__declspec")) + t (when (boundp (c-mode-symbol "extra-keywords")) + (mapcar #'car (c-mode-var "extra-keywords"))) + (c c++) (append (c-lang-const c-decl-hangon-kwds) + '( ;; GCC extension. + "__attribute__" + ;; MSVC extension. + "__declspec"))) (c-lang-defconst c-decl-hangon-key ;; Adorned regexp matching `c-decl-hangon-kwds'. @@ -2120,11 +2118,18 @@ (c-lang-defconst c-paren-nontype-kwds "Keywords that may be followed by a parenthesis expression that doesn't contain type identifiers." - t nil - (c c++) '(;; GCC extension. - "__attribute__" - ;; MSVC extension. - "__declspec")) + t (when (boundp (c-mode-symbol "extra-keywords")) + (apply 'nconc + (mapcar (lambda (kw) + (when (cdr kw) + (list (car kw)))) + (c-mode-var "extra-keywords")))) + (c c++) (append + (c-lang-const c-paren-nontype-kwds) + '( ;; GCC extension. + "__attribute__" + ;; MSVC extension. + "__declspec"))) (c-lang-defconst c-paren-type-kwds "Keywords that may be followed by a parenthesis expression containing @@ -3155,115 +3160,38 @@ ;; Make the `c-lang-setvar' variables buffer local in the current buffer. ;; These are typically standard emacs variables such as `comment-start'. -(defmacro c-make-emacs-variables-local () - `(progn - ,@(mapcar (lambda (init) - `(make-local-variable ',(car init))) - (cdr c-emacs-variable-inits)))) - -(defun c-make-init-lang-vars-fun (mode) - "Create a function that initializes all the language dependent variables -for the given mode. - -This function should be evaluated at compile time, so that the -function it returns is byte compiled with all the evaluated results -from the language constants. Use the `c-init-language-vars' macro to -accomplish that conveniently." - - (if (and (not load-in-progress) - (boundp 'byte-compile-dest-file) - (stringp byte-compile-dest-file)) - - ;; No need to byte compile this lambda since the byte compiler is - ;; smart enough to detect the `funcall' construct in the - ;; `c-init-language-vars' macro below and compile it all straight - ;; into the function that contains `c-init-language-vars'. - `(lambda () - - ;; This let sets up the context for `c-mode-var' and similar - ;; that could be in the result from `cl-macroexpand-all'. - (let ((c-buffer-is-cc-mode ',mode) - current-var source-eval) - (c-make-emacs-variables-local) - (condition-case err - - (if (eq c-version-sym ',c-version-sym) - (setq ,@(let ((c-buffer-is-cc-mode mode) - (c-lang-const-expansion 'immediate)) - ;; `c-lang-const' will expand to the evaluated - ;; constant immediately in `cl-macroexpand-all' - ;; below. - (mapcan - (lambda (init) - `(current-var ',(car init) - ,(car init) ,(cl-macroexpand-all - (elt init 1)))) - ;; Note: The following `append' copies the - ;; first argument. That list is small, so - ;; this doesn't matter too much. - (append (cdr c-emacs-variable-inits) - (cdr c-lang-variable-inits))))) - - ;; This diagnostic message isn't useful for end - ;; users, so it's disabled. - ;;(unless (get ',mode 'c-has-warned-lang-consts) - ;; (message ,(concat "%s compiled with CC Mode %s " - ;; "but loaded with %s - evaluating " - ;; "language constants from source") - ;; ',mode ,c-version c-version) - ;; (put ',mode 'c-has-warned-lang-consts t)) - - (setq source-eval t) - (let ((init ',(append (cdr c-emacs-variable-inits) - (cdr c-lang-variable-inits)))) - (while init - (setq current-var (caar init)) - (set (caar init) (eval (cadar init))) - (setq init (cdr init))))) - - (error - (if current-var - (message "Eval error in the `c-lang-defvar' or `c-lang-setvar' for `%s'%s: %S" - current-var - (if source-eval - (format "\ - (fallback source eval - %s compiled with CC Mode %s but loaded with %s)" - ',mode ,c-version c-version) - "") - err) - (signal (car err) (cdr err))))))) - - ;; Being evaluated from source. Always use the dynamic method to - ;; work well when `c-lang-defvar's in this file are reevaluated - ;; interactively. - `(lambda () - (require 'cc-langs) - (let ((c-buffer-is-cc-mode ',mode) - (init (append (cdr c-emacs-variable-inits) - (cdr c-lang-variable-inits))) - current-var) - (c-make-emacs-variables-local) - (condition-case err - - (while init - (setq current-var (caar init)) - (set (caar init) (eval (cadar init))) - (setq init (cdr init))) - - (error - (if current-var - (message - "Eval error in the `c-lang-defvar' or `c-lang-setver' for `%s' (source eval): %S" - current-var err) - (signal (car err) (cdr err))))))) - )) +(defun c-make-emacs-variables-local () + (mapcar (lambda (init) + (make-local-variable (car init))) + (cdr c-emacs-variable-inits))) + +(defun c-init-language-vars-for (mode) + "Initialize the cc-mode language variables for MODE. +MODE is a symbol naming the mode to initialize." + (let ((c-buffer-is-cc-mode mode) + (init (append (cdr c-emacs-variable-inits) + (cdr c-lang-variable-inits))) + current-var) + (c-make-emacs-variables-local) + (condition-case err + (while init + (setq current-var (caar init)) + (set (caar init) (eval (cadar init) nil)) + (setq init (cdr init))) + (error + (if current-var + (message + "Eval error in the `c-lang-defvar' or `c-lang-setver' for `%s' (source eval): %S" + current-var err) + (signal (car err) (cdr err))))))) (defmacro c-init-language-vars (mode) "Initialize all the language dependent variables for the given mode. -This macro is expanded at compile time to a form tailored for the mode -in question, so MODE must be a constant. Therefore MODE is not -evaluated and should not be quoted." - `(funcall ,(c-make-init-lang-vars-fun mode))) +MODE is not evaluated and should not be quoted. This macro used +to produce an optimized initialization tailored to MODE, but that +optimization is no longer worth it. Use +`c-init-language-vars-for' instead." + `(c-init-language-vars-for ',mode)) (cc-provide 'cc-langs) === modified file 'lisp/progmodes/cc-mode.el' --- lisp/progmodes/cc-mode.el 2014-03-04 04:03:34 +0000 +++ lisp/progmodes/cc-mode.el 2014-05-02 01:20:44 +0000 @@ -149,21 +149,6 @@ (defun c-leave-cc-mode-mode () (setq c-buffer-is-cc-mode nil)) -(defun c-init-language-vars-for (mode) - "Initialize the language variables for one of the language modes -directly supported by CC Mode. This can be used instead of the -`c-init-language-vars' macro if the language you want to use is one of -those, rather than a derived language defined through the language -variable system (see \"cc-langs.el\")." - (cond ((eq mode 'c-mode) (c-init-language-vars c-mode)) - ((eq mode 'c++-mode) (c-init-language-vars c++-mode)) - ((eq mode 'objc-mode) (c-init-language-vars objc-mode)) - ((eq mode 'java-mode) (c-init-language-vars java-mode)) - ((eq mode 'idl-mode) (c-init-language-vars idl-mode)) - ((eq mode 'pike-mode) (c-init-language-vars pike-mode)) - ((eq mode 'awk-mode) (c-init-language-vars awk-mode)) - (t (error "Unsupported mode %s" mode)))) - ;;;###autoload (defun c-initialize-cc-mode (&optional new-style-init) "Initialize CC Mode for use in the current buffer. === modified file 'lisp/progmodes/cc-vars.el' --- lisp/progmodes/cc-vars.el 2014-01-01 07:43:34 +0000 +++ lisp/progmodes/cc-vars.el 2014-05-02 05:24:28 +0000 @@ -1614,6 +1614,75 @@ :group 'c) + +(define-widget 'c-extra-keywords-widget 'lazy + "Internal CC Mode widget for the `*-extra-keywords' variables." + :type '(repeat + (cons + (string :tag "Keyword") + (boolean :tag "Parenthesized expression follows")))) + +(defun c-make-extra-keywords-blurb (mode1 mode2) + (concat "\ +*List of extra keywords to recognize in " + mode1 " mode. +Each list item should be a cons (KW . PAREN). +KW should be a string naming a single identifier. +PAREN should be nil or t. If t, expect the a parenthesized expression +after KW and skip over it. + +Note that this variable is only consulted when the major mode is +initialized. If you change it later you have to reinitialize CC +Mode by doing \\[" mode2 "]. Additionally, if you change this +variable outside of customize, you need to call +`c-clear-value-cache' to make your changes take effect.")) + +(defun c-extra-keywords-setter (sym val) + (set-default sym val) + (c-clear-value-cache)) + +(defcustom c-extra-keywords + nil + (c-make-extra-keywords-blurb "C" "c-mode") + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + +(defcustom c++-extra-keywords + nil + (c-make-extra-keywords-blurb "C++" "c++-mode") + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + +(defcustom objc-extra-keywords + nil + (c-make-extra-keywords-blurb "ObjC" "objc-mode") + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + +(defcustom java-extra-keywords + nil + (c-make-extra-keywords-blurb "Java" "java-mode") + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + +(defcustom idl-extra-keywords nil + nil + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + +(defcustom pike-extra-keywords + nil + (c-make-extra-keywords-blurb "Pike" "pike-mode") + :type 'c-extra-keywords-widget + :set 'c-extra-keywords-setter + :group 'c) + + ;; Non-customizable variables, still part of the interface to CC Mode (defvar c-macro-with-semi-re nil ;; Regular expression which matches a (#define'd) symbol whose expansion