1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-02-08 00:33:13 -08:00

Amend to indent and fontify macros "which include their own semicolon"

correctly, using the "virtual semicolon" mechanism.

cc-defs.el: Update "virtual semicolon" comments.

cc-engine.el (c-crosses-statement-barrier-p): Recoded to scan one line at
at time rather than having \n and \r explicitly in c-stmt-delim-chars
(for some modes, e.g. AWK).
(c-forward-label): Amend for virtual semicolons.
(c-at-macro-vsemi-p, c-macro-vsemi-status-unknown-p): New functions

cc-fonts.el (c-font-lock-declarations): Take account of the new C macros.

cc-langs.el (c-at-vsemi-p-fn, c-vsemi-status-unknown-p-fn): move to
earlier in the file.
(c-opt-cpp-symbol, c-line-comment-start-regexp): New language variables.
(c-opt-cpp-macro-define): Make into a full language variable.
(c-stmt-delim-chars, c-stmt-delim-chars-with-comma): Special value for
AWK Mode (including \n, \r) removed, no longer needed.

cc-mode.el (c-mode, c++-mode, objc-mode): Invoke
c-make-macro-with-semi-re.

cc-vars.el (c-macro-with-semi-re, c-macro-names-with-semicolon): New
variables.
(c-make-macro-with-semi-re): New function

cc-mode.texi (Indentation Commands): Mention "macros with semicolons".
(Other Special Indentations): Add an xref to "Macros with ;".
(Customizing Macros): Add stuff about syntax in macros.  Add an xref to
"Macros with ;".
(Macros with ;): New page.
This commit is contained in:
Alan Mackenzie 2011-10-27 20:34:23 +00:00
parent 86c6068184
commit 536610a433
7 changed files with 320 additions and 77 deletions

View file

@ -341,6 +341,11 @@ Line-Up Functions
* Comment Line-Up::
* Misc Line-Up::
Customizing Macros
* Macro Backslashes::
* Macros with ;::
@end detailmenu
@end menu
@ -655,6 +660,10 @@ expression, to some statements, or perhaps to whole functions, the
syntactic recognition can be wrong. @ccmode{} manages to figure it
out correctly most of the time, though.
Some macros, when invoked, ''have their own semicolon''. To get the
next line indented correctly, rather than as a continuation line,
@xref{Macros with ;}.
Reindenting large sections of code can take a long time. When
@ccmode{} reindents a region of code, it is essentially equivalent to
hitting @key{TAB} on every line of the region.
@ -6550,6 +6559,9 @@ custom line-up function associated with it.
@section Other Special Indentations
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
To configure macros which you invoke without a terminating @samp{;},
see @xref{Macros with ;}.
Here are the remaining odds and ends regarding indentation:
@defopt c-label-minimum-indentation
@ -6601,6 +6613,13 @@ functions to this hook, not remove them. @xref{Style Variables}.
@cindex preprocessor directives
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Preprocessor macros in C, C++, and Objective C (introduced by
@code{#define}) have a syntax different from the main language---for
example, a macro declaration is not terminated by a semicolon, and if
it is more than a line long, line breaks in it must be escaped with
backslashes. @ccmode{} has some commands to manipulate these, see
@ref{Macro Backslashes}.
Normally, the lines in a multi-line macro are indented relative to
each other as though they were code. You can suppress this behavior
by setting the following user option:
@ -6612,6 +6631,28 @@ is @code{nil}, all lines inside macro definitions are analyzed as
@code{cpp-macro-cont}.
@end defopt
Because a macro can expand into anything at all, near where one is
invoked @ccmode{} can only indent and fontify code heuristically.
Sometimes it gets it wrong. Usually you should try to design your
macros so that they ''look like ordinary code'' when you invoke them.
However, one situation is so common that @ccmode{} handles it
specially: that is when certain macros needn't (or mustn't) be
followed by a @samp{;}. You need to configure @ccmode{} to handle
these macros properly, see @ref{Macros with ;}.
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@menu
* Macro Backslashes::
* Macros with ;::
@end menu
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@node Macro Backslashes, Macros with ;, Custom Macros, Custom Macros
@comment node-name, next, previous, up
@section Customizing Macro Backslashes
@cindex #define
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@ccmode{} provides some tools to help keep the line continuation
backslashes in macros neat and tidy. Their precise action is
customized with these variables:
@ -6653,6 +6694,62 @@ get aligned only when you explicitly invoke the command
@code{c-backslash-region} (@kbd{C-c C-\}).
@end defopt
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@node Macros with ;, , Macro Backslashes, Custom Macros
@comment node-name, next, previous, up
@section Macros with semicolons
@cindex macros with semicolons
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Macros which needn't (or mustn't) be followed by a semicolon when you
invoke them, @dfn{macros with semicolons}, are very common. These can
cause @ccmode{} to parse the next line wrongly as a
@code{statement-cont} (@pxref{Function Symbols}) and thus mis-indent
it.
You can prevent this by specifying which macros have semicolons. It
doesn't matter whether or not such a macro has a parameter list:
@defopt c-macro-names-with-semicolon
@vindex macro-names-with-semicolon (c-)
This buffer-local variable specifies which macros have semicolons.
After setting its value, you need to call
@code{c-make-macro-with-semi-re} for it to take effect. It should be
set to one of these values:
@table @asis
@item nil
There are no macros with semicolons.
@item a list of strings
Each string is the name of a macro with a semicolon. Only valid
@code{#define} names are allowed here. For example, to set the
default value, you could write the following into your @file{.emacs}:
@example
(setq c-macro-names-with-semicolon
'("Q_OBJECT" "Q_PROPERTY" "Q_DECLARE" "Q_ENUMS"))
@end example
@item a regular expression
This matches each symbol which is a macro with a semicolon. It must
not match any string which isn't a valid @code{#define} name. For
example:
@example
(setq c-macro-names-with-semicolon
"\\<\\(CLEAN_UP_AND_RETURN\\|Q_[[:upper:]]+\\)\\>")
@end example
@end table
@end defopt
@defun c-make-macro-with-semi-re
@findex make-macro-with-semi-re (c-)
Call this (non-interactive) function, which sets internal variables,
each time you change the value of
@code{c-macro-names-with-semicolon}. It takes no arguments, and its
return value has no meaning. This function is called by @ccmode{}'s
initialization code.
@end defun
@comment !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@node Odds and Ends, Sample .emacs File, Custom Macros, Top
@comment node-name, next, previous, up

View file

@ -744,19 +744,20 @@ be after it."
;; V i r t u a l S e m i c o l o n s
;;
;; In most CC Mode languages, statements are terminated explicitly by
;; semicolons or closing braces. In some of the CC modes (currently only AWK
;; Mode (April 2004)), statements are (or can be) terminated by EOLs. Such a
;; statement is said to be terminated by a "virtual semicolon" (VS). A
;; statement terminated by an actual semicolon or brace is never considered to
;; have a VS.
;; semicolons or closing braces. In some of the CC modes (currently AWK Mode
;; and certain user-specified #define macros in C, C++, etc. (November 2008)),
;; statements are (or can be) terminated by EOLs. Such a statement is said to
;; be terminated by a "virtual semicolon" (VS). A statement terminated by an
;; actual semicolon or brace is never considered to have a VS.
;;
;; The indentation engine (or whatever) tests for a VS at a specific position
;; by invoking the macro `c-at-vsemi-p', which in its turn calls the mode
;; specific function (if any) which is the value of the language variable
;; `c-at-vsemi-p-fn'. The actual details of what constitutes a VS in a
;; language are thus encapsulated in code specific to that language
;; (e.g. cc-awk.el). `c-at-vsemi-p' returns non-nil if point (or the optional
;; parameter POS) is at a VS, nil otherwise.
;; `c-at-vsemi-p-fn'. This function should only use "low-level" features of
;; CC Mode, i.e. features which won't trigger infinite recursion. ;-) The
;; actual details of what constitutes a VS in a language are thus encapsulated
;; in code specific to that language (e.g. cc-awk.el). `c-at-vsemi-p' returns
;; non-nil if point (or the optional parameter POS) is at a VS, nil otherwise.
;;
;; The language specific function might well do extensive analysis of the
;; source text, and may use a cacheing scheme to speed up repeated calls.

View file

@ -1149,42 +1149,65 @@ the line. If this virtual semicolon is _at_ from, the function recognizes it.
Note that this function might do hidden buffer changes. See the
comment at the start of cc-engine.el for more info."
(let ((skip-chars c-stmt-delim-chars)
lit-range)
(save-excursion
(catch 'done
(goto-char from)
(while (progn (skip-chars-forward skip-chars to)
(< (point) to))
(cond
((setq lit-range (c-literal-limits from)) ; Have we landed in a string/comment?
(goto-char (cdr lit-range)))
((eq (char-after) ?:)
(forward-char)
(if (and (eq (char-after) ?:)
(< (point) to))
;; Ignore scope operators.
(forward-char)
(setq c-maybe-labelp (1- (point)))))
((eq (char-after) ??)
;; A question mark. Can't be a label, so stop
;; looking for more : and ?.
(setq c-maybe-labelp nil
skip-chars (substring c-stmt-delim-chars 0 -2)))
((memq (char-after) '(?# ?\n ?\r)) ; A virtual semicolon?
(if (and (eq (char-before) ?\\) (memq (char-after) '(?\n ?\r)))
(backward-char))
(skip-chars-backward " \t" from)
(if (c-at-vsemi-p)
(throw 'done (point))
(forward-line)))
(t (throw 'done (point)))))
;; In trailing space after an as yet undetected virtual semicolon?
(c-backward-syntactic-ws from)
(if (and (< (point) to)
(c-at-vsemi-p))
(point)
nil)))))
(let* ((skip-chars
;; If the current language has CPP macros, insert # into skip-chars.
(if c-opt-cpp-symbol
(concat (substring c-stmt-delim-chars 0 1) ; "^"
c-opt-cpp-symbol ; usually "#"
(substring c-stmt-delim-chars 1)) ; e.g. ";{}?:"
c-stmt-delim-chars))
(non-skip-list
(append (substring skip-chars 1) nil)) ; e.g. (?# ?\; ?{ ?} ?? ?:)
lit-range vsemi-pos)
(save-restriction
(widen)
(save-excursion
(catch 'done
(goto-char from)
(while (progn (skip-chars-forward
skip-chars
(min to (c-point 'bonl)))
(< (point) to))
(cond
;; Virtual semicolon?
((and (bolp)
(save-excursion
(progn
(if (setq lit-range (c-literal-limits from)) ; Have we landed in a string/comment?
(goto-char (car lit-range)))
(c-backward-syntactic-ws) ; ? put a limit here, maybe?
(setq vsemi-pos (point))
(c-at-vsemi-p))))
(throw 'done vsemi-pos))
;; In a string/comment?
((setq lit-range (c-literal-limits))
(goto-char (cdr lit-range)))
((eq (char-after) ?:)
(forward-char)
(if (and (eq (char-after) ?:)
(< (point) to))
;; Ignore scope operators.
(forward-char)
(setq c-maybe-labelp (1- (point)))))
((eq (char-after) ??)
;; A question mark. Can't be a label, so stop
;; looking for more : and ?.
(setq c-maybe-labelp nil
skip-chars (substring c-stmt-delim-chars 0 -2)))
;; At a CPP construct?
((and c-opt-cpp-symbol (looking-at c-opt-cpp-symbol)
(save-excursion
(forward-line 0)
(looking-at c-opt-cpp-prefix)))
(c-end-of-macro))
((memq (char-after) non-skip-list)
(throw 'done (point)))))
;; In trailing space after an as yet undetected virtual semicolon?
(c-backward-syntactic-ws from)
(if (and (< (point) to)
(c-at-vsemi-p))
(point)
nil))))))
(defun c-at-statement-start-p ()
"Return non-nil if the point is at the first token in a statement
@ -7158,12 +7181,14 @@ comment at the start of cc-engine.el for more info."
;; Check that we're not after a token that can't precede a label.
(or
;; Trivially succeeds when there's no preceding token.
;; Succeeds when we're at a virtual semicolon.
(if preceding-token-end
(<= preceding-token-end (point-min))
(save-excursion
(c-backward-syntactic-ws)
(setq preceding-token-end (point))
(bobp)))
(or (bobp)
(c-at-vsemi-p))))
;; Check if we're after a label, if we're after a closing
;; paren that belong to statement, and with
@ -8372,6 +8397,57 @@ comment at the start of cc-engine.el for more info."
paren-state)
containing-sexp)))))
(defun c-at-macro-vsemi-p (&optional pos)
;; Is there a "virtual semicolon" at POS or point?
;; (See cc-defs.el for full details of "virtual semicolons".)
;;
;; This is true when point is at the last non syntactic WS position on the
;; line, there is a macro call last on the line, and this particular macro's
;; name is defined by the regexp `c-vs-macro-regexp' as not needing a
;; semicolon.
(save-excursion
(save-restriction
(widen)
(if pos
(goto-char pos)
(setq pos (point)))
(and
c-macro-with-semi-re
(not (c-in-literal))
(eq (skip-chars-backward " \t") 0)
;; Check we've got nothing after this except comments and empty lines
;; joined by escaped EOLs.
(skip-chars-forward " \t") ; always returns non-nil.
(progn
(while ; go over 1 block comment per iteration.
(and
(looking-at "\\(\\\\[\n\r][ \t]*\\)*")
(goto-char (match-end 0))
(cond
((looking-at c-block-comment-start-regexp)
(and (forward-comment 1)
(skip-chars-forward " \t"))) ; always returns non-nil
((looking-at c-line-comment-start-regexp)
(end-of-line)
nil)
(t nil))))
(eolp))
(goto-char pos)
(progn (c-backward-syntactic-ws)
(eq (point) pos))
;; Check for one of the listed macros being before point.
(or (not (eq (char-before) ?\)))
(when (c-go-list-backward)
(c-backward-syntactic-ws)
t))
(c-simple-skip-symbol-backward)
(looking-at c-macro-with-semi-re)))))
(defun c-macro-vsemi-status-unknown-p () t) ; See cc-defs.el.
;; `c-guess-basic-syntax' and the functions that precedes it below
;; implements the main decision tree for determining the syntactic

View file

@ -1277,9 +1277,11 @@ casts and declarations are fontified. Used on level 2 and higher."
(when
;; The result of the form below is true when we don't recognize a
;; declaration or cast.
(if (and (eq (get-text-property (point) 'face)
'font-lock-keyword-face)
(looking-at c-not-decl-init-keywords))
(if (or (and (eq (get-text-property (point) 'face)
'font-lock-keyword-face)
(looking-at c-not-decl-init-keywords))
(and c-macro-with-semi-re
(looking-at c-macro-with-semi-re))) ; 2008-11-04
;; Don't do anything more if we're looking at a keyword that
;; can't start a declaration.
t

View file

@ -508,6 +508,31 @@ parameters \(point-min), \(point-max) and <buffer size>."
(c-lang-defvar c-before-font-lock-function
(c-lang-const c-before-font-lock-function))
;;; Syntactic analysis ("virtual semicolons") for line-oriented languages (AWK).
(c-lang-defconst c-at-vsemi-p-fn
"Contains a function \"Is there a virtual semicolon at POS or point?\".
Such a function takes one optional parameter, a buffer position (defaults to
point), and returns nil or t. This variable contains nil for languages which
don't have EOL terminated statements. "
t nil
(c c++ objc) 'c-at-macro-vsemi-p
awk 'c-awk-at-vsemi-p)
(c-lang-defvar c-at-vsemi-p-fn (c-lang-const c-at-vsemi-p-fn))
(c-lang-defconst c-vsemi-status-unknown-p-fn
"Contains a function \"are we unsure whether there is a virtual semicolon on this line?\".
The (admittedly kludgey) purpose of such a function is to prevent an infinite
recursion in c-beginning-of-statement-1 when point starts at a `while' token.
The function MUST NOT UNDER ANY CIRCUMSTANCES call c-beginning-of-statement-1,
even indirectly. This variable contains nil for languages which don't have
EOL terminated statements."
t nil
(c c++ objc) 'c-macro-vsemi-status-unknown-p
awk 'c-awk-vsemi-status-unknown-p)
(c-lang-defvar c-vsemi-status-unknown-p-fn
(c-lang-const c-vsemi-status-unknown-p-fn))
;;; Lexer-level syntax (identifiers, tokens etc).
@ -737,6 +762,12 @@ literal are multiline."
(c-lang-defvar c-multiline-string-start-char
(c-lang-const c-multiline-string-start-char))
(c-lang-defconst c-opt-cpp-symbol
"The symbol which starts preprocessor constructs when in the margin."
t "#"
(java awk) nil)
(c-lang-defvar c-opt-cpp-symbol (c-lang-const c-opt-cpp-symbol))
(c-lang-defconst c-opt-cpp-prefix
"Regexp matching the prefix of a cpp directive in the languages that
normally use that macro preprocessor. Tested at bol or at boi.
@ -785,6 +816,8 @@ file name in angle brackets or quotes."
definition, or nil if the language doesn't have any."
t (if (c-lang-const c-opt-cpp-prefix)
"define"))
(c-lang-defvar c-opt-cpp-macro-define
(c-lang-const c-opt-cpp-macro-define))
(c-lang-defconst c-opt-cpp-macro-define-start
;; Regexp matching everything up to the macro body of a cpp define, or the
@ -1171,14 +1204,12 @@ operators."
;; optimize `c-crosses-statement-barrier-p' somewhat, it's assumed to
;; begin with "^" to negate the set. If ? : operators should be
;; detected then the string must end with "?:".
t "^;{}?:"
awk "^;{}#\n\r?:") ; The newline chars gets special treatment.
t "^;{}?:")
(c-lang-defvar c-stmt-delim-chars (c-lang-const c-stmt-delim-chars))
(c-lang-defconst c-stmt-delim-chars-with-comma
;; Variant of `c-stmt-delim-chars' that additionally contains ','.
t "^;,{}?:"
awk "^;,{}\n\r?:") ; The newline chars gets special treatment.
t "^;,{}?:")
(c-lang-defvar c-stmt-delim-chars-with-comma
(c-lang-const c-stmt-delim-chars-with-comma))
@ -1238,7 +1269,6 @@ properly."
re)))
(c-lang-defvar c-comment-start-regexp (c-lang-const c-comment-start-regexp))
;;;; Added by ACM, 2003/9/18.
(c-lang-defconst c-block-comment-start-regexp
;; Regexp which matches the start of a block comment (if such exists in the
;; language)
@ -1248,6 +1278,15 @@ properly."
(c-lang-defvar c-block-comment-start-regexp
(c-lang-const c-block-comment-start-regexp))
(c-lang-defconst c-line-comment-start-regexp
;; Regexp which matches the start of a line comment (if such exists in the
;; language; it does in all 7 CC Mode languages).
t (if (c-lang-const c-line-comment-starter)
(regexp-quote (c-lang-const c-line-comment-starter))
"\\<\\>"))
(c-lang-defvar c-line-comment-start-regexp
(c-lang-const c-line-comment-start-regexp))
(c-lang-defconst c-literal-start-regexp
;; Regexp to match the start of comments and string literals.
t (concat (c-lang-const c-comment-start-regexp)
@ -1474,29 +1513,6 @@ properly."
"\\)"))
(c-lang-defvar c-syntactic-eol (c-lang-const c-syntactic-eol))
;;; Syntactic analysis ("virtual semicolons") for line-oriented languages (AWK).
(c-lang-defconst c-at-vsemi-p-fn
"Contains a function \"Is there a virtual semicolon at POS or point?\".
Such a function takes one optional parameter, a buffer position (defaults to
point), and returns nil or t. This variable contains nil for languages which
don't have EOL terminated statements. "
t nil
awk 'c-awk-at-vsemi-p)
(c-lang-defvar c-at-vsemi-p-fn (c-lang-const c-at-vsemi-p-fn))
(c-lang-defconst c-vsemi-status-unknown-p-fn
"Contains a function \"are we unsure whether there is a virtual semicolon on this line?\".
The (admittedly kludgey) purpose of such a function is to prevent an infinite
recursion in c-beginning-of-statement-1 when point starts at a `while' token.
The function MUST NOT UNDER ANY CIRCUMSTANCES call c-beginning-of-statement-1,
even indirectly. This variable contains nil for languages which don't have
EOL terminated statements."
t nil
awk 'c-awk-vsemi-status-unknown-p)
(c-lang-defvar c-vsemi-status-unknown-p-fn
(c-lang-const c-vsemi-status-unknown-p-fn))
;;; Defun functions

View file

@ -1187,6 +1187,7 @@ Key bindings:
abbrev-mode t)
(use-local-map c-mode-map)
(c-init-language-vars-for 'c-mode)
(c-make-macro-with-semi-re) ; matches macro names whose expansion ends with ;
(c-common-init 'c-mode)
(easy-menu-add c-c-menu)
(cc-imenu-init cc-imenu-c-generic-expression)
@ -1246,6 +1247,7 @@ Key bindings:
abbrev-mode t)
(use-local-map c++-mode-map)
(c-init-language-vars-for 'c++-mode)
(c-make-macro-with-semi-re) ; matches macro names whose expansion ends with ;
(c-common-init 'c++-mode)
(easy-menu-add c-c++-menu)
(cc-imenu-init cc-imenu-c++-generic-expression)
@ -1303,6 +1305,7 @@ Key bindings:
abbrev-mode t)
(use-local-map objc-mode-map)
(c-init-language-vars-for 'objc-mode)
(c-make-macro-with-semi-re) ; matches macro names whose expansion ends with ;
(c-common-init 'objc-mode)
(easy-menu-add c-objc-menu)
(cc-imenu-init nil 'cc-imenu-objc-function)

View file

@ -1608,6 +1608,54 @@ names)."))
;; 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
;; ends with a semicolon.
;;
;; This variable should be set by `c-make-macros-with-semi-re' rather than
;; directly.
)
(make-variable-buffer-local 'c-macro-with-semi-re)
(defun c-make-macro-with-semi-re ()
;; Convert `c-macro-names-with-semicolon' into the regexp
;; `c-macro-with-semi-re' (or just copy it if it's already a re).
(setq c-macro-with-semi-re
(and
c-opt-cpp-macro-define
(cond
((stringp c-macro-names-with-semicolon)
(copy-sequence c-macro-names-with-semicolon))
((consp c-macro-names-with-semicolon)
(concat
"\\<"
(regexp-opt c-macro-names-with-semicolon)
"\\>")) ; N.B. the PAREN param of regexp-opt isn't supported by
; all XEmacsen.
((null c-macro-names-with-semicolon)
nil)
(t (error "c-make-macro-with-semi-re: invalid \
c-macro-names-with-semicolon: %s"
c-macro-names-with-semicolon))))))
(defvar c-macro-names-with-semicolon
'("Q_OBJECT" "Q_PROPERTY" "Q_DECLARE" "Q_ENUMS")
"List of #defined symbols whose expansion ends with a semicolon.
Alternatively it can be a string, a regular expression which
matches all such symbols.
The \"symbols\" must be syntactically valid identifiers in the
target language \(C, C++, Objective C), or \(as the case may be)
the regular expression must match only valid identifiers.
If you change this variable's value, call the function
`c-make-macros-with-semi-re' to set the necessary internal
variables.
Note that currently \(2008-11-04) this variable is a prototype,
and is likely to disappear or change its form soon.")
(make-variable-buffer-local 'c-macro-names-with-semicolon)
(defvar c-file-style nil
"Variable interface for setting style via File Local Variables.
In a file's Local Variable section, you can set this variable to a