From: Daniel Colascione <dancol@dancol.org>
To: Emacs developers <emacs-devel@gnu.org>
Subject: POC: customizable cc-mode keywords
Date: Thu, 01 May 2014 22:26:07 -0700 [thread overview]
Message-ID: <53632C6F.5070903@dancol.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 18192 bytes --]
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 @@
\f
;;; 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)))
+
\f
(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))
\f
(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)
\f
+
+(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)
+
+\f
;; 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
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 884 bytes --]
next reply other threads:[~2014-05-02 5:26 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-05-02 5:26 Daniel Colascione [this message]
2014-05-10 23:13 ` POC: customizable cc-mode keywords Daniel Colascione
2014-05-11 21:13 ` Alan Mackenzie
2014-05-11 21:23 ` Daniel Colascione
2014-05-16 17:52 ` Alan Mackenzie
2014-05-16 18:06 ` Daniel Colascione
2014-05-18 21:33 ` Alan Mackenzie
2014-05-18 22:28 ` Daniel Colascione
2014-05-19 2:25 ` Stefan Monnier
2014-05-25 18:08 ` Alan Mackenzie
2014-09-08 17:28 ` Stefan Monnier
2014-09-11 13:55 ` Further CC-mode changes Stefan Monnier
2014-09-12 23:59 ` Alan Mackenzie
2014-09-13 1:09 ` Ivan Andrus
2014-09-13 10:04 ` Alan Mackenzie
2014-09-13 3:04 ` Stefan Monnier
2014-09-13 15:10 ` Alan Mackenzie
2014-09-13 19:24 ` Stefan Monnier
2014-09-13 23:08 ` Syntax-propertize and CC-mode [Was: Further CC-mode changes] Alan Mackenzie
2014-09-14 4:04 ` Stefan Monnier
2014-09-16 17:30 ` Sync'ing cc-mode Stefan Monnier
2014-09-26 19:19 ` Stefan Monnier
[not found] ` <jwv4mvmjl0c.fsf-monnier+emacs@gnu.org>
[not found] ` <jwv4mvkqy4g.fsf-monnier+emacs@gnu.org>
[not found] ` <jwv4mv3mn2a.fsf-monnier+emacs@gnu.org>
[not found] ` <jwv38a644p7.fsf-monnier+emacs@gnu.org>
[not found] ` <jwvppd2wx7o.fsf-monnier+emacs@gnu.org>
[not found] ` <20141106104534.GA3385@acm.acm>
2014-11-06 14:06 ` bug#18022: " Stefan Monnier
2014-09-15 20:24 ` Further CC-mode changes Glenn Morris
2014-09-16 3:07 ` Stephen J. Turnbull
2014-09-16 13:39 ` Stefan Monnier
2014-09-16 14:22 ` David Kastrup
2014-09-16 23:40 ` Stephen J. Turnbull
2014-09-17 1:02 ` Stefan Monnier
2014-09-17 1:48 ` Stephen J. Turnbull
2014-09-17 5:22 ` David Kastrup
2014-09-17 13:00 ` Stefan Monnier
2014-09-17 18:31 ` Glenn Morris
2014-09-17 19:12 ` David Kastrup
2014-09-17 5:24 ` Eli Zaretskii
2014-09-17 6:54 ` Stephen J. Turnbull
2014-09-17 7:20 ` Eli Zaretskii
2014-09-17 7:30 ` David Kastrup
2014-09-17 13:04 ` Stefan Monnier
2014-09-17 18:25 ` Glenn Morris
2014-09-18 5:20 ` Stephen J. Turnbull
2014-09-18 9:44 ` Emilio Lopes
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=53632C6F.5070903@dancol.org \
--to=dancol@dancol.org \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.