1141 lines
29 KiB
Common Lisp
1141 lines
29 KiB
Common Lisp
;;
|
|
;; Copyright (c) 2002 by The XFree86 Project, Inc.
|
|
;;
|
|
;; Permission is hereby granted, free of charge, to any person obtaining a
|
|
;; copy of this software and associated documentation files (the "Software"),
|
|
;; to deal in the Software without restriction, including without limitation
|
|
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
;; and/or sell copies of the Software, and to permit persons to whom the
|
|
;; Software is furnished to do so, subject to the following conditions:
|
|
;;
|
|
;; The above copyright notice and this permission notice shall be included in
|
|
;; all copies or substantial portions of the Software.
|
|
;;
|
|
;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
;; THE XFREE86 PROJECT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
;; WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
;; OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
;; SOFTWARE.
|
|
;;
|
|
;; Except as contained in this notice, the name of the XFree86 Project shall
|
|
;; not be used in advertising or otherwise to promote the sale, use or other
|
|
;; dealings in this Software without prior written authorization from the
|
|
;; XFree86 Project.
|
|
;;
|
|
;; Author: Paulo César Pereira de Andrade
|
|
;;
|
|
;;
|
|
;; $XFree86: xc/programs/xedit/lisp/modules/progmodes/c.lsp,v 1.27 2004/01/12 17:53:20 paulo Exp $
|
|
;;
|
|
|
|
(require "syntax")
|
|
(require "indent")
|
|
(in-package "XEDIT")
|
|
|
|
(defsynprop *prop-format*
|
|
"format"
|
|
:font "*lucidatypewriter-medium-r*-12-*"
|
|
:foreground "RoyalBlue2"
|
|
:underline t
|
|
)
|
|
|
|
(defsynoptions *c-DEFAULT-style*
|
|
;; Positive number. Basic indentation.
|
|
(:indentation . 4)
|
|
|
|
;; Boolean. Support for GNU style indentation.
|
|
(:brace-indent . nil)
|
|
|
|
;; Boolean. Add one indentation level to case and default?
|
|
(:case-indent . t)
|
|
|
|
;; Boolean. Remove one indentation level for labels?
|
|
(:label-dedent . t)
|
|
|
|
;; Boolean. Add one indentation level to continuations?
|
|
(:cont-indent . t)
|
|
|
|
;; Boolean. Move cursor to the indent column after pressing <Enter>?
|
|
(:newline-indent . t)
|
|
|
|
;; Boolean. Set to T if tabs shouldn't be used to fill indentation.
|
|
(:emulate-tabs . nil)
|
|
|
|
;; Boolean. Force a newline before braces?
|
|
(:newline-before-brace . nil)
|
|
|
|
;; Boolean. Force a newline after braces?
|
|
(:newline-after-brace . nil)
|
|
|
|
;; Boolean. Force a newline after semicolons?
|
|
(:newline-after-semi . nil)
|
|
|
|
;; Boolean. Only calculate indentation after pressing <Enter>?
|
|
;; This may be useful if the parser does not always
|
|
;; do what the user expects...
|
|
(:only-newline-indent . nil)
|
|
|
|
;; Boolean. Remove extra spaces from previous line.
|
|
;; This should default to T when newline-indent is not NIL.
|
|
(:trim-blank-lines . t)
|
|
|
|
;; Boolean. If this hash-table entry is set, no indentation is done.
|
|
;; Useful to temporarily disable indentation.
|
|
(:disable-indent . nil)
|
|
)
|
|
|
|
;; BSD like style
|
|
(defsynoptions *c-BSD-style*
|
|
(:indentation . 8)
|
|
(:brace-indent . nil)
|
|
(:case-indent . nil)
|
|
(:label-dedent . t)
|
|
(:cont-indent . t)
|
|
(:newline-indent . t)
|
|
(:emulate-tabs . nil)
|
|
(:newline-before-brace . nil)
|
|
(:newline-after-brace . t)
|
|
(:newline-after-semi . t)
|
|
(:trim-blank-lines . t)
|
|
)
|
|
|
|
;; GNU like style
|
|
(defsynoptions *c-GNU-style*
|
|
(:indentation . 2)
|
|
(:brace-indent . t)
|
|
(:case-indent . nil)
|
|
(:label-dedent . t)
|
|
(:cont-indent . t)
|
|
(:newline-indent . nil)
|
|
(:emulate-tabs . nil)
|
|
(:newline-before-brace . t)
|
|
(:newline-after-brace . t)
|
|
(:newline-after-semi . t)
|
|
(:trim-blank-lines . nil)
|
|
)
|
|
|
|
;; K&R like style
|
|
(defsynoptions *c-K&R-style*
|
|
(:indentation . 5)
|
|
(:brace-indent . nil)
|
|
(:case-indent . nil)
|
|
(:label-dedent . t)
|
|
(:cont-indent . t)
|
|
(:newline-indent . t)
|
|
(:emulate-tabs . t)
|
|
(:newline-before-brace . t)
|
|
(:newline-after-brace . t)
|
|
(:newline-after-semi . t)
|
|
(:trim-blank-lines . t)
|
|
)
|
|
|
|
(defvar *c-styles* '(
|
|
("xedit" . *c-DEFAULT-style*)
|
|
("BSD" . *c-BSD-style*)
|
|
("GNU" . *c-GNU-style*)
|
|
("K&R" . *c-K&R-style*)
|
|
))
|
|
|
|
(defvar *c-mode-options* *c-DEFAULT-style*)
|
|
; (setq *c-mode-options* *c-gnu-style*)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; This is a very lazy "pattern matcher" for the C language.
|
|
;; If the syntax in the code is not correct, it may get confused, and
|
|
;; because it is "lazy" some wrong constructs will be recognized as
|
|
;; correct when reducing patterns.
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(defindent *c-mode-indent* :main
|
|
;; this must be the first token
|
|
(indtoken "^\\s*" :start-of-line)
|
|
(indtoken "\\<case\\>" :c-case)
|
|
(indtoken "\\<default\\>" :c-default)
|
|
(indtoken "\\<do\\>" :do)
|
|
(indtoken "\\<if\\>" :c-if)
|
|
(indtoken "\\<else\\>" :c-else)
|
|
(indtoken "\\<for\\>" :c-for)
|
|
(indtoken "\\<switch\\>" :c-switch)
|
|
(indtoken "\\<while\\>" :c-while)
|
|
;; Match identifiers and numbers as an expression
|
|
(indtoken "\\w+" :expression)
|
|
(indtoken ";" :semi :nospec t)
|
|
(indtoken "," :comma :nospec t)
|
|
(indtoken ":" :collon :nospec t)
|
|
;; Ignore spaces before collon, this avoids dedenting ternary
|
|
;; and bitfield definitions as the parser does not distinguish
|
|
;; labels from those, another option would be to use the pattern
|
|
;; "\\w+:", but this way should properly handle labels generated
|
|
;; by macros, example: `MACRO_LABEL(value):'
|
|
(indtoken "\\s+:" nil)
|
|
|
|
(indinit (c-braces 0))
|
|
(indtoken "{"
|
|
:obrace
|
|
:nospec t
|
|
:code (decf c-braces)
|
|
)
|
|
(indtoken "}"
|
|
:cbrace
|
|
:nospec t
|
|
:begin :braces
|
|
:code (incf c-braces)
|
|
)
|
|
(indtable :braces
|
|
(indtoken "{"
|
|
:obrace
|
|
:nospec t
|
|
:switch -1
|
|
:code (decf c-braces)
|
|
)
|
|
(indtoken "}"
|
|
:cbrace
|
|
:nospec t
|
|
:begin :braces
|
|
:code (incf c-braces)
|
|
)
|
|
)
|
|
|
|
(indinit (c-bra 0))
|
|
(indtoken ")" :cparen :nospec t :code (incf c-bra))
|
|
(indtoken "(" :oparen :nospec t :code (decf c-bra))
|
|
(indtoken "]" :cbrack :nospec t :code (incf c-bra))
|
|
(indtoken "[" :obrack :nospec t :code (decf c-bra))
|
|
(indtoken "\\\\$" :continuation)
|
|
|
|
;; C++ style comment, disallow other tokens to match inside comment
|
|
(indtoken "//.*$" nil)
|
|
|
|
(indtoken "#" :hash :nospec t)
|
|
|
|
;; if in the same line, reduce now, this must be done because the
|
|
;; delimiters are identical
|
|
(indtoken "'([^\\']|\\\\.)*'" :expression)
|
|
(indtoken "\"([^\\\"]|\\\\.)*\"" :expression)
|
|
|
|
(indtoken "\"" :cstring :nospec t :begin :string)
|
|
|
|
(indtoken "'" :cconstant :nospec t :begin :constant)
|
|
|
|
(indtoken "*/" :ccomment :nospec t :begin :comment)
|
|
;; this must be the last token
|
|
(indtoken "$" :end-of-line)
|
|
|
|
(indtable :string
|
|
;; Ignore escaped characters
|
|
(indtoken "\\." nil)
|
|
;; Return to the toplevel when the start of the string is found
|
|
(indtoken "\"" :ostring :nospec t :switch -1)
|
|
)
|
|
(indtable :constant
|
|
;; Ignore escaped characters
|
|
(indtoken "\\." nil)
|
|
;; Return to the toplevel when the start of the character is found
|
|
(indtoken "'" :oconstant :nospec t :switch -1)
|
|
)
|
|
(indtable :comment
|
|
(indtoken "/*" :ocomment :nospec t :switch -1)
|
|
)
|
|
|
|
;; "Complex" statements
|
|
(indinit (c-complex 0) (c-cases 0))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Order of reduce rules here is important, process comment,
|
|
;; continuations, preprocessor and set states when an eol is found.
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(indinit (c-offset (point-max))
|
|
(c-prev-offset c-offset)
|
|
)
|
|
(indreduce :indent
|
|
t
|
|
((:start-of-line))
|
|
(and (= *ind-start* *ind-offset*)
|
|
(setq
|
|
*offset* (+ *ind-offset* *ind-length*)
|
|
)
|
|
)
|
|
(setq
|
|
c-prev-offset c-offset
|
|
c-offset *ind-offset*
|
|
)
|
|
)
|
|
|
|
;; Delete comments
|
|
(indreduce nil
|
|
t
|
|
((:ocomment nil :ccomment))
|
|
)
|
|
|
|
;; Join in a single token to simplify removal of possible multiline
|
|
;; preprocessor directives
|
|
(indinit c-continuation)
|
|
(indreduce :continuation
|
|
t
|
|
((:continuation :end-of-line))
|
|
(setq c-continuation t)
|
|
)
|
|
|
|
(indreduce :eol
|
|
t
|
|
((:end-of-line))
|
|
;; Anything after the eol offset is safe to parse now
|
|
(setq c-continuation nil)
|
|
)
|
|
|
|
;; Delete blank lines
|
|
(indreduce nil
|
|
t
|
|
((:indent :eol))
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Preprocessor
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce nil
|
|
(>= *ind-offset* *ind-start*)
|
|
((:indent :hash))
|
|
(setq *indent* 0)
|
|
(indent-macro-reject-left)
|
|
)
|
|
(indreduce nil
|
|
t
|
|
((:indent :hash nil :eol))
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Expressions
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce :expression
|
|
t
|
|
;; Reduce to a single expression
|
|
((:expression :parens)
|
|
(:expression :bracks)
|
|
(:expression :expression)
|
|
;; These may be multiline
|
|
(:ostring (not :ostring) :cstring)
|
|
(:oconstant (not :oconstant) :cconstant)
|
|
)
|
|
)
|
|
|
|
(indreduce :expression
|
|
t
|
|
((:expression :eol :indent :expression)
|
|
(:expression :eol :expression)
|
|
)
|
|
)
|
|
|
|
(indreduce :exp-comma
|
|
t
|
|
((:expression :comma)
|
|
)
|
|
)
|
|
|
|
;; A semicollon, start a statement
|
|
(indreduce :stat
|
|
t
|
|
((:semi))
|
|
)
|
|
|
|
;; Expression following (possibly empty) statement
|
|
(indreduce :stat
|
|
t
|
|
(((or :expression :exp-comma) :stat))
|
|
)
|
|
|
|
;; Multiline statements
|
|
(indreduce :stat
|
|
t
|
|
(((or :expression :exp-comma) :eol :indent :stat)
|
|
;; rule below may have removed the :indent
|
|
((or :expression :exp-comma) :eol :stat)
|
|
)
|
|
)
|
|
|
|
(indinit c-exp-indent)
|
|
;; XXX This rule avoids parsing large amounts of code
|
|
(indreduce :stat
|
|
t
|
|
;; Eat eol if following expression
|
|
((:indent :stat :eol)
|
|
(:indent :stat)
|
|
)
|
|
(if
|
|
(or
|
|
(null c-exp-indent)
|
|
(/= (cdar c-exp-indent) (+ *ind-offset* *ind-length*))
|
|
)
|
|
;; A new statement, i.e. not just joining a multiline one
|
|
(push
|
|
(cons
|
|
(offset-indentation *ind-offset* :resolve t)
|
|
(+ *ind-offset* *ind-length*)
|
|
)
|
|
c-exp-indent
|
|
)
|
|
;; Update start of statement
|
|
(rplaca
|
|
(car c-exp-indent)
|
|
(offset-indentation *ind-offset* :resolve t)
|
|
)
|
|
)
|
|
(when (consp (cdr c-exp-indent))
|
|
(if (and
|
|
(zerop c-complex)
|
|
(zerop c-cases)
|
|
(zerop c-bra)
|
|
(= (caar c-exp-indent) (caadr c-exp-indent))
|
|
)
|
|
;; Two statements with the same indentation
|
|
(progn
|
|
(setq *indent* (caar c-exp-indent))
|
|
(indent-macro-reject-left)
|
|
)
|
|
;; Different indentation or complex state
|
|
(progn
|
|
(rplacd c-exp-indent nil)
|
|
(setq c-complex 0)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle braces
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce :stat
|
|
;; If block finishes before current line, group as a statement
|
|
(< (+ *ind-offset* *ind-length*) *ind-start*)
|
|
((:obrace (not :obrace) :cbrace))
|
|
)
|
|
(indreduce :obrace
|
|
;; If not in the first line
|
|
(< *ind-offset* *ind-start*)
|
|
;; If the opening { is the first non blank char in the line
|
|
((:indent :obrace))
|
|
(setq *indent* (offset-indentation (+ *ind-offset* *ind-length*)))
|
|
|
|
;; XXX This may be the starting brace of a switch
|
|
(setq c-case-flag nil)
|
|
(indent-macro-reject-left)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Labels
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; XXX this frequently doesn't do what is expected, should redefine
|
|
;; some rules, as it frequently will dedent while typing something
|
|
;; like test ? exp1 : exp2
|
|
;; ^ dedents here because it reduces everything
|
|
;; before ':' to a single :expression token.
|
|
(indreduce :label
|
|
t
|
|
((:indent :expression :collon :eol))
|
|
(when (and *label-dedent* (>= *ind-offset* *ind-start*))
|
|
(setq
|
|
*indent*
|
|
(- (offset-indentation *ind-offset* :resolve t) *base-indent*)
|
|
)
|
|
(indent-macro-reject-left)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle if
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce :if
|
|
t
|
|
((:c-if :parens)
|
|
)
|
|
(incf c-complex)
|
|
)
|
|
|
|
(indreduce :else
|
|
t
|
|
((:c-else))
|
|
(incf c-complex)
|
|
)
|
|
|
|
;; Join
|
|
(indreduce :else-if
|
|
t
|
|
((:else :if)
|
|
(:else :eol :indent :if)
|
|
)
|
|
(incf c-complex)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle for
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Join with the parentheses
|
|
(indreduce :for
|
|
t
|
|
((:c-for :parens)
|
|
)
|
|
(incf c-complex)
|
|
)
|
|
;; Before current line, simplify
|
|
(indreduce :stat
|
|
(< (+ *ind-offset* *ind-length*) *ind-point*)
|
|
((:for :stat)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle while and do
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce :while
|
|
t
|
|
((:c-while :parens)
|
|
)
|
|
(incf c-complex)
|
|
)
|
|
(indreduce :stat
|
|
t
|
|
((:do :stat :while)
|
|
(:while :stat)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle switch
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indinit c-case-flag)
|
|
|
|
(indreduce :switch
|
|
t
|
|
((:c-switch :parens)
|
|
)
|
|
)
|
|
;; Transform in a statement
|
|
(indreduce :stat
|
|
(< (+ *ind-offset* *ind-length*) *ind-start*)
|
|
((:switch :stat)
|
|
;; Do it now or some rule may stop parsing, and calculate
|
|
;; a wrong indentation for nested switches
|
|
(:switch :eol :indent :stat)
|
|
)
|
|
)
|
|
;; An open switch
|
|
(indreduce :obrace
|
|
(and
|
|
(<= c-braces 0)
|
|
(> *ind-start* *ind-offset*)
|
|
)
|
|
((:indent :switch :obrace)
|
|
)
|
|
(setq
|
|
*indent* (offset-indentation *ind-offset* :resolve t)
|
|
c-case-flag nil
|
|
)
|
|
(indent-macro-reject-left)
|
|
)
|
|
(indreduce :obrace
|
|
(and
|
|
(<= c-braces 0)
|
|
(> *ind-start* *ind-offset*)
|
|
)
|
|
((:indent :switch :eol :indent :obrace)
|
|
)
|
|
(setq
|
|
*indent* (- (offset-indentation *ind-offset* :resolve t) *base-indent*)
|
|
c-case-flag nil
|
|
)
|
|
(and *brace-indent* (incf *indent* *base-indent*))
|
|
(indent-macro-reject-left)
|
|
)
|
|
;; Before current line
|
|
(indreduce :case
|
|
(and
|
|
(or
|
|
(not *case-indent*)
|
|
(prog1 c-case-flag (setq c-case-flag t))
|
|
)
|
|
(<= c-braces 0)
|
|
(< *ind-offset* *ind-start*)
|
|
)
|
|
((:indent :case)
|
|
)
|
|
(setq
|
|
*indent* (offset-indentation *ind-offset* :resolve t)
|
|
c-case-flag nil
|
|
)
|
|
(indent-macro-reject-left)
|
|
)
|
|
(indreduce :case
|
|
t
|
|
((:c-case :expression :collon)
|
|
(:c-default :collon)
|
|
;; Assume that it is yet being edited, or adjusting indentation
|
|
(:c-case)
|
|
(:c-default)
|
|
)
|
|
(and (>= *ind-offset* *ind-start*)
|
|
(incf c-cases)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Handle parentheses and brackets
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Reduce matches
|
|
(indreduce :parens
|
|
t
|
|
((:oparen (not :oparen) :cparen))
|
|
(when
|
|
(and
|
|
(< *ind-offset* *ind-start*)
|
|
(> (+ *ind-offset* *ind-length*) *ind-start*)
|
|
)
|
|
(setq *indent* (1+ (offset-indentation *ind-offset* :align t)))
|
|
(indent-macro-reject-left)
|
|
)
|
|
)
|
|
(indreduce :bracks
|
|
t
|
|
((:obrack (not :obrack) :cbrack))
|
|
(when
|
|
(and
|
|
(< *ind-offset* *ind-start*)
|
|
(> (+ *ind-offset* *ind-length*) *ind-start*)
|
|
)
|
|
(setq *indent* (1+ (offset-indentation *ind-offset* :align t)))
|
|
(indent-macro-reject-left)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Assuming previous lines have correct indentation, this allows
|
|
;; resolving the indentation fastly
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Line ended with an open brace
|
|
(indreduce :obrace
|
|
(< *ind-offset* *ind-start*)
|
|
((:indent (or :for :while :if :else-if :else :do) :obrace)
|
|
)
|
|
(setq *indent* (offset-indentation *ind-offset* :resolve t))
|
|
(indent-macro-reject-left)
|
|
)
|
|
;; Adjust indentation level if current line starts with an open brace
|
|
(indreduce nil
|
|
(< *ind-offset* *ind-start* (+ *ind-offset* *ind-length*))
|
|
;; Just set initial indentation
|
|
((:indent (or :for :while :if :else-if :else :do) :eol :indent :obrace)
|
|
)
|
|
(setq
|
|
*indent*
|
|
(- (offset-indentation *ind-offset* :resolve t) *base-indent*)
|
|
)
|
|
(and *brace-indent* (incf *indent* *base-indent*))
|
|
(indent-macro-reject-left)
|
|
)
|
|
;; Previous rule failed, current line does not start with an open brace
|
|
(indreduce :flow
|
|
;; first statement is in current line
|
|
(and
|
|
(<= c-braces 0)
|
|
(> (+ *ind-offset* *ind-length*) *ind-start* *ind-offset*)
|
|
)
|
|
((:indent (or :for :while :if :else-if :else :do) :eol :indent)
|
|
)
|
|
(setq *indent* (offset-indentation *ind-offset* :resolve t))
|
|
(indent-macro-reject-left)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Simplify, remove old (:eol :indent)
|
|
;; This must be the last rule, to avoid not matching the
|
|
;; rules for fast calculation of indentation above
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indreduce nil
|
|
(> *ind-offset* c-prev-offset)
|
|
((:eol :indent))
|
|
)
|
|
|
|
|
|
(indinit (c-flow 0))
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; If
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indinit c-if-flow)
|
|
(indresolve :if
|
|
(and (< *ind-offset* *ind-start*)
|
|
(push c-flow c-if-flow)
|
|
(incf *indent* *base-indent*)
|
|
(incf c-flow)
|
|
)
|
|
)
|
|
(indresolve (:else-if :else)
|
|
(when c-if-flow
|
|
(while (< c-flow (car c-if-flow))
|
|
(incf *indent* *base-indent*)
|
|
(incf c-flow)
|
|
)
|
|
(or (eq *ind-token* :else-if) (pop c-if-flow))
|
|
)
|
|
(and (< *ind-offset* *ind-start*)
|
|
(incf *indent* *base-indent*)
|
|
(incf c-flow)
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; For/while/do
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indinit c-do-flow)
|
|
(indresolve (:for :while :do)
|
|
(if (eq *ind-token* :do)
|
|
(and (< *ind-offset* *ind-start*) (push c-flow c-do-flow))
|
|
(when (and c-do-flow (eq *ind-token* :while))
|
|
(while (< c-flow (car c-do-flow))
|
|
(incf *indent* *base-indent*)
|
|
(incf c-flow)
|
|
)
|
|
(pop c-do-flow)
|
|
)
|
|
)
|
|
(and (< *ind-offset* *ind-start*)
|
|
(incf *indent* *base-indent*)
|
|
(incf c-flow)
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Switch
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indresolve :switch
|
|
(setq c-case-flag nil)
|
|
)
|
|
(indresolve (:case :c-case)
|
|
(if (< *ind-offset* *ind-start*)
|
|
(or c-case-flag
|
|
(setq
|
|
*indent*
|
|
(+ (offset-indentation *ind-offset* :resolve t)
|
|
*base-indent*
|
|
)
|
|
)
|
|
)
|
|
(if c-case-flag
|
|
(and (= (decf c-cases) 0)
|
|
(decf *indent* *base-indent*)
|
|
)
|
|
(or *case-indent*
|
|
(decf *indent* *base-indent*)
|
|
)
|
|
)
|
|
)
|
|
(setq c-case-flag t)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Braces/flow control
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indresolve :flow
|
|
(incf *indent* *base-indent*)
|
|
)
|
|
(indresolve :obrace
|
|
(and (< *ind-offset* *ind-start*)
|
|
(incf *indent* *base-indent*)
|
|
)
|
|
)
|
|
(indresolve :cbrace
|
|
(decf *indent* *base-indent*)
|
|
(and *case-indent* c-case-flag
|
|
(decf *indent* *base-indent*)
|
|
(setq c-case-flag nil)
|
|
)
|
|
(and (not *offset*) (>= *ind-offset* *ind-start*)
|
|
(setq *offset* *ind-offset*)
|
|
)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Statements
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indresolve :stat
|
|
(when (< *ind-offset* *ind-start*)
|
|
(while (> c-flow 0)
|
|
(setq
|
|
*indent* (- *indent* *base-indent*)
|
|
c-flow (1- c-flow)
|
|
)
|
|
)
|
|
)
|
|
(and
|
|
*cont-indent*
|
|
(< *ind-offset* *ind-start*)
|
|
(> (+ *ind-offset* *ind-length*) *ind-start*)
|
|
(incf *indent* *base-indent*)
|
|
)
|
|
)
|
|
|
|
(indresolve :expression
|
|
(and
|
|
*cont-indent*
|
|
(zerop c-bra)
|
|
(> *indent* 0)
|
|
(< *ind-offset* *ind-start*)
|
|
(> (+ *ind-offset* *ind-length*) *ind-start*)
|
|
(incf *indent* *base-indent*)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Open
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(indresolve (:oparen :obrack)
|
|
(and (< *ind-offset* *ind-start*)
|
|
(setq *indent* (1+ (offset-indentation *ind-offset* :align t)))
|
|
)
|
|
)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Find a "good" offset to start parsing backwards, so that it should
|
|
;; always generate the same results.
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
(defun c-offset-indent (&aux char (point (point)))
|
|
;; Skip spaces forward
|
|
(while (member (setq char (char-after point)) indent-spaces)
|
|
(incf point)
|
|
)
|
|
(or (characterp char) (return-from c-offset-indent point))
|
|
|
|
;; Skip word chars
|
|
(when (alphanumericp char)
|
|
(while (and (setq char (char-after point)) (alphanumericp char))
|
|
(incf point)
|
|
)
|
|
(or (characterp char) (return-from c-offset-indent point))
|
|
|
|
;; Skip spaces forward
|
|
(while (member (setq char (char-after point)) indent-spaces)
|
|
(incf point)
|
|
)
|
|
(or (characterp char) (return-from c-offset-indent point))
|
|
)
|
|
|
|
;; don't include " or ' to avoid parsing strings "inverted"
|
|
(if (member char '(#\Newline #\" #\')) point (1+ point))
|
|
)
|
|
(compile 'c-offset-indent)
|
|
|
|
(defun c-should-indent (options)
|
|
(when (hash-table-p options)
|
|
;; check if previous line has extra spaces
|
|
(and (gethash :trim-blank-lines options)
|
|
(indent-clear-empty-line)
|
|
)
|
|
|
|
;; indentation disabled?
|
|
(and (gethash :disable-indent options)
|
|
(return-from c-should-indent)
|
|
)
|
|
|
|
(let*
|
|
(
|
|
(point (point))
|
|
(start (scan point :eol :left))
|
|
(char (char-before point))
|
|
offset
|
|
match
|
|
text
|
|
)
|
|
|
|
;; at the start of an empty file
|
|
(or (characterp char)
|
|
(return-from c-should-indent)
|
|
)
|
|
|
|
;; if at bol and should indent only when starting a line
|
|
(and (gethash :only-newline-indent options)
|
|
(return-from c-should-indent (= point start))
|
|
)
|
|
|
|
(and
|
|
(char= char #\;)
|
|
(gethash :newline-after-semi options)
|
|
(return-from c-should-indent t)
|
|
)
|
|
|
|
;; if one of these was typed, must check indentation
|
|
(and (member char '(#\{ #\} #\: #\] #\) #\#))
|
|
(return-from c-should-indent t)
|
|
)
|
|
|
|
;; at the start of a line
|
|
(and (= point start)
|
|
(return-from c-should-indent (gethash :newline-indent options))
|
|
)
|
|
|
|
;; if first character
|
|
(and (= point (1+ start))
|
|
(return-from c-should-indent t)
|
|
)
|
|
|
|
;; check if is the first non-blank character in a new line
|
|
(when
|
|
(and
|
|
(gethash :cont-indent options)
|
|
(= point (scan point :eol :right))
|
|
(alphanumericp char)
|
|
)
|
|
(setq offset (1- point))
|
|
(while
|
|
(and
|
|
(> offset start)
|
|
(member (char-before offset) indent-spaces)
|
|
)
|
|
(decf offset)
|
|
)
|
|
;; line has only one character with possible spaces before it
|
|
(and (<= offset start)
|
|
(return-from c-should-indent t)
|
|
)
|
|
)
|
|
|
|
;; check for keywords that change indentation
|
|
(when (alphanumericp char)
|
|
(setq offset (1- point))
|
|
(while
|
|
(and
|
|
(alphanumericp (char-before offset))
|
|
(> offset start)
|
|
)
|
|
(decf offset)
|
|
)
|
|
(setq
|
|
text (read-text offset (- point offset))
|
|
match (re-exec #.(re-comp "(case|else|while)\\w?\\>")
|
|
text)
|
|
)
|
|
(and
|
|
(consp match)
|
|
(return-from c-should-indent (<= (- (caar match) offset) 2))
|
|
)
|
|
)
|
|
)
|
|
)
|
|
;; Should not indent
|
|
nil
|
|
)
|
|
(compile 'c-should-indent)
|
|
|
|
|
|
(defun c-indent-check (syntax syntable options
|
|
&aux start point char left brace change)
|
|
(setq
|
|
point (point)
|
|
char (char-before point)
|
|
left point
|
|
brace (member char '(#\{ #\}))
|
|
)
|
|
|
|
(when
|
|
(and brace (gethash :newline-before-brace options))
|
|
(setq start (scan point :eol :left))
|
|
(while
|
|
(and
|
|
(> (decf left) start)
|
|
(member (char-before left) indent-spaces)
|
|
)
|
|
;; skip blanks
|
|
)
|
|
(when (> left start)
|
|
(replace-text left left (string #\Newline))
|
|
(c-indent syntax syntable)
|
|
(setq change t)
|
|
)
|
|
)
|
|
|
|
(when
|
|
(or
|
|
(and brace (not change) (gethash :newline-after-brace options))
|
|
(and (char= char #\;) (gethash :newline-after-semi options))
|
|
)
|
|
(setq left (point))
|
|
(replace-text left left (string #\Newline))
|
|
(goto-char (1+ left))
|
|
(c-indent syntax syntable)
|
|
)
|
|
)
|
|
|
|
(defun c-indent (syntax syntable)
|
|
(let*
|
|
(
|
|
(options (syntax-options syntax))
|
|
*base-indent*
|
|
*brace-indent*
|
|
*case-indent*
|
|
*label-dedent*
|
|
*cont-indent*
|
|
)
|
|
|
|
(or (c-should-indent options) (return-from c-indent))
|
|
|
|
(setq
|
|
*base-indent* (gethash :indentation options 4)
|
|
*brace-indent* (gethash :brace-indent options nil)
|
|
*case-indent* (gethash :case-indent options t)
|
|
*label-dedent* (gethash :label-dedent options t)
|
|
*cont-indent* (gethash :cont-indent options t)
|
|
)
|
|
|
|
(indent-macro
|
|
*c-mode-indent*
|
|
(c-offset-indent)
|
|
(gethash :emulate-tabs options)
|
|
)
|
|
|
|
(c-indent-check syntax syntable options)
|
|
)
|
|
)
|
|
(compile 'c-indent)
|
|
|
|
(defsyntax *c-mode* :main nil #'c-indent *c-mode-options*
|
|
;; All recognized C keywords.
|
|
(syntoken
|
|
(string-concat
|
|
"\\<("
|
|
"asm|auto|break|case|catch|char|class|const|continue|default|"
|
|
"delete|do|double|else|enum|extern|float|for|friend|goto|if|"
|
|
"inline|int|long|new|operator|private|protected|public|register|"
|
|
"return|short|signed|sizeof|static|struct|switch|template|this|"
|
|
"throw|try|typedef|union|unsigned|virtual|void|volatile|while"
|
|
")\\>")
|
|
:property *prop-keyword*)
|
|
|
|
;; Numbers, this is optional, comment this rule if xedit is
|
|
;; too slow to load c files.
|
|
(syntoken
|
|
(string-concat
|
|
"\\<("
|
|
;; Integers
|
|
"(\\d+|0x\\x+)(u|ul|ull|l|ll|lu|llu)?|"
|
|
;; Floats
|
|
"\\d+\\.?\\d*(e[+-]?\\d+)?[lf]?"
|
|
")\\>")
|
|
:icase t
|
|
:property *prop-number*
|
|
)
|
|
|
|
;; String start rule.
|
|
(syntoken "\"" :nospec t :begin :string :contained t)
|
|
|
|
;; Character start rule.
|
|
(syntoken "'" :nospec t :begin :character :contained t)
|
|
|
|
;; Preprocessor start rule.
|
|
(syntoken "^\\s*#\\s*\\w+" :begin :preprocessor :contained t)
|
|
|
|
;; Comment start rule.
|
|
(syntoken "/*" :nospec t :begin :comment :contained t)
|
|
|
|
;; C++ style comments.
|
|
(syntoken "//.*" :property *prop-comment*)
|
|
|
|
;; Punctuation, this is also optional, comment this rule if xedit is
|
|
;; too slow to load c files.
|
|
(syntoken "[][(){}/*+:;=<>,&.!%|^~?-][][(){}*+:;=<>,&.!%|^~?-]?"
|
|
:property *prop-punctuation*)
|
|
|
|
|
|
;; Rules for comments.
|
|
(syntable :comment *prop-comment* #'default-indent
|
|
;; Match nested comments as an error.
|
|
(syntoken "/*" :nospec t :property *prop-error*)
|
|
|
|
(syntoken "XXX|TODO|FIXME" :property *prop-annotation*)
|
|
|
|
;; Rule to finish a comment.
|
|
(syntoken "*/" :nospec t :switch -1)
|
|
)
|
|
|
|
;; Rules for strings.
|
|
(syntable :string *prop-string* #'default-indent
|
|
;; Ignore escaped characters, this includes \".
|
|
(syntoken "\\\\.")
|
|
|
|
;; Match, most, printf arguments.
|
|
(syntoken "%%|%([+-]?\\d+)?(l?[deEfgiouxX]|[cdeEfgiopsuxX])"
|
|
:property *prop-format*)
|
|
|
|
;; Ignore continuation in the next line.
|
|
(syntoken "\\\\$")
|
|
|
|
;; Rule to finish a string.
|
|
(syntoken "\"" :nospec t :switch -1)
|
|
|
|
;; Don't allow strings continuing in the next line.
|
|
(syntoken ".?$" :begin :error)
|
|
)
|
|
|
|
;; Rules for characters.
|
|
(syntable :character *prop-constant* nil
|
|
;; Ignore escaped characters, this includes \'.
|
|
(syntoken "\\\\.")
|
|
|
|
;; Ignore continuation in the next line.
|
|
(syntoken "\\\\$")
|
|
|
|
;; Rule to finish a character constant.
|
|
(syntoken "'" :nospec t :switch -1)
|
|
|
|
;; Don't allow constants continuing in the next line.
|
|
(syntoken ".?$" :begin :error)
|
|
)
|
|
|
|
;; Rules for preprocessor.
|
|
(syntable :preprocessor *prop-preprocessor* #'default-indent
|
|
;; Preprocessor includes comments.
|
|
(syntoken "/*" :nospec t :begin :comment :contained t)
|
|
|
|
;; Ignore strings and constants in the same line and finishes table
|
|
;; This is kind hackish, but must be done because the current parser
|
|
;; will not flag eol. Maybe it could be extended to properly handle
|
|
;; and have an internal flag to tell it to pass again if there
|
|
;; is a regex that can match eol on an empty string.
|
|
;; A test is already done (but at compile time) to not allow patterns
|
|
;; that match an empty string (but allow patterns matching
|
|
;; bol, eol or both on an empty string).
|
|
(syntoken "\"([^\\\"]|\\\\.)*\"$" :property *prop-string* :switch -1)
|
|
(syntoken "'([^']|\\\\.)*'$" :property *prop-constant* :switch -1)
|
|
|
|
;; Ignore strings and constants in the same line
|
|
(syntoken "\"([^\\\"]|\\\\.)*\"" :property *prop-string*)
|
|
(syntoken "'([^']|\\\\.)*'" :property *prop-constant*)
|
|
|
|
;; Ignore lines finishing with a backslash.
|
|
(syntoken "\\\\$")
|
|
|
|
;; multiline strings
|
|
(syntoken "\"" :nospec t :begin :string)
|
|
|
|
;; multiline constants
|
|
(syntoken "'" :nospec t :begin :character)
|
|
|
|
;; C++ style comments
|
|
(syntoken "//.*$" :property *prop-comment* :switch -1)
|
|
|
|
;; Return to previous state if end of line found.
|
|
(syntoken ".?$" :switch -1)
|
|
)
|
|
|
|
(syntable :error *prop-error* nil
|
|
(syntoken "^.*$" :switch -2)
|
|
)
|
|
|
|
;; You may also want to comment this rule if the parsing is
|
|
;; noticeably slow.
|
|
(syntoken "\\c" :property *prop-control*)
|
|
)
|