(defgroup gas nil
"Mode for editing gas syntax assembler code."
:link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
:group 'languages)
(defcustom gas-comment-char ?\;
"The comment start character assumed by gas mode."
:type 'character
:group 'gas)
(defcustom gas-opcode-column 14
"The opcode column."
:type 'integer
:group 'gas)
(defcustom gas-argument-column 20
"The column for the arguments (if any)."
:type 'integer
:group 'gas)
(defcustom gas-comment-column 36
"The column where end of line asm comments go to."
:type 'integer
:group 'gas)
(defcustom gas-comment-char-starts-comment nil
"Always jump to comment column when a variable `gas-comment-char' is typed.
It t, starts/expands a comment if appropriate. When
unset (nil), use `gas-comment-dwim' (usually bound to M-;) to get there."
:type 'boolean
:group 'gas)
(defcustom gas-indent-current-field-only nil
"If nil, 'indent' indents all fileds on the current line.
Else only the current field is affected."
:type 'boolean
:group 'gas)
(setq gas-preserve-trailing-whitespace nil)
(defcustom gas-enable-symbol-highlight t
"Enable symbol recognition and highlighing.
When t and if point is on a symbol, some limited parsing data is
collected and all occurences of this symbol in the buffer get
highlighted according to the results (defined, global, etc.). As
long as a symbol is highlighted, `forward-sexp' and `backward-sexp'
move to the next/previous occurence of the same symbol in the
same buffer."
:type 'boolean
:group 'gas)
(defcustom gas-use-C-passthrough t
"When true, C passthrough comments are recognized.
This kind of comment is introduced by the starting sequence
\"/*C\" and is meant to be processed later by an external
program \(see the introducing comment in gas-mode.el for an
example) into C source code. Within these passthrough-comments,
the combination /# ... #/ is available for nested comments which
will be later changed into real C comments (/* ... */) by the
same external program."
:type 'boolean
:group 'gas)
(defcustom gas-defun-regexp "\n\\([;#].*\\|.*[*]/[ \t]*\\|\\|[ \t]*\\|[ \t]+\\..*\\)\n\\([^ \t\n;]+:\\)"
"Regexp used to recognize the beginning of a defun.
The default value describes a line which is either empty, a
full-line left-justified comment or a directive, followed by a
line starting with a label. Note that the character \";\" in the
regexp will be replaced by the actual comment character described
by variable `gas-comment-char'."
:type 'regexp
:group 'gas)
(defcustom gas-defun-regexp-subexp 2
"The subexp in `gas-defun-regexp to jump to."
:type 'integer
:group 'gas)
(defcustom gas-C-indent 3
"Indent to use with C style comments."
:group 'gas
:type 'integer)
(defcustom gas-C-comment-end-column 0
"Where to indent a C comment end (\"*/\") if it starts a line."
:group 'gas
:type 'integer)
(defcustom gas-symbol-highlight-delay 0.5
"After this many seconds symbols get highlighted.
Number of seconds of idle time (a float) to wait before a symbol
gets highlighted."
:group 'gas
:type 'float)
(defgroup gas-faces nil
"Faces used by gas-mode."
:group 'gas)
(defface gas-builtin
'((((class color) (background light)) (:foreground "maroon"))
(t (:foreground "yellow")))
"Face to use for Gas buitins."
:group 'gas-faces)
(defface gas-symbol-ok
'((((class color) (background light)) (:background "#e0ffe0"))
(((class color) (background dark)) (:background "#001f00"))
(t (:foreground "yellow" :background "blue")))
"Face to use for symbols where exactly 1 definition was found."
:group 'gas-faces)
(defface gas-symbol-error
'((((class color) (background light))
(:background "#ffffe8" :foreground "red" :weight bold))
(((class color) (background dark))
(:background "#181800" :foreground "red" :weight bold))
(t (:foreground "yellow" :background "red")))
"Face to use when highlighting symbols with more than 1 definition."
:group 'gas-faces)
(defface gas-symbol-global
'((((class color) (background light)) (:background "#d0f8ff"))
(((class color) (background dark)) (:background "#00383f"))
(t (:foreground "yellow" :background "blue")))
"Face to use when highlighting global symbols."
:group 'gas-faces)
(defface gas-symbol-undef
'((((class color) (background light))
(:background "#ffffe8" :foreground "maroon"))
(((class color) (background dark))
(:background "#181800"))
(t (:foreground "yellow" :background "red")))
"Face to use for symbols defined as global when no definition
was found."
:group 'gas-faces)
(defface gas-symbol-global-undef
'((((class color) (background light))
(:background "#d0f8ff" :foreground "red"))
(((class color) (background dark))
(:background "#002840" :foreground "red"))
(t (:foreground "yellow" :background "red")))
"Face to use for symbols when no definition is found."
:group 'gas-faces)
(defface gas-passthrough-code
'((((class color) (background light)) (:foreground "magenta4"))
(((class color) (background dark)) (:foreground "magenta2"))
(t (:foreground "magenta1" :background "cyan")))
"Marks passthrough code."
:group 'gas-faces)
(defface gas-passthrough-comment
'((((class color) (background light)) (:foreground "turquoise4"))
(((class color) (background dark)) (:foreground "turquoise2"))
(t (:foreground "turquoise1")))
"Marks passthrough comments."
:group 'gas-faces)
(defvar gas-builtin-face 'gas-builtin)
(defvar gas-symbol-ok-face 'gas-symbol-ok)
(defvar gas-symbol-error-face 'gas-symbol-error)
(defvar gas-symbol-global-face 'gas-symbol-global)
(defvar gas-symbol-undef-face 'gas-symbol-undef)
(defvar gas-symbol-global-undef-face 'gas-symbol-global-undef)
(defvar gas-passthrough-code-face 'gas-passthrough-code)
(defvar gas-passthrough-comment-face 'gas-passthrough-comment)
(defconst gas-max-lines-in-cache 500
"Maximum number of parsed lines in cache.
I don't expect much impact from this on performance (the line
cache is emptied on any buffer change anyway). Play around with
this value if you suspect memory may be your problem.")
(defconst gas-max-labels-in-cache 300
"Maximum number of symbols in highlight cache.
Reduce this if memory footprint grows too high (very unlikely).")
(defconst gas-re-sym "\\([_$A-Za-z][_0-9$A-Za-z]*\\)"
"Regexp defining a valid symbol as a subexpression.")
(defconst gas-skip-sym "_0-9$A-Za-z"
"The valid characters for a symbol as used in `skip-chars-*' functions.")
(defconst gas-re-nosym"[^_0-9$A-Za-z]"
"Regexp defining the character set not allowed in a symbol.")
(defvar gas-mode-syntax-table
(let ((st (make-syntax-table)))
(modify-syntax-entry ?\n "> b" st)
(modify-syntax-entry ?. "_" st)
(modify-syntax-entry ?/ ". 124b" st)
(modify-syntax-entry ?* ". 23" st)
st)
"Syntax table used while in gas mode.")
(defvar gas-mode-abbrev-table nil
"Abbrev table used while in Gas mode.")
(define-abbrev-table 'gas-mode-abbrev-table ())
(defvar gas-mode-map
(let ((map (make-sparse-keymap)))
(define-key map ":" 'gas-colon)
(define-key map "\M-;" 'gas-comment)
(define-key map ";" 'gas-comment-char)
(define-key map "#" 'gas-hash)
(define-key map (kbd "<S-iso-lefttab>") 'gas-indent-backward)
(define-key map "\C-c;" 'comment-region)
(define-key map "\C-j" 'newline-and-indent)
(define-key map "\C-m" 'newline-and-indent)
map)
"Keymap for Gas mode.")
(defconst gas-equ (regexp-opt '(".equ" ".set" ".eqv" ".equiv" ".set"))
"Regex matching all operators which define a symbol.")
(defconst gas-indents
'((C-comment-end . gas-C-comment-end-column)
(C-comment-start . 0)
(C-comment . gas-get-C-relative-indent)
(cpp-macro-def . 0)
(cpp-argument . 0)
(label . 0)
(opcode . gas-opcode-column)
(argument . gas-argument-column)
(asm-comment . gas-get-asm-comment-column))
"Fields and their indents.
The cdr (the indent) may either be a number, a symbol bound to a
number, or a symbol bound to a function yielding the value.")
(defconst gas-parse-sequences
'(
(starting-asm-line
. ((cpp-macro-def . cpp-macro-def)
(label . label)
(empty-label . label)
(asm-comment . asm-comment)
(opcode . opcode)))
(label
. ((opcode . opcode)
(asm-comment . asm-comment)
(garbage . garbage)))
(opcode
. ((asm-comment . asm-comment)
(argument . argument)
(eol-ws . eol-ws)
(garbage . garbage)))
(argument
. ((asm-comment . asm-comment)
(eol-ws . eol-ws)
(garbage . garbage)))
(asm-comment
. ((eol-ws . eol-ws)))
(cpp-argument
. ((eol-ws . eol-ws)
(garbage . garbage)))
(cpp-macro-def
. ((cpp-argument . cpp-argument)
(garbage . garbage)))
(cpp-argument
. ((eol-ws)
(garbage . garbage)))
(C-comment-start
. ((C-comment . C-comment)))
(starting-within-C-comment
. ((C-comment-end . C-comment-end)
(C-comment . C-comment)))
(C-comment
. ((C-comment-end . C-comment-end)
(eol-ws . eol-ws)
(garbage . garbage)))
(garbage
. ((eol-ws . eol-ws)))
(eol-ws
. fini))
"Mapping from the field type we're on to the field types to check next.
car - type of field we're on (IOW, the one just handled)
cdr - ordered list of (first (most special) check first):
car - token to match
cdr - next field type iff match." )
(defconst gas-patterns
'((cpp-macro-def
"[ \t]*\\(\\(#[^ \t\n]*\\)\\)"
(0 1 1 1)
"/[*]")
(cpp-argument
"[ \t]*\\(\\([ \t]*\\([^ \t\n]\\)+\\)+\\)"
(0 1 1 1)
"/[*]")
(label
"[ \t]*\\([^ :\t\n]+:\\)"
(0 1 1 1)
"/[*]\\|;")
(opcode
"[ \t]*\\([^ \t\n]+\\)"
(0 1 1 1)
"/[*]\\|;")
(argument
"[ \t]*\\(\\([ \t]*\\([^ \t\n]\\)+\\)+\\)"
(0 1 1 1)
"/[*]\\|;")
(garbage
"[ \t]*\\(\\([ \t]*\\([^ \t\n]\\)+\\)+\\)"
(0 1 1 1)
"/[*]\\|;")
(asm-comment
"[ \t]*\\(\\(;+\\)\\([ \t]*[^ \t\n]+\\)*\\)"
(0 1 1 1)
nil)
(C-inline-comment
"[ \t]*\\(/[*].*?[*]/\\)"
(0 1 1 1)
nil)
(C-comment-start
"[ \t]*\\(/[*]C?\\)"
(0 1 1 1)
nil)
(empty-label
" [ \t]*"
(0 0 0 nil)
"/[*]\\|;")
(C-comment-end
"[ \t]*\\([*]/\\)"
(0 1 1 1)
nil)
(C-comment
"[ \t]*\\(\\([ \t]*[^ \t]+\\)*\\)"
(0 1 1 1)
"[*]/")
(eol-ws
"[ \t]+\\( ?\\)"
(0 1 1 1)
nil))
"An alist of parse patterns.
Each entry holds 4 elements (SYMBOL REGEXP SUBEXPS TERMINATE-RE):
SYMBOL - designator (a symbol) under which it will be
referenced.
REGEXP - the regexp to match against,
SUBEXPS - a list (BEG-COL TEXT-COL END-COL END-OF-FIELD) of at
which subexpression of REGEXP to find beg-col, text-col,
end-col (see `gas-parse-line-really') and the end of the
field,
TERMINATE-RE - a regexp, the start of which (if it matches and
if outside a \"..\" string) unconditionally terminates the
field.
Every occurence of the character \";\" in both regexps is
replaced by variable `gas-comment-char' before use.")
(defconst gas-elmt-types
'(type subtype beg-col text-col end-col text modified)
"The elements of a gas syntax field.")
(defconst gas-builtin-keywords (concat "^\\(\\(\\sw\\|\\s_\\)+:?\\)?[ \t]+\\("
(regexp-opt '(
".Abort" ".ABORT" ".Align" ".Altmacro" ".Ascii" ".Asciz"
".Balign" ".Byte" ".Comm"
".Data" ".Def" ".Desc" ".Dim" ".Double" ".Eject"
".Else" ".Elseif" ".End" ".Endef" ".Endfunc" ".Endif"
".Equ" ".Equiv" ".Eqv"
".Err" ".Error" ".Exitm" ".Extern" ".Fail"
".File" ".Fill" ".Float" ".Func"
".Global" ".Hidden" ".hword" ".Ident"
".If" ".ifb" ".ifc" ".ifeq" ".ifeqs"
".ifge" ".ifle" ".ifgt" ".iflt"
".ifnb" ".ifnc" ".ifndef" ".ifdef" ".ifnotdef" ".ifne" ".ifnes"
".Incbin" ".Include" ".Int"
".Internal" ".Irp" ".Irpc" ".Lcomm" ".Lflags" ".Line"
".Linkonce" ".List" ".Ln"
".Long" ".Macro" ".MRI" ".Noaltmacro"
".Nolist" ".Octa" ".Org" ".P2align" ".PopSection" ".Previous"
".Print" ".Protected" ".Psize" ".Purgem" ".PushSection"
".Quad" ".Rept" ".Sbttl" ".Scl" ".Section" ".Set" ".Short"
".Single" ".Size" ".Skip" ".Sleb128" ".Space" ".Stab" ".String"
".Struct" ".SubSection" ".Symver" ".Tag" ".Text" ".Title" ".Type"
".Uleb128" ".Val" ".Version" ".VTableEntry" ".VTableInherit"
".Warning" ".Weak" ".Weakref" ".Word" ".Deprecated"))
"\\)[ \t\n]"))
(defvar gas-font-lock-keywords
(append
(list
'(gas-return-passthrough-code-hi . (0 gas-passthrough-code-face t))
'(gas-return-passthrough-comment-hi . (0 gas-passthrough-comment-face t))
'(gas-return-gas-hi-ok . (0 gas-symbol-ok-face t))
'(gas-return-gas-hi-global . (0 gas-symbol-global-face t))
'(gas-return-gas-hi-error . (0 gas-symbol-error-face t))
'(gas-return-gas-hi-undef . (0 gas-symbol-undef-face t))
'(gas-return-gas-hi-global-undef . (0 gas-symbol-global-undef-face t))
(list gas-builtin-keywords 3 'gas-builtin-face)
'("^\\(\\(\\sw\\|\\s_\\)+\\)\\>:?" 1 font-lock-function-name-face))
cpp-font-lock-keywords)
"Additional expressions to highlight in gas mode.")
(put 'gas-mode 'font-lock-defaults '(gas-font-lock-keywords))
(defun gas-mode ()
"Major mode for editing assembler code.
Commands:
\(Some of these commands may exhibit slightly different behaviour if point
is on a C syntax line.)
\\[indent-for-tab-command] indent the field(s) point is on. If it already is in its position,
move on to the next field on the line.
\\[gas-indent-backward] Move to the previous field.
\\[gas-comment] When no region is active, starts a comment sequence:
- If a comment is present and point is not at its start, jump there.
- Else start a comment. If there already is one, increase its comment level.
What that is and what it does, is best explained when you try
it out: Move to en empty line, then type \\[gas-comment] and
then repeatedly \\[gas-comment] or \\[gas-comment-char].
Calls \\[gas-comment-dwim] (see below) if the region is active.
\\[forward-sexp] If you're on a highlighted symbol, jump to its next
occurence. Else do `forward-sexp' like in text mode.
\\[backward-sexp] If you're on a highlighted symbol, jump to its previous
occurence. Else do `backward-sexp' like in text mode.
\\[gas-comment-dwim] If the region starts at the leading white space
before a comment, all full-line comments in region will be
removed. If the region starts on a comment, comments will be
removed, but comments with leading white space will be left
untouched. Else insert triple variable `gas-comment-char's before all
lines in region.
\\[fill-paragraph] beautifies the paragraph around
point, i.e. it adjusts all assembly syntax fields to their
standard positions.
\\[indent-region] beautifies the region, i.e., adjusts all fields in region.
The following characters have a special meaning in special cases:
\\[gas-colon] if it terminates a label: outdent the label and
move to opcode column. Else, just insert \\[gas-colon] as usual.
\\[gas-comment-char] The value which introduces an asm style comment.
If typed in in a row after \\[gas-comment], behaves as an alias to \\[gas-comment]. else
just insert \\[gas-comment-char].
Can be customized to always act as alias (`gas-comment-char-starts-comment').
Alternatively, you may use a File Variable to make it buffer local
(which allows you to use different syntaxes in the same session).
Note: Setting it to a value other than ?\; has not yet been tested.
\\[gas-hash] If it starts a preprocessor directive: Outdent it to first
column.
Customization: Entry on this mode runs `gas-mode-hook'.
The customization group is called 'gas'.
Special commands:
\\{gas-mode-map}"
(interactive)
(kill-all-local-variables)
(setq mode-name "gas")
(setq major-mode 'gas-mode)
(setq local-abbrev-table gas-mode-abbrev-table)
(setq gas-comment-string (string gas-comment-char))
(setq gas-comment-re (regexp-quote gas-comment-string))
(set (make-local-variable 'indent-line-function) 'gas-indent)
(set (make-local-variable 'indent-region-function) 'gas-indent-region)
(set (make-local-variable 'forward-sexp-function) 'gas-forward-sexp)
(set (make-local-variable 'fill-paragraph-function) 'gas-fill-paragraph)
(set (make-local-variable 'font-lock-defaults) '(gas-font-lock-keywords))
(set (make-local-variable 'gas-local-comment-char) gas-comment-char)
(set (make-local-variable 'beginning-of-defun-function)
'gas-beginning-of-defun)
(set (make-local-variable 'end-of-defun-function)
'gas-end-of-defun)
(set (make-local-variable 'font-lock-keywords-case-fold-search) t)
(use-local-map (nconc (make-sparse-keymap) gas-mode-map))
(local-set-key (vector gas-comment-char) 'gas-comment-char)
(set-syntax-table (make-syntax-table gas-mode-syntax-table))
(modify-syntax-entry gas-comment-char "< b")
(dolist (var '(gas-line-cache gas-globals-cache gas-locals-cache
gas-hi-valid gas-hi-sym-list gas-hi-global gas-hi-undef
gas-hi-error gas-hi-ok gas-changed gas-highlights gas-hi
gas-pass-code-hi gas-pass-comment-hi gas-symbol-timer
gas-highlights-error gas-symbol-highlight-beg
gas-symbol-highlight-end gas-doing-comment
after-change-functions))
(set (make-local-variable var) nil))
(add-to-list 'after-change-functions 'gas-after-change)
(add-hook 'pre-command-hook 'gas-symbol-pre-command)
(gas-start-symbol-timer)
(run-mode-hooks 'gas-mode-hook)
(gas-symbol-highlight))
(defun gas-dbg ()
"You won't need this unless you're debugging `gas-mode'."
(interactive)
(setq debug-on-error t)
(setq debug-items '(indent))
(switch-to-buffer-other-frame "*Messages*")
(switch-to-buffer-other-frame "*scratch*")
(info "Elisp")
(switch-to-buffer-other-frame "*scratch*")
(find-file-other-frame "x.S")
(switch-to-buffer-other-frame "x.S")
(switch-to-buffer-other-frame (get-buffer-create "*gas-dbg*"))
(find-file-other-frame "gas-mode.el")
(column-number-mode t)
(switch-to-buffer-other-frame "gas-mode.el"))
(defun dmsg (condition &rest args)
"Helper function, outputs debug messages into a buffer of their own.
If DEBUG-ITEMS (a symbol or a list) has a non-empty
intersection with CONDITION (a symbol or a list)', apply `format'
to ARGS and insert the result at the end of the buffer
`gas-dbg' (which is created if non-existing).
Currently defined symbols are: 'wip (work in progress),
'hi (highlighting), 'parser, 'cursor, 'sym (symbol highlighting),
'cursor."
(when (and (boundp 'debug-items) debug-items)
(when (not (listp condition))
(setq condition (list condition)))
(when (not (listp debug-items))
(setq debug-items (list debug-items)))
(when (or (eq condition '(all))
(catch 'found
(dolist (c condition)
(when (member c debug-items)
(throw 'found t)))))
(let ((contents (apply 'format args)))
(save-current-buffer
(set-buffer (get-buffer-create "*gas-dbg*"))
(goto-char (point-max))
(newline)
(insert contents)
(goto-char (point-max))
)))))
(defun gas-change-comment-regexp (str)
"Return STR with all \";\"s replaced by (regexp-quote variable `gas-comment-char')."
(when str
(setq str (replace-regexp-in-string
".*\\[[^]]*\\(;\\)" gas-comment-string str t t 1))
(replace-regexp-in-string ";" (regexp-quote gas-comment-string) str t t)))
(defun gas-change-comment-string (str)
"Return STR with all \";\"s replaced by variable `gas-comment-char'."
(when str
(replace-regexp-in-string ";" gas-comment-string str t t)))
(defun gas-set-patterns (comment-char)
"Replace `gas-patterns' by a copy, replacing ?\; by COMMENT-CHAR.
Also sets variable `gas-comment-char' to COMMENT-CHAR."
(set (make-local-variable 'gas-comment-char) comment-char)
(set (make-local-variable 'gas-comment-string) (string gas-comment-char))
(set (make-local-variable 'gas-comment-re) (regexp-quote gas-comment-string))
(kill-local-variable 'gas-patterns)
(let (result)
(dolist (pattern gas-patterns)
(add-to-list 'result
(list
(car pattern)
(gas-change-comment-regexp (nth 1 pattern))
(nth 2 pattern)
(gas-change-comment-regexp (nth 3 pattern)))))
(set (make-local-variable 'gas-patterns) result)
(dolist (sym '(gas-defun-regexp))
(kill-local-variable sym)
(let ((result (gas-change-comment-regexp (eval sym))))
(set (make-local-variable sym) result)))))
(defun gas-after-change (beg end len)
"Invalidate saved parser state.
Argument BEG BEG, END, and LEN, athough saved in `gas-changed' for debugging purposes, are not used."
(setq gas-globals-cache nil)
(setq gas-hi-valid nil)
(setq gas-locals-cache nil)
(setq gas-changed (append gas-changed (list (current-buffer) beg end len))))
(defun gas-symbol-pre-command()
(setq gas-hi nil))
(defun gas-start-symbol-timer (&optional stop)
"Schedule a timer for symbol highlighting (if not already scheduled).
Optional STOP, if non-nil, means remove from schedule."
(if (and stop gas-symbol-timer)
(progn
(cancel-timer gas-symbol-timer)
(setq gas-symbol-timer nil))
(unless (and gas-symbol-timer
(memq gas-symbol-timer timer-idle-list))
(setq gas-symbol-timer
(run-with-idle-timer gas-symbol-highlight-delay
t
'gas-symbol-highlight-maybe)))))
(defun gas-return-passthrough-hi (pos what)
"Return next passthrough match (if any).
See highlight.el for POS and WHAT."
(let ((curpoint (point)))
(catch 'found
(dolist (match what)
(when (and (< curpoint (cdr match))
(> pos (car match)))
(when (< curpoint (car match))
(goto-char (car match)))
(when (re-search-forward ".+" (min pos (cdr match)) t)
(dmsg 'hi "match: %s, data: '%s'" match (match-string 0))
(throw 'found t)))
(when (>= (car match) pos)
(throw 'found nil))))))
(defun gas-return-passthrough-code-hi (pos)
"Return next passthrough code match (if any).
See highlight.el for documentation on POS."
(gas-return-passthrough-hi pos gas-pass-code-hi))
(defun gas-return-passthrough-comment-hi (pos)
"Return next passthrough comment match (if any).
See highlight.el for documentaion on POS ."
(gas-return-passthrough-hi pos gas-pass-comment-hi))
(defun gas-passthrough-highlight ()
"Compute blocks of C passthroughs to be highlighted."
(goto-char (point-min))
(setq gas-pass-code-hi nil)
(setq gas-pass-comment-hi nil)
(let (beg end limit)
(while (and (re-search-forward "/[*]C" nil t)
(setq beg (point))
(setq limit
(save-excursion
(and (re-search-forward "[*]/" nil t)
(- (point) 2)))))
(while (and beg
(setq end (and (re-search-forward "/#" limit t)
(point))))
(add-to-list 'gas-pass-code-hi (cons beg (- end 2)) t)
(forward-char 2)
(when
(setq beg (and (re-search-forward "#/" limit t) (point)))
(add-to-list 'gas-pass-comment-hi (cons (- end 2) beg) t)))
(when beg
(add-to-list 'gas-pass-code-hi (cons beg limit) t)))))
(defun gas-return-highlight (pos hi-list)
"Called through the gas-return-gas-hi-* functions by highlight.el.
If there's a match of POS against one of the entries in HI-LIST,
return match data. Else, return nil."
(when gas-hi-valid
(dmsg 'hi "gas-return-highlight, pos: %s, point: %s, list: %s" pos (point) hi-list)
(catch 'found
(dolist (match hi-list)
(if (< (car match) pos)
(when (>= (car match) (point))
(dmsg 'hi "gas-return-highlight, match: %s" match)
(goto-char (car match))
(throw 'found (re-search-forward ".+" (min pos (cadr match)) t)))
(dmsg 'hi "gas-return-highlight: nope.")
(throw 'found nil)
nil)))))
(defun gas-return-gas-hi-global (pos)
"Check POS against the entries in the list `gas-hi-global'.
Called by highlight.el."
(gas-return-highlight pos gas-hi-global))
(defun gas-return-gas-hi-undef (pos)
"Check POS against the entries in the list `gas-hi-undef'.
Called by highlight.el."
(gas-return-highlight pos gas-hi-undef))
(defun gas-return-gas-hi-error (pos)
"Check POS against the entries in the list `gas-hi-error'.
Called by highlight.el."
(gas-return-highlight pos gas-hi-error))
(defun gas-return-gas-hi-ok (pos)
"Check POS against the entries in the list `gas-hi-ok'.
Called by highlight.el."
(gas-return-highlight pos gas-hi-ok))
(defun gas-return-gas-hi-global-undef (pos)
"Check POS against the entries in the list `gas-hi-undef'.
Called by highlight.el."
(gas-return-highlight pos gas-hi-global-undef))
(defun gas-qualify-symbol (sym-re slist lflags)
"The common part of `gas-scan-global-symbol and `gas-scan-local-symbol'.
Gets called with point on a line where a label match may be
found. It expects SYM-RE to be a regexp describing the
label. Adds on match the match it finds to SLIST (a symbol bound
to a list of matches) and the kind of match (as a symbol, like
'def for a definition or 'duplicate for a duplicate definition)
to LFLAGS."
(save-excursion
(let ((eol (line-end-position))
(bol (line-beginning-position)))
(setq case-fold-search nil)
(if (gas-C-comment-p)
(progn
(backward-char)
(while (re-search-forward
(format "%s\\(%s\\)\\(%s\\|$\\)"
gas-re-nosym sym-re gas-re-nosym)
eol t)
(let ((beg (match-beginning 1))
(end (match-end 1)))
(when (gas-C-passthrough-code-p)
(add-to-list lflags 'C-ref t)
(add-to-list slist (list 'C-ref beg end) t)))))
(let* ((fields (gas-parsed))
(lbl (gas-nth 'text 'label fields))
(arg (gas-nth 'text 'argument fields))
(type 'ref)
(argno 0))
(when (and lbl
(string-match (concat "^" sym-re ":?$") lbl))
(setq nlabels (1+ nlabels))
(if (member 'def (eval lflags))
(add-to-list lflags 'duplicate t)
(add-to-list lflags 'def t))
(move-to-column 0)
(looking-at "[^:]+:")
(add-to-list slist
(list 'def (match-beginning 0) (match-end 0)) t))
(when arg
(move-to-column (- (gas-nth 'text-col 'argument fields) 1))
(let ((type 'ref)
(eo-arg (+ bol (gas-nth 'end-col 'argument fields) 2))
(opcode (gas-nth 'text 'opcode fields)))
(while (re-search-forward
(format "%s\\(%s\\)\\(%s\\|$\\)"
gas-re-nosym sym-re gas-re-nosym)
eo-arg t)
(let ((beg (match-beginning 1))
(end (match-end 1)))
(if (and (= argno 0)
(string-match gas-equ opcode))
(progn
(setq type 'def)
(if (member 'def (eval lflags))
(add-to-list lflags 'duplicate t)
(add-to-list lflags 'def t)))
(add-to-list lflags 'ref t))
(when (string-match ".global" opcode)
(add-to-list lflags 'global t))
(add-to-list slist (list type beg end) t)
(setq argno (1+ argno))))))))))
(end-of-line))
(defun gas-scan-global-symbol (sym)
"Scan the buffer vor valid occurences of the global symbol SYM.
Called by `gas-symbol-highlight'."
(goto-char (point-min))
(let* (sym-list
flags
qualifiers
(nlabels 0)
(sym-re (regexp-quote sym))
(re (format "\\(^\\|%s\\)\\(%s\\)\\(%s\\|$\\)"
gas-re-nosym sym-re gas-re-nosym)))
(while (re-search-forward re nil t)
(when (input-pending-p)
(throw 'event-abort nil))
(goto-char (match-beginning 2))
(dmsg 'sym "global: qualified1: %s" sym-list)
(gas-qualify-symbol sym-re 'sym-list 'flags))
(dmsg 'sym "global: sym-list: %s" sym-list)
(unless (equal sym-list '(nil))
(list sym flags sym-list))))
(defun gas-scan-local-symbol (orig-sym)
"Scan the buffer vor valid occurences of the local symbol SYM.
Called by `gas-symbol-highlight'.
Argument ORIG-SYM is the complete symbol (as written)."
(when (string-match "^\\(.*\\)\\([:bf]\\)" orig-sym)
(let* ((search-lo (point-min))
search-mid
(search-hi (point-max))
(sym (match-string 1 orig-sym))
(sym-kind (match-string 2 orig-sym))
(sym-re (regexp-quote sym))
(lbl-re (concat "^\\(" sym-re ":" "\\)" ))
(nlabels 0)
sym-list
flags
qualifiers
searches)
(save-excursion
(when (equal sym-kind "b")
(re-search-backward lbl-re nil t))
(when (re-search-backward lbl-re nil t)
(setq search-lo (match-end 1)))
(goto-char search-lo)
(when (re-search-forward lbl-re nil t)
(setq search-mid (match-beginning 1)))
(when (re-search-forward lbl-re nil t)
(setq search-hi (match-beginning 1))))
(let ((lo search-hi)
(hi search-lo)
(search-params
(if search-mid
(list
(list (concat sym-re "f") search-lo search-mid)
(list (concat sym-re ":")
search-mid (+ search-mid (length sym) 2))
(list (concat sym-re "b") search-mid search-hi))
(list
(list (concat sym-re "f") search-lo search-hi)
(list (concat sym-re "b") search-lo search-hi)))))
(dolist (param search-params)
(when (input-pending-p)
(throw 'event-abort nil))
(let* ((sym-re (car param))
(search-re (concat gas-re-nosym
"\\(" sym-re "\\)" gas-re-nosym))
(limit (caddr param)))
(goto-char (- (cadr param) 1))
(while (and (< (point) limit)
(re-search-forward search-re limit t))
(setq lo (min lo (match-beginning 1)))
(setq hi (max hi (match-end 1)))
(goto-char (match-beginning 1))
(gas-qualify-symbol sym-re 'sym-list 'flags))))
(dmsg 'sym "local: sym-list: %s" sym-list)
(unless (equal sym-list '(nil))
(list sym lo hi flags sym-list))))))
(defun gas-sym-invalidate ()
"Invalidate all symbol scan results."
(setq gas-hi-sym-list nil)
(setq gas-hi-global nil)
(setq gas-hi-global-undef nil)
(setq gas-hi-undef nil)
(setq gas-hi-error nil)
(setq gas-hi-ok nil))
(defun gas-symbol-highlight ()
"Get symbol point is on, look for match and highlight it.
For use with the idle timer."
(save-match-data
(save-excursion
(gas-passthrough-highlight))
(save-excursion
(let* ((fields (gas-parsed))
(curpoint (point))
(case-fold-orig case-fold-search)
(pointpos (gas-get-pointpos fields))
sym-list)
(catch 'event-abort
(dmsg 'hi "pointpos=%s, fields=%s" pointpos fields)
(skip-chars-backward gas-skip-sym)
(when (member (car pointpos) '(label argument C-comment))
(setq case-fold-search nil)
(setq sym-list
(catch 'found
(if (and (not (eq (car pointpos) 'C-comment))
(looking-at (concat "\\(\\([0-9]+\\)\\([bf:]\\)\\)"
gas-re-nosym)))
(let ((lbl (match-string 2)))
(dolist (entry gas-locals-cache)
(when (input-pending-p)
(throw 'event-abort nil))
(and (equal (car entry) lbl)
(>= (nth 1 entry) curpoint)
(<= (nth 2 entry) curpoint)
(dolist (sym (nthcdr 3 entry))
(and (>= (nth 1 entry) curpoint)
(<= (nth 2 entry) curpoint)
(throw 'found (nthcdr 3 entry)))))
nil)
(let ((entry (gas-scan-local-symbol (match-string 1))))
(when entry
(when (> (length gas-locals-cache)
gas-max-labels-in-cache)
(dmsg 'sym "gas-locals-cache truncated.")
(nbutlast gas-locals-cache
(/ (* gas-max-labels-in-cache 3) 4)))
(add-to-list 'gas-locals-cache entry)
(throw 'found (nthcdr 3 entry)))))
(when (looking-at (format "\\(%s+\\)%s"
gas-re-sym gas-re-nosym))
(setq lbl (match-string 1))
(when (setq entry (assoc lbl gas-globals-cache))
(throw 'found (cdr entry)))
(let ((entry (gas-scan-global-symbol lbl)))
(when entry
(when (> (length gas-globals-cache)
gas-max-labels-in-cache)
(dmsg 'sym "gas-globals-cache truncated.")
(nbutlast gas-globals-cache
(/ (* gas-max-labels-in-cache 3) 4)))
(add-to-list 'gas-globals-cache entry)
(throw 'found (cdr entry))))))))
(dmsg 'sym "gas-symbol-highlight: Matched: %s" sym-list)
(gas-sym-invalidate)
(setq gas-hi-sym-list sym-list)
(let ((flags (car sym-list))
target-list)
(dolist (sym (cadr sym-list))
(when (input-pending-p)
(throw 'event-abort nil))
(setq target-list
(cond
((member 'duplicate flags) 'gas-hi-error)
((and (member 'global flags) (member 'def flags))
'gas-hi-global)
((member 'global flags) 'gas-hi-global-undef)
((eq (car sym) 'C-ref) nil)
((and (member 'def flags) (member 'ref flags))
'gas-hi-ok)
((> (length (cadr sym-list)) 1) 'gas-hi-undef)))
(dmsg 'sym "target-list, sym: %s, list: %s" sym target-list)
(when target-list
(add-to-list target-list (cdr sym) t))))
(setq gas-hi-valid t)
(font-lock-fontify-buffer)))
(setq case-fold-search case-fold-orig)))))
(defun gas-symbol-highlight-maybe ()
"Check for a symbol at point to be highlighted.
For use with the idle timer."
(unless (or (not gas-enable-symbol-highlight ) (input-pending-p))
(undo-boundary)
(gas-symbol-highlight)
(undo-boundary)))
(defun gas-C-comment-p ()
"True if we're editing a C syntax comment (the one enclosed in /* */)."
(save-excursion
(let ((current (point)))
(cond ((not (re-search-backward "/\\*" 0 t)) nil)
((not (re-search-forward "\\*/" current t)))))))
(defun gas-C-passthrough-p ()
"True if we're editing a C passthrough (a C style comment enclosed in /*C ...
*/)"
(when gas-use-C-passthrough
(save-excursion
(let ((current (point)))
(and (re-search-backward "/\\*C" 0 t)
(not (re-search-forward "\\*/" current t)))))))
(defun gas-C-passthrough-comment-p ()
"True if we're editing a C passthrough comment (/# ...
#/ within a /*C ... */ comment)"
(when gas-use-C-passthrough
(save-excursion
(let ((current (point)))
(when (gas-C-passthrough-p)
(save-restriction
(prog2
(gas-narrow-to-C-comment)
(and (re-search-backward "/\\#" 0 t)
(not (re-search-forward "[#*]/" current t))))))))))
(defun gas-C-passthrough-code-p ()
"True if we're editing C passthrough code.
i.e. we're within a /*C ...*/ comment but not within /# ... #/."
(when gas-use-C-passthrough
(and (gas-C-passthrough-p)
(not (gas-C-passthrough-comment-p)))))
(defun gas-C-comment-really-p ()
"True if we're in a standard C commont or in a nested passthrough C comment."
(and (gas-C-comment-p)
(not (gas-C-passthrough-code-p))))
(defun gas-comment-p ()
"True if we're editing some kind of comment."
(or (looking-back (concat gas-comment-string ".*"))
(gas-C-comment-p)))
(defun gas-narrow-to-C-comment ()
"Narrow region to the C comment point resides in."
(unless (gas-C-comment-p)
(error "Not within C comment"))
(save-excursion
(let* ((beg (re-search-backward ".*/[*]"))
(end (re-search-forward "[*]/" nil t)))
(when end
(narrow-to-region beg end)))))
(defun gas-token-pattern (cur-check)
"Return a list how to parse for token `CUR-CHECK'.
The returned list holds three elements, see the documentation
of `gas-patterns' for an explanation."
(let ((pattern (cdr (assq cur-check gas-patterns))))
(when (not pattern)
(t (error "Gas internal: illegal pattern %s requested"
cur-check)))
pattern))
(defun gas-next-token (check-list)
"Parse for one of the tokens in list `CHECK-LIST'.
`CHECK-LIST' is a list of cons cells (TOKEN-SYMBOL . RESULTING-TYPE).
Look up each TOKEN-SYMBOL in `gas-patterns' in order, then match
the text at point against the two regexps found there. On match,
eat the match (advance point past it) and return the resulting
field with its car set to RESULTING-TYPE (which is the same as
TOKEN-SYMBOL in nearly all cases except for the final parser
state, 'fini (and may be nil if same)). If none matches,
return nil.
Valid TOKEN-SYMBOLS and RESULTING-TYPEs are the ones listed in
'gas-patterns'."
(save-match-data
(catch 'got-token
(dolist (cur-check check-list)
(save-restriction
(let* ((pattern-list (gas-token-pattern (car cur-check)))
(regexp (pop pattern-list))
(subexps (pop pattern-list))
(limit-re (pop pattern-list))
limit
(subtype nil))
(setq limit (line-end-position))
(when (and limit-re
(looking-at
(format "\\(\\(\"[^\n\"]*\"\\)\\|[^\n\"]\\)*?\\(%s\\)"
limit-re)))
(setq limit (match-beginning 3)))
(when (>= limit (point))
(narrow-to-region (point) limit)
(when (looking-at regexp)
(cond
((eq (cdr cur-check) 'asm-comment)
(setq subtype (- (match-end 2) (match-beginning 2))))
((eq (cdr cur-check) 'argument)
(let ((arg-beg (point))
(arg-end (match-end 0)))
(setq subtype '(0))
(save-excursion
(save-match-data
(while (re-search-forward "," arg-end t)
(add-to-list 'subtype (- (point) arg-beg 1) t)))))))
(goto-char (min limit (match-end 0)))
(throw 'got-token
(list (cdr cur-check)
subtype
(- (min limit (match-beginning (pop subexps))) curbol)
(- (min limit (match-beginning (pop subexps))) curbol)
(- (min limit (match-end (pop subexps))) curbol)
(let ((subexp (pop subexps)))
(and subexp
(not (equal (match-string subexp) ""))
(match-string subexp)))
nil))))))))))
(defun gas-parse-line-really ()
"Parse the line point is on, element by element.
The returned list holds a list of syntactic elements (fields)
found, in the order of appearance. Each syntactic element is
represented by a list holding 7 elements:
'(TYPE SUBTYPE BEG-COL TEXT-COL END-COL TEXT MODIFIED)
0 - 'type: ('label, 'opcode ...)
1 - 'subtype: (additional information required by some types)
2 - 'beg-col: first slot (column #) (usually occupied by white
space before text)
3 - 'text-col: first text slot (column #)
4 - 'end-col: first free slot after field (column #)
5 - 'text: text contents (nil if empty)
6 - 'modified: nil (will later reflect if rearrangement (indentation)
is required / was done).
Terminology: A single list of these 7 elements is called a
`field' throughout gas-mode.el. `fields' as symbol name (or part
of a symbol name) means it stands for a variable bound to a list
of `field' lists which, in order of appearance, describe a
complete source line.
Note that text-col may lie outside (on the right side of) the
range beg-col ... end-col if there's no actual text (it is the
column where text would have to go to).
Don't assume a fixed element position except for 'type, which is
always the car of the list. Instead, use `gas-nth' to extract
elements by their symbolic names ('type, 'subtype, ...)."
(let (field
(f-type 'start)
pushed-f-type
(C-inline-comment-level 0)
fields)
(save-excursion
(setq cureol (line-end-position))
(beginning-of-line)
(setq curbol (point))
(setq f-type
(if (gas-C-comment-p)
(progn
(setq pushed-f-type 'starting-asm-line)
'starting-within-C-comment)
'starting-asm-line))
(while f-type
(setq field
(and (not (gas-C-comment-p))
(gas-next-token
'((C-inline-comment . C-inline-comment)
(C-comment-start . C-comment-start)))))
(unless field
(let ((next-check (cdr (assq f-type gas-parse-sequences))))
(when (not next-check)
(error "Gas-mode internal: gas-parse-line: %s" f-type))
(when (not (eq next-check 'fini))
(setq field (gas-next-token next-check)))
(when (eq (car field) 'garbage)
(error "Gas-mode internal: garbage: %s" field))
(dmsg 'parser "parsed a field: %s->%s" f-type field)))
(when (eq (car field) 'C-inline-comment)
(setcar field (list 'C-inline-comment C-inline-comment-level))
(setq C-inline-comment-level (1+ C-inline-comment-level)))
(when (and (eq 'asm-comment (car field))
(assq 'opcode fields))
(gas-set-nth 'subtype field 1))
(when field
(add-to-list 'fields field t))
(when (eq (car field) 'C-comment-start)
(setq pushed-f-type f-type))
(unless (and field (listp (car field)))
(setq f-type (car field)))
(when (eq f-type 'C-comment-end)
(setq f-type pushed-f-type))
(when (listp f-type)
(setq f-type (car f-type)))))
(dmsg '(parser cursor) "parsed all fields: %s" fields)
fields))
(defun gas-get-field (field-type field-list &optional offset)
"Return field of specified type.
Return field (a list) with first element (syntactic type) eq
to FIELD-TYPE from FIELD-LIST, nil if no such field.
Optional OFFSET >= 0: return next field, OFFSET < 0: return
previous field. If OFFSET is given. both fields (FIELD-TYPE and
the one before/after) must exist, else nil is returned."
(if offset
(let (f-types
f-type)
(dolist (field field-list)
(add-to-list 'f-types (car field) t))
(if (> offset 0)
(while (not (eq (pop ftypes) field-type)))
(while (and (setq f-type (nth 1 f-types))
f-type
(not (eq f-type field-type)))
(pop f-types)))
(gas-get-field (car f-types) field-list))
(assq field-type field-list)))
(defun gas-nth (component field-from &optional field-list)
"Get one of the components of a field.
COMPONENT is the component to be returned. FIELD-FROM is either
a list containing a field, or a symbol designating a field (see
`gas-parse-line-really' for both).
In the latter case, optional FIELD-LIST must be supplied as a
list of fields where FIELD-FROM is extracted from."
(let ((field-from (if field-list
(gas-get-field field-from field-list)
field-from)))
(nth (gas-elmt-n component) field-from)))
(defun gas-elmt-n (elmt)
"Return the numeric index of field component ELMT."
(when (symbolp elmt)
(- (length gas-elmt-types) (length (memq elmt gas-elmt-types)))))
(defun gas-set-nth (elmt field val-or-fields &optional val)
"Set a single field component.
ELMT is the content to set the field to.
If FIELD is a list containing a single field, set its ELMT (a
symbol) to VAL-OR-FIELDS. If FIELD is a symbol designating a
field and VAL-OR-FIELDS is a list of such fields (as returned by
`gas-parsed'), set ELMT in the FIELD field of VAL-OR-FIELDS to
VAL."
(let ((curfield field))
(if val
(setq curfield (assq field val-or-fields))
(setq val val-or-fields))
(setcar (nthcdr (gas-elmt-n elmt) curfield) val)))
(defun gas-rearrange (fields &optional elmt f-type val)
"Rearrange FIELDS so they fit together without gaps or overlapping.
FIELDS is the field list describing the current line.
Optional ELMT designates a field component in the field specified
by (eq to) F-TYPE, which has to be changed before rearrangement
to VAL. VAL may be a variable, a symbol which evaluates to the
taget value, or bound to a function returning the target type of
the component.
If the line did change, return the field type (the car) of the
first field that has changed, else nil."
(let ((field-slot 0)
(text-slot 0)
(end-slot 0)
did-change)
(dolist (field fields)
(let ((old-col (gas-nth 'text-col field))
(text (gas-nth 'text field)))
(gas-set-nth 'beg-col field field-slot)
(when (and val (eq f-type (gas-nth 'type field)))
(gas-set-nth elmt field val))
(let ((tcol (gas-nth 'text-col field)))
(when (or (eq elmt 'all)
(and (not val) (eq f-type (car field))))
(setq tcol (cdr (assq (car field) gas-indents))))
(when (and tcol (symbolp tcol))
(setq tcol (if (fboundp tcol)
(funcall tcol)
(eval tcol))))
(unless tcol
(setq tcol text-slot))
(setq tcol (max tcol text-slot))
(gas-set-nth 'text-col field tcol)
(when (and text
(not (eq tcol old-col)))
(unless did-change
(setq did-change (car field)))
(gas-set-nth 'modified field t))
(when text
(setq field-slot (+ (gas-nth 'text-col field) (length text))))
(setq end-slot (max end-slot (or (gas-nth 'end-col field) 0)))
(gas-set-nth 'end-col field field-slot)
(setq text-slot (max tcol text-slot))
(when (/= 0 field-slot)
(setq text-slot (max text-slot (1+ field-slot)))))))
(gas-set-nth 'end-col 'eol-ws fields end-slot)
did-change))
(defun gas-reset-modified (fields)
"Clears the `modified' flag on all fields in FIELDS."
(dolist (field fields)
(gas-set-nth 'modified field nil)))
(defun gas-get-pointpos (ffields)
"Determine the field in FFIELDS point is on.
Returns a list (F-TYPE SUBTYPE OFFSET-TO-FIELD-TEXT-BEGIN ABS-COLUMN EOL).
EOL may bei either the symbol 'eol or nil"
(let* ((curcol (current-column))
(fields (copy-tree ffields))
(ppos (list (caar fields) curcol)))
(catch 'pos-found
(dolist (field fields)
(when (and (gas-nth 'text field)
(>= curcol (gas-nth 'beg-col field))
(<= curcol (gas-nth 'end-col field)))
(setq ppos (list (car field) (- curcol (gas-nth 'text-col field))))
(throw 'pos-found t)))
(gas-rearrange fields 'all)
(dolist (field fields)
(when (or (<= curcol (gas-nth 'text-col field))
(eq (car field) 'eol-ws))
(setq ppos (list (car field) (- curcol (gas-nth 'text-col field))))
(throw 'pos-found t))))
(setq ppos (append ppos (list curcol)))
(if (>= curcol (gas-nth 'end-col 'eol-ws fields))
(append ppos '(eol))
ppos)))
(defun gas-get-C-relative-indent ()
"Return indent of the closest previous non-blank line in current C comment."
(if (not (gas-C-comment-p))
gas-C-indent
(save-restriction
(gas-narrow-to-C-comment)
(let ((col
(save-excursion
(catch 'found
(if (looking-at "[ \t]*#/")
(while (= 0 (forward-line -1))
(when (looking-at "[ \t]*/#")
(throw 'found (current-indentation))))
(while (= 0 (forward-line -1))
(when (looking-at "[ \t]*/[*#]")
(throw 'found (+ gas-C-indent (current-indentation))))
(when (looking-at "[ \t]*[^ \t\n]")
(throw 'found (current-indentation)))))
gas-C-indent))))
(when (gas-C-passthrough-code-p)
(if (looking-at "[ \t\n]*}[ \t\n]*;")
(setq col (max 0 (- col gas-C-indent)))))
col))))
(defun gas-add-missing-fields (parsed-fields)
"Complete parser result by adding in empty but allowed fields.
Determine line syntax from the parse result in PARSED-FIELDS,
then add in empty fields for syntactic components not used in the
source line but allowed by the syntax, so later code can safely
assume they are present."
(let (existing-types mandatory line-syntax new-fields f-type cur-type)
(setq mandatory
(cond ((not parsed-fields)
'(label opcode argument asm-comment eol-ws))
((assq 'C-comment parsed-fields)
'(C-comment-start C-comment C-comment-end eol-ws))
((assq 'cpp-macro-def parsed-fields)
'(cpp-macro-def cpp-argument eol-ws))
(t
(let ((subtype (gas-nth 'subtype 'asm-comment parsed-fields)))
(cond
((eq subtype 3) '(asm-comment eol-ws))
((eq subtype 2) '(label asm-comment eol-ws))
(t '(label opcode argument asm-comment eol-ws)))))))
(dmsg '(parser cursor) "mandatory: %s" mandatory)
(dolist (field parsed-fields)
(setq f-type (car field))
(if (member f-type mandatory)
(progn
(while (progn
(setq cur-type (pop mandatory))
(not (eq cur-type f-type)))
(dmsg 'parser "adding missing mandatory: %s" cur-type)
(add-to-list 'new-fields
(list cur-type nil nil nil nil nil nil) t))
(dmsg 'parser "adding existing mandatory: %s" field)
(add-to-list 'new-fields field t))
(add-to-list 'new-fields field t)))
(while (setq cur-type (pop mandatory))
(dmsg 'rearranged "adding left-over: %s" cur-type)
(add-to-list 'new-fields
(list cur-type nil nil nil nil nil nil) t))
(dmsg 'parser "new-fields=%s" new-fields)
new-fields))
(defun gas-parsed (&optional lineno)
"Return fields on line LINENO or, if nil, on current line.
Return cached data, if available. Else, call
`gas-parse-line-really', store result into the gas-line-cache and
return it."
(dmsg 'parser "gas-changed: %s, lenght gas-line-cache: %s"
gas-changed (length gas-line-cache))
(unless (and (eq gas-comment-char ?\;)
(eq gas-local-comment-char gas-comment-char))
(setq gas-local-comment-char gas-comment-char)
(gas-set-patterns gas-comment-char))
(when gas-changed
(setq gas-line-cache nil)
(setq gas-changed nil))
(when (> (length gas-line-cache) gas-max-lines-in-cache)
(dmsg 'parser "gas-line-cache truncated.")
(nbutlast gas-line-cache (/ (* gas-max-lines-in-cache 3) 4)))
(let* ((curline (or lineno (line-number-at-pos)))
(cached (assq curline gas-line-cache)))
(if cached
(cadr cached)
(let (fields)
(save-excursion
(goto-line curline)
(setq fields (gas-parse-line-really))
(setq fields (gas-add-missing-fields fields))
(gas-rearrange fields)
(dmsg 'parser "->gas-line-cache: %s" fields)
(add-to-list 'gas-line-cache (list curline fields)))
fields))))
(defun gas-put-parsed (fields lineno)
"Put FIELDS into gas-line-cache at LINENO.
Overwrites the previous entry for that line (if any)."
(gas-reset-modified fields)
(let ((current (assq lineno gas-line-cache)))
(if current
(setcdr current (list fields))
(add-to-list 'gas-line-cache (list curline fields)))))
(defun gas-put-out (&optional fields)
"Replace the current scrren line by the one described by FIELDS."
(when (not fields)
(setq fields (gas-parsed)))
(dmsg 'indent "put-out, fields: %s" fields)
(combine-after-change-calls
(save-excursion
(beginning-of-line)
(delete-region (point) (line-end-position))
(dolist (field fields)
(when (and (gas-nth 'text field)
(or gas-preserve-trailing-whitespace
(not (eq (car field) 'eol-ws))))
(indent-to (gas-nth 'text-col field))
(insert (gas-nth 'text field)))))))
(defun gas-get-asm-comment-column ()
"Get comment column according to asm comment subtype on current line.
The caller must provide the variable `fields' (bound to current
line's parsed content)."
(let ((subtype (gas-nth 'subtype 'asm-comment fields)))
(cond ((not subtype) gas-comment-column)
((= subtype 1) gas-comment-column)
((= subtype 2) gas-opcode-column)
(t 0))))
(defun gas-set-point-to-field (f-type fields)
"Postition point to the start of the field of type F-TYPE.
FIELDS is a field list reflecting the current line."
(let ((target-col (gas-nth 'text-col f-type fields)))
(move-to-column target-col)
(indent-to target-col)
(when (eq f-type 'asm-comment)
(skip-chars-forward (format " %c" gas-comment-char))
(when (not (looking-back " "))
(insert-char ?\s 1)))))
(defun gas-set-point (pointpos fields &optional direction always)
"Set point to a field of the current line.
POINTPOS is the current field as returned by a call to
`gas-get-pointpos', FIELDS the parsed content of the current
line.
If DIRECTION is nil or 'stay, set it to the beginning of
the current field. If DIRECTION is 'end-of-field, set it to the
end of the current field. If DIRECTION equals 'backward or
'forward, and ALWAYS is nil, set it to the beginning of the
next/previous field only if the field is unchanged, else to the
beginning of the current. Finally, if DIRECTION equals 'backward
or'forward and ALWAYS is non-nil, move point unconditionally to
the beginning of the next/previous field."
(let ((existing '(line-begin))
f-type)
(dmsg 'indent "(gas-set-point %s %s %s)" pointpos direction always)
(dolist (field fields)
(setq existing (append existing (list (car field)))))
(add-to-list 'existing 'line-end t)
(when (eq direction 'backward)
(setq existing (nreverse existing)))
(setq f-type (car pointpos))
(when (and (eq direction 'forward)
(eq (nth 3 pointpos) 'eol))
(cond ((and (eq f-type 'C-comment-start) (= (nth 2 pointpos) 0))
nil)
((memq f-type '(label opcode argument))
(cond ((>= (nth 2 pointpos) gas-comment-column)
(setq f-type 'eol-ws))
((and (not (gas-nth 'text 'opcode fields))
(>= (nth 2 pointpos) gas-opcode-column))
(setq f-type 'argument))))
(t (setq f-type 'eol-ws))))
(when (eq direction 'backward)
(when (= (nth 2 pointpos) 0)
(setq f-type (car (last existing 2))))
(and (> (nth 1 pointpos) 0)
(or (not (eq f-type 'asm-comment))
(not (looking-back ";[ \t]")))
(setq direction 'stay)))
(when (and (not (memq direction '(pos stay end-of-field)))
(or always
(and direction
(not (gas-nth 'modified f-type fields)))))
(setq f-type (cadr (member f-type existing))))
(dmsg 'indent "gas-set-point next:%s, fields: %s" f-type fields)
(if (eq f-type 'line-begin)
(progn
(if (bobp)
(beep))
(beginning-of-line))
(when (eq f-type 'line-end)
(when (eobp)
(beep)
(error "End of buffer"))
(setq f-type 'eol-ws)
(setq direction 'end-of-field))
(gas-put-out)
(let ((where (gas-nth 'text-col f-type fields)))
(cond ((eq direction 'pos)
(setq where (+ (gas-nth 'text-col f-type fields)
(cadr pointpos))))
((eq direction 'end-of-field)
(setq where (max where (gas-nth 'end-col f-type fields)))))
(move-to-column where t)
(indent-to where))
(when (eq f-type 'asm-comment)
(skip-chars-forward (format " %c" gas-comment-char))
(when (not (looking-back " "))
(insert-char ?\s 1))))))
(defun gas-indent (&optional direction always ffields)
"Indent current line.
For lines carrying asm syntax, `gas-indent-current-field-only'
determines if only the current field or the entire line is
affected.
See `gas-set-point' for a description of DIRECTION and ALWAYS.
FFIELDS, if present, is a list describing the fields on the
current line as returned by `gas-parsed'."
(let* ((fields (or ffields (gas-parsed)))
rearranged-type
pointpos)
(setq pointpos (gas-get-pointpos fields))
(dmsg 'indent "gas-indent, fields: %s" fields)
(dmsg 'indent "gas-indent, pointpos: %s" pointpos)
(when (and (not direction)
(not (gas-C-comment-p))
(eq this-command 'newline-and-indent))
(setq pointpos
(list
(or (catch 'field-found
(dolist (field fields)
(when (gas-nth 'text field)
(throw 'field-found (car field)))))
'opcode)
0 gas-opcode-column))
(setq direction 'stay)
(setq always t))
(setq rearranged-type
(cond
((and (eq (caar fields) 'C-comment-start)
(or (save-excursion (beginning-of-line)(looking-at "[ \t]*$"))
(and (eq (car pointpos) 'C-comment) (= (nth 1 pointpos) 0))))
(let ((n_indents (/ (nth 2 pointpos) gas-C-indent)))
(if (eq direction 'backward)
(setq n_indents (max 0 (1- n_indents)))
(setq n_indents (1+ n_indents)))
(gas-rearrange fields 'text-col 'C-comment
(* gas-C-indent n_indents))
(setq direction 'stay)
'C-comment))
(gas-indent-current-field-only
(gas-rearrange fields 'text-col (car pointpos)
(cdr (assq (car pointpos) gas-indents))))
(t (gas-rearrange fields 'all))))
(dmsg 'indent "re-indented, fields: %s" fields)
(gas-put-parsed fields (line-number-at-pos))
(gas-put-out)
(cond (always (gas-set-point pointpos fields direction always))
(rearranged-type (gas-set-point-to-field rearranged-type fields))
((and (eq (car pointpos) 'C-comment) (not direction)
(> (cadr pointpos) 0))
(gas-set-point pointpos fields 'pos always))
((and (not direction) (>= (cadr pointpos) 0))
(gas-set-point pointpos fields 'forward always))
(t (gas-set-point pointpos fields direction always)))
fields))
(defun gas-indent-region (&optional from to)
"Indent all fields in region.
If optional FROM and TO are given, they are used instead of point
and mark for the region's end points."
(interactive)
(unless from (setq from (min (point) (mark))))
(unless to (setq to (max (point) (mark))))
(combine-after-change-calls
(save-excursion
(goto-char from)
(while (and (not (eobp)) (< (point) to))
(dmsg 'indent "indent-region, line:%s" (line-number-at-pos))
(if (gas-C-comment-p)
(beginning-of-line 2)
(let ((fields (gas-parsed)))
(gas-rearrange fields 'all)
(gas-put-out fields)
(beginning-of-line 2)))))))
(defun gas-indent-backward ()
"Indent, then move to previous field.
While in C-comment, remove one level of indentation."
(interactive)
(gas-indent 'backward 'always))
(defun gas-forward-sexp (n)
"On a highlighted symbol, move to next (previous if N < 9).
Else do `forward-sexp' as usual."
(gas-sym-invalidate)
(interactive)
(gas-symbol-highlight)
(if gas-hi-sym-list
(unless
(catch 'found-one
(if (< 0 n)
(dolist (match (cadr gas-hi-sym-list))
(when (> (cadr match) (point))
(goto-char (cadr match))
(throw 'found-one t)))
(dolist (match (reverse (cadr gas-hi-sym-list)))
(when (< (nth 2 match) (point))
(goto-char (cadr match))
(throw 'found-one t)))))
(beep))
(let (forward-sexp-function)
(forward-sexp n))))
(defun gas-beginning-of-defun ()
"Skip to the beginning of the current block.
The block delimiter is described by `gas-defun-regexp'."
(interactive)
(let* ((beg (save-excursion
(re-search-backward gas-defun-regexp nil t))))
(when beg
(goto-char (match-beginning gas-defun-regexp-subexp)))))
(defun gas-end-of-defun ()
"Skip to the beginning of the next block.
The block delimiter is described by `gas-defun-regexp'."
(interactive)
(let ((beg (save-excursion
(re-search-forward gas-defun-regexp nil t))))
(when beg
(goto-char (match-beginning gas-defun-regexp-subexp)))))
(defun gas-hash ()
"Insert a hash mark. If it start a macro, delete the indentaion."
(interactive)
(when (and (not (gas-comment-p))
(looking-back "^[ \t]*"))
(beginning-of-line)
(delete-horizontal-space))
(call-interactively 'self-insert-command))
(defun gas-colon ()
"Insert a colon; if it follows a label, delete the label's indentation."
(interactive)
(call-interactively 'self-insert-command)
(let* ((fields (gas-parsed))
(pointpos (gas-get-pointpos fields)))
(dmsg 'indent "gas-colon, fields: %s" fields)
(dmsg 'indent "gas-colon, pointpos: %s" pointpos)
(when (eq (car pointpos) 'label)
(beginning-of-line)
(gas-indent 'forward t))))
(defalias 'gas-newline 'newline-and-indent)
(defun gas-comment-region-dwim (&optional from to)
"De-comment region if at start of comment, make comment out of region if not.
Optional argument FROM Optional FROM and TO mark the region."
(unless from
(setq from (min (point) (mark)))
(setq to (max (point) (mark))))
(when (save-excursion (goto-char to) (bolp))
(setq to (1- to)))
(save-excursion
(let ((mmax (progn (goto-char to) (point-marker)))
(mmin (progn (goto-char from) (point-marker))))
(if (gas-C-passthrough-p)
(let* (beg end end-match
(pos (looking-at "[ \t\n]*/#"))
(de-comment (or pos (gas-C-passthrough-comment-p))))
(save-restriction
(gas-narrow-to-C-comment)
(if de-comment
(progn
(when (gas-C-passthrough-comment-p)
(insert " #/\n"))
(while (re-search-forward
"\\(\n[ \t]*\\)?/#\\(\n[ \t]*\\)?" mmax t)
(replace-match "")
(when (re-search-forward
"\\(\n[ \t]*\\)?[#]/\\(\n[ \t]*\\)?" mmax t)
(replace-match "")))
(let* ((end (save-excursion
(re-search-forward "[#*]/" nil t)))
(end-match (match-string 0))
(beg (save-excursion
(re-search-forward "/#" nil t))))
(when (and (equal end-match "#/")
(or (not beg)
(> beg end)))
(goto-char mmax)
(insert "\n/#\n"))))
(insert " /#\n ")
(while (and (save-excursion
(setq end (re-search-forward "[*#]/" nil t)))
(setq beg (re-search-forward "/#" mmax t)))
(replace-match "#/ /# ")
(re-search-forward "[#*]/" mmax t)
(if (equal end-match "*/")
(replace-match " #/\n*/")
(replace-match "#/ /#")))
(let* ((end (save-excursion
(re-search-forward "[#*]/" nil t)))
(end-match (match-string 0))
(beg (save-excursion
(re-search-forward "/#" nil t))))
(when (and (equal end-match "*/")
(or (not beg)
(> beg end)))
(goto-char mmax)
(backward-char 2)
(insert "\n #/\n"))))))
(when (looking-back (format "%c+[ \t]*" gas-comment-char))
(goto-char (match-beginning 0))
(skip-chars-backward gas-comment-string))
(save-restriction
(narrow-to-region (point) to)
(let ((triple-comment (concat (make-string 3 gas-comment-char) " "))
comment-pattern
(de-comment
(looking-at (format "[ \t]*%c\\|.*\n[ \t\n]*\%c"
gas-comment-char gas-comment-char))))
(when de-comment
(goto-char (match-end 0))
(skip-chars-backward gas-comment-string)
(looking-at (format "\n?\\([ \t]\\)*\\(%c+\\)\\([ \t]?\\)"
gas-comment-char))
(let ((pre-pattern (match-string 1))
(mid-pattern (regexp-quote (match-string 2)))
(post-pattern (match-string 3)))
(setq pre-pattern
(if (and pre-pattern
(string-match "[ \t]" pre-pattern))
"\\([ \t]*"
"\\("))
(setq post-pattern
(if (and post-pattern
(string-match "[ \t]" post-pattern))
"[ \t]?\\)"
(format "\\)\\([^%c]\\|$\\)" gas-comment-char)))
(setq comment-pattern
(concat pre-pattern mid-pattern post-pattern))))
(catch 'fini
(while t
(cond ((gas-C-comment-p) nil)
((looking-at "[ \t]/[*]") nil)
((not de-comment) (insert triple-comment))
(t (when (looking-at comment-pattern)
(replace-match "" nil t nil 1))))
(unless (and (= 0 (forward-line)) (bolp))
(throw 'fini t)))))))
(setq mmax nil)
(setq mmin nil))))
(defun gas-backward-indent ()
"Skip to previous asm field. Within C style comments, decrease indentation."
(interactive)
(gas-indent 'backward 'always))
(defun gas-comment ()
"Start/expand a comment.
Suggested usage: while writing your code, trigger `gas-comment'
repeatedly until you are satisfied with the kind of comment."
(interactive)
(setq gas-doing-comment t)
(if mark-active
(gas-comment-region-dwim)
(let* ((fields (save-excursion (gas-parsed)))
(curcol (current-column))
(pointpos (gas-get-pointpos fields))
(subtype (gas-nth 'subtype 'asm-comment fields)))
(dmsg 'indent "gas-comment, pointpos: %s, fields. %s"
pointpos fields)
(cond
((gas-C-passthrough-comment-p) (insert-char gas-comment-char 1))
((gas-C-passthrough-code-p)
(when (looking-back "/[*]C[ \t]*")
(insert "\n"))
(if (looking-back "[ \t]")
(insert "/# #/ ")
(insert " /# #/ "))
(backward-char 5))
((gas-C-comment-p)
(if (and gas-use-C-passthrough
(looking-back "\\(/[*] ?\\)[ \t\n]*") )
(save-excursion (replace-match "/*C" nil nil nil 1))
(insert-char gas-comment-char 1)))
((and subtype (>= subtype 3))
(move-to-column (gas-nth 'text-col 'asm-comment fields))
(looking-at ";+ ?")
(replace-match "")
(insert "/*")
(backward-char)
(gas-indent 'forward 'always)
(let ((curindent (current-indentation)))
(insert "\n\n")
(indent-to curindent)
(insert "*/")
(forward-line -1)
(indent-to (+ curindent gas-C-indent))))
((looking-back "[ \t]*#.*" (line-beginning-position))
(insert "/* */ ")
(backward-char 5))
(t
(let ((target-col (gas-nth 'text-col 'asm-comment fields)))
(when target-col
(move-to-column target-col)))
(when (or (not subtype)
(and (<= (current-column) curcol)
(save-excursion
(skip-chars-forward (format "%c \t" gas-comment-char))
(>= (current-column) curcol))))
(insert-char gas-comment-char 1)
(setq fields (gas-parsed)))
(gas-rearrange fields 'text-col 'asm-comment)
(gas-put-out fields)
(gas-set-point-to-field 'asm-comment fields))))))
(defun gas-fill-paragraph (arg)
"Beautify asm code block. Within comments, fill current paragraph.
Calls `fill-paragraph' if within a C comment with ARG passed
through (which is ignored otherwise)."
(let ((curcol (current-column))
(fill-paragraph-function nil)
(asm-comment-re (format "\\([ \t]*\\(%c+\\)\\)[ \t]*[^ \t%c\n]\n?"
gas-comment-char gas-comment-char))
(empty-line-re "\\([^ \t]+:\\)?[ \t]*$"))
(cond ((gas-C-comment-really-p)
(let* ((beg (save-excursion
(re-search-backward "/#\\|/[*][^C]\\([ \t]*\n\\)?")
(match-end 0)))
(end (save-excursion
(re-search-forward "\\(\n[ \t]*\\)?[*#]/")
(match-beginning 0))))
(save-restriction
(narrow-to-region beg end)
(fill-paragraph arg))))
((gas-C-passthrough-code-p)
(let* ((beg (save-excursion
(re-search-backward "\\(#/\\|/[*]C\\)\\([ \t]*\n\\)?")
(match-end 0)))
(end (save-excursion
(re-search-forward "\\(\n[ \t]*\\)?[*#]/")
(match-beginning 0))))
(goto-char beg)
(while (< (point) end)
(let ((fields (gas-parsed)))
(gas-rearrange fields 'all)
(gas-put-out fields)
(beginning-of-line 2)))))
((save-excursion (beginning-of-line) (looking-at asm-comment-re))
(let* ((fill-prefix (concat (match-string 1) " "))
(re (format "[^%c\n]*\\%s[ \t]*[^ \t%c\n]\n?" gas-comment-char
(match-string 2) gas-comment-char))
(beg (save-excursion
(beginning-of-line)
(while (save-excursion
(beginning-of-line)
(looking-at re))
(end-of-line 0))
(point)))
(end (save-excursion
(end-of-line)
(while (save-excursion
(beginning-of-line 2)
(looking-at re))
(end-of-line 2))
(point))))
(save-restriction
(narrow-to-region beg end)
(fill-paragraph arg))))
(t
(let ((beg (save-excursion
(beginning-of-line)
(while (save-excursion
(beginning-of-line 0)
(not (or (looking-at asm-comment-re)
(looking-at empty-line-re)
(gas-comment-p))))
(beginning-of-line 0))
(point)))
(end (save-excursion
(end-of-line)
(while (save-excursion
(beginning-of-line 2)
(not (or (looking-at asm-comment-re)
(looking-at empty-line-re)
(gas-comment-p))))
(end-of-line 2))
(point))))
(gas-indent-region beg end))))
(move-to-column curcol)) t)
(defun gas-comment-char ()
"Handle comment character.
If we're in a comment start sequence (either introduced by the
last command being `gas-comment' or by
`gas-comment-char-starts-comment' being customized to a non-nil
value and typing 'gas-comment-char'), increment the level of the
comment just started.
Else, if `gas-comment-char-starts-comment' is customized to a
non-nil value, start comment.
Else just insert variable `gas-comment-char'."
(interactive)
(if (and gas-comment-char-starts-comment
(not (gas-C-comment-p)))
(gas-comment)
(if (and gas-doing-comment
(member last-command '(gas-comment-char gas-comment)))
(gas-comment)
(setq gas-doing-comment nil)
(insert-char gas-comment-char 1))))
(provide 'gas-mode)