Mercurial > emacs
changeset 26542:0d41332e3819
Major mode for ANTLR grammar files.
author | Gerd Moellmann <gerd@gnu.org> |
---|---|
date | Mon, 22 Nov 1999 15:18:35 +0000 |
parents | ce6bf7b42bc7 |
children | c84c8d663642 |
files | lisp/progmodes/antlr-mode.el |
diffstat | 1 files changed, 994 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/progmodes/antlr-mode.el Mon Nov 22 15:18:35 1999 +0000 @@ -0,0 +1,994 @@ +;;; antlr-mode.el --- Major mode for ANTLR grammar files + +;; Copyright (C) 1999 Free Software Foundation, Inc. +;; +;; Author: Christoph.Wedler@sap.com +;; Version: $Id: antlr-mode.el,v 1.2 1999/11/11 14:40:51 wedler Exp $ + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 2, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, +;; Boston, MA 02111-1307, USA. + +;;; Commentary: + +;; Major mode for editing ANTLR grammar files, i.e., files ending with `.g'. +;; ANTLR is ANother Tool for Language Recognition (an excellent alternative to +;; lex/yacc), see <http://www.ANTLR.org> and <news:comp.compilers.tools.pccts>. + +;; Variable `antlr-language' is set according to the language in actions and +;; semantic predicates of the grammar (see ANTLR's file option "language"). +;; The supported languages are "Java" (java-mode) and "Cpp" (c++-mode). This +;; package uses features of the Emacs package cc-mode. + +;; This package provides the following features: +;; * Indentation for the current line (TAB) and selected region (C-M-\). +;; * Syntax coloring (via font-lock) with language dependend coloring. +;; * Support for imenu/speedbar: menu "Index" (Parser, Lexer, TreeParser). +;; * Direct move to previous/next rule, beginning/end of rule body etc. + +;; INDENTATION. This package supports ANTLR's (intended) indentation style +;; which is based on a simple paren/brace/bracket depth-level calculation, see +;; `antlr-indent-line'. The indentation engine of cc-mode is only used inside +;; block comments (it is not easy to use it for actions, esp if they come early +;; in the rule body). By default, this package uses TABs for a basic offset of +;; 4 to be consistent to both ANTLR's conventions (TABs usage) and the +;; `c-indentation-style' "java" which sets `c-basic-offset' to 4, see +;; `antlr-tab-offset-alist'. + +;; SYNTAX COLORING comes in three phases. First, comments and strings are +;; highlighted. Second, the grammar code is highlighted according to +;; `antlr-font-lock-additional-keywords' (rule refs: blue, token refs: brown, +;; definition: ditto+bold). Third, actions, semantic predicates and arguments +;; are highlighted according to the usual font-lock keywords of +;; `antlr-language', see also `antlr-font-lock-maximum-decoration'. We define +;; special font-lock faces for the grammar code to allow you to distinguish +;; ANTLR keywords from Java/C++ keywords. + +;;; Installation: + +;; This file requires Emacs-20.3, XEmacs-20.4 or higher. + +;; If antlr-mode is not part of your distribution, put this file into your +;; load-path and the following into your ~/.emacs: +;; (autoload 'antlr-mode "antlr-mode" nil t) +;; (setq auto-mode-alist (cons '("\\.g\\'" . antlr-mode) auto-mode-alist)) +;; (add-hook 'speedbar-load-hook ; would be too late in antlr-mode.el +;; (lambda () (speedbar-add-supported-extension ".g"))) + +;; If you edit ANTLR's source files, you might also want to use +;; (autoload 'antlr-set-tabs "antlr-mode") +;; (add-hook 'java-mode-hook 'antlr-set-tabs) + +;; To customize, use `M-x customize-group RET antlr RET' or the custom browser +;; (Emacs->Programming->Languages->Antlr). + +;;; Code: + +(provide 'antlr-mode) +(eval-when-compile (require 'cl)) +(require 'easymenu) ; Emacs +(eval-when-compile (require 'cc-mode)) ; shut up most warnings + +(eval-and-compile + (if (string-match "XEmacs" emacs-version) + (defalias 'antlr-scan-sexps 'scan-sexps) + (defalias 'antlr-scan-sexps 'antlr-scan-sexps-internal)) + (if (and (fboundp 'buffer-syntactic-context) + (fboundp 'buffer-syntactic-context-depth)) + (progn + (defalias 'antlr-invalidate-context-cache 'antlr-xemacs-bug-workaround) + (defalias 'antlr-syntactic-context 'antlr-fast-syntactic-context)) + (defalias 'antlr-invalidate-context-cache 'ignore) + (defalias 'antlr-syntactic-context 'antlr-slow-syntactic-context))) + + + +;;;;########################################################################## +;;;; Variables +;;;;########################################################################## + + +(defgroup antlr nil + "Major mode for ANTLR grammar files." + :group 'languages + :link '(emacs-commentary-link "antlr-mode.el") + :prefix "antlr-") + +(defconst antlr-version "1.2" + "ANTLR major mode version number.") + + +;;;=========================================================================== +;;; Controlling ANTLR's code generator (language option) +;;;=========================================================================== + +(defvar antlr-language nil + "Major mode corresponding to ANTLR's \"language\" option. +Set via `antlr-language-alist'. The only useful place to change this +buffer-local variable yourself is in `antlr-mode-hook' or in the \"local +variable list\" near the end of the file, see +`enable-local-variables'.") + +(defcustom antlr-language-alist + '((java-mode "Java" nil "Java") + (c++-mode "C++" "Cpp")) + "List of ANTLR's supported languages. +Each element in this list looks like + (MAJOR-MODE MODELINE-STRING OPTION-VALUE...) + +MAJOR-MODE, the major mode of the code in the grammar's actions, is the +value of `antlr-language' if the first regexp group matched by REGEXP in +`antlr-language-limit-n-regexp' is one of the OPTION-VALUEs. An +OPTION-VALUE of nil denotes the fallback element. MODELINE-STRING is +also displayed in the modeline next to \"Antlr\"." + :group 'antlr + :type '(repeat (group :value (java-mode "") + (function :tag "Major mode") + (string :tag "Modeline string") + (repeat :tag "ANTLR language option" :inline t + (choice (const :tag "Default" nil) + string ))))) + +(defcustom antlr-language-limit-n-regexp + '(3000 . "language[ \t]*=[ \t]*\"\\([A-Z][A-Za-z_]*\\)\"") + "Used to set a reasonable value for `antlr-language'. +Looks like (LIMIT . REGEXP). Search for REGEXP from the beginning of +the buffer to LIMIT to set the language according to +`antlr-language-alist'." + :group 'antlr + :type '(cons (choice :tag "Limit" (const :tag "No" nil) (integer :value 0)) + regexp)) + + +;;;=========================================================================== +;;; Indent/Tabs +;;;=========================================================================== + +(defcustom antlr-indent-comment 'tab + "*Non-nil, if the indentation should touch lines in block comments. +If nil, no continuation line of a block comment is changed. If t, they +are changed according to `c-indentation-line'. When not nil and not t, +they are only changed by \\[antlr-indent-command]." + :group 'antlr + :type '(radio (const :tag "No" nil) + (const :tag "Always" t) + (sexp :tag "With TAB" :format "%t" :value tab))) + +(defcustom antlr-tab-offset-alist + '((antlr-mode nil 4 t) + (java-mode "antlr" 4 t)) + "Alist to determine whether to use ANTLR's convention for TABs. +Each element looks like (MAJOR-MODE REGEXP TAB-WIDTH INDENT-TABS-MODE). +The first element whose MAJOR-MODE is nil or equal to `major-mode' and +whose REGEXP is nil or matches `buffer-file-name' is used to set +`tab-width' and `indent-tabs-mode'. This is useful to support both +ANTLR's and Java's indentation styles. Used by `antlr-set-tabs'." + :group 'antlr + :type '(repeat (group :value (antlr-mode nil 8 nil) + (choice (const :tag "All" nil) + (function :tag "Major mode")) + (choice (const :tag "All" nil) regexp) + (integer :tag "Tab width") + (boolean :tag "Indent-tabs-mode")))) + +(defvar antlr-indent-item-regexp + "[]}):;|&]\\|default[ \t]*:\\|case[ \t]+\\('\\\\?.'\\|[0-9]+\\|[A-Za-z_][A-Za-z_0-9]*\\)[ \t]*:" ; & is local ANTLR extension + "Regexp matching lines which should be indented by one TAB less. +See command \\[antlr-indent-command].") + + +;;;=========================================================================== +;;; Menu +;;;=========================================================================== + +(defcustom antlr-imenu-name t + "*Non-nil, if a \"Index\" menu should be added to the menubar. +If it is a string, it is used instead \"Index\". Requires package +imenu." + :group 'antlr + :type '(choice (const :tag "No menu" nil) + (const :tag "Index menu" t) + (string :tag "Other menu name"))) + +(defvar antlr-mode-map + (let ((map (make-sparse-keymap))) + (define-key map "\t" 'antlr-indent-command) + (define-key map "\e\C-a" 'antlr-beginning-of-rule) + (define-key map "\e\C-e" 'antlr-end-of-rule) + (define-key map "\C-c\C-a" 'antlr-beginning-of-body) + (define-key map "\C-c\C-e" 'antlr-end-of-body) + (define-key map "\C-c\C-f" 'c-forward-into-nomenclature) + (define-key map "\C-c\C-b" 'c-backward-into-nomenclature) + ;; I'm too lazy to define my own: + (define-key map "\ea" 'c-beginning-of-statement) + (define-key map "\ee" 'c-end-of-statement) + map) + "Keymap used in `antlr-mode' buffers.") + +(easy-menu-define antlr-mode-menu + antlr-mode-map + "Major mode menu." + '("Antlr" + ["Indent Line" antlr-indent-command + :active (not buffer-read-only)] + ["Indent for Comment" indent-for-comment + :active (not buffer-read-only)] + ["Backward Rule" antlr-beginning-of-rule t] + ["Forward Rule" antlr-end-of-rule t] + ["Start of Rule Body" antlr-beginning-of-body + :active (antlr-inside-rule-p)] + ["End of Rule Body" antlr-end-of-body + :active (antlr-inside-rule-p)] + "---" + ["Backward Statement" c-beginning-of-statement t] + ["Forward Statement" c-end-of-statement t] + ["Backward Into Nomencl." c-backward-into-nomenclature t] + ["Forward Into Nomencl." c-forward-into-nomenclature t])) + + +;;;=========================================================================== +;;; font-lock +;;;=========================================================================== + +(defcustom antlr-font-lock-maximum-decoration 'inherit + "*The maximum decoration level for fontifying actions. +Value `none' means, do not fontify actions, just normal grammar code +according to `antlr-font-lock-additional-keywords'. Value `inherit' +means, use value of `font-lock-maximum-decoration'. Any other value is +interpreted as in `font-lock-maximum-decoration' with no level-0 +fontification, see `antlr-font-lock-keywords-alist'. + +While calculating the decoration level for actions, `major-mode' is +bound to `antlr-language'. For example, with value + ((java-mode . 2) (c++-mode . 0)) +Java actions are fontified with level 2 and C++ actions are not +fontified at all." + :type '(choice (const :tag "none" none) + (const :tag "inherit" inherit) + (const :tag "default" nil) + (const :tag "maximum" t) + (integer :tag "level" 1) + (repeat :menu-tag "mode specific" :tag "mode specific" + :value ((t . t)) + (cons :tag "Instance" + (radio :tag "Mode" + (const :tag "all" t) + (symbol :tag "name")) + (radio :tag "Decoration" + (const :tag "default" nil) + (const :tag "maximum" t) + (integer :tag "level" 1)))))) + +(defvar antlr-font-lock-keywords-alist + '((java-mode + (list) ; nil won't work (would use level-3) + java-font-lock-keywords-1 java-font-lock-keywords-2 + java-font-lock-keywords-3) + (c++-mode + (list) ; nil won't work (would use level-3) + c++-font-lock-keywords-1 c++-font-lock-keywords-2 + c++-font-lock-keywords-3)) + "List of font-lock keywords for actions in the grammar. +Each element in this list looks like + (MAJOR-MODE KEYWORD...) + +If `antlr-language' is equal to MAJOR-MODE, the KEYWORDs are the +font-lock keywords according to `font-lock-defaults' used for the code +in the grammar's actions and semantic predicates, see +`antlr-font-lock-maximum-decoration'.") + +(defvar antlr-font-lock-keyword-face 'antlr-font-lock-keyword-face) +(defface antlr-font-lock-keyword-face + '((((class color) (background light)) (:foreground "black" :bold t))) + "ANTLR keywords." + :group 'antlr) + +(defvar antlr-font-lock-ruledef-face 'antlr-font-lock-ruledef-face) +(defface antlr-font-lock-ruledef-face + '((((class color) (background light)) (:foreground "blue" :bold t))) + "ANTLR rule references (definition)." + :group 'antlr) + +(defvar antlr-font-lock-tokendef-face 'antlr-font-lock-tokendef-face) +(defface antlr-font-lock-tokendef-face + '((((class color) (background light)) (:foreground "brown3" :bold t))) + "ANTLR token references (definition)." + :group 'antlr) + +(defvar antlr-font-lock-ruleref-face 'antlr-font-lock-ruleref-face) +(defface antlr-font-lock-ruleref-face + '((((class color) (background light)) (:foreground "blue4"))) + "ANTLR rule references (usage)." + :group 'antlr) + +(defvar antlr-font-lock-tokenref-face 'antlr-font-lock-tokenref-face) +(defface antlr-font-lock-tokenref-face + '((((class color) (background light)) (:foreground "brown4"))) + "ANTLR token references (usage)." + :group 'antlr) + +(defvar antlr-font-lock-literal-face 'antlr-font-lock-literal-face) +(defface antlr-font-lock-literal-face + '((((class color) (background light)) (:foreground "brown4" :bold t))) + "ANTLR literal tokens consisting merely of letter-like characters." + :group 'antlr) + +(defvar antlr-font-lock-additional-keywords + `((antlr-invalidate-context-cache) + ("\\$setType[ \t]*(\\([A-Z\300-\326\330-\337]\\sw*\\))" + (1 antlr-font-lock-tokendef-face)) + ("\\$\\sw+" (0 font-lock-keyword-face)) + ;; the tokens are already fontified as string/docstrings: + (,(lambda (limit) + (antlr-re-search-forward "\"\\(\\sw\\(\\sw\\|-\\)*\\)\"" limit)) + (1 antlr-font-lock-literal-face t)) + (,(lambda (limit) + (antlr-re-search-forward + "^\\(class\\)[ \t]+\\([A-Z\300-\326\330-\337]\\sw*\\)[ \t]+\\(extends\\)[ \t]+\\([A-Z\300-\326\330-\337]\\sw*\\)[ \t]*;" limit)) + (1 antlr-font-lock-keyword-face) + (2 antlr-font-lock-ruledef-face) + (3 antlr-font-lock-keyword-face) + (4 (if (member (match-string 4) '("Lexer" "Parser" "TreeParser")) + 'antlr-font-lock-keyword-face + 'font-lock-type-face))) + (,(lambda (limit) + (antlr-re-search-forward + "\\<\\(header\\|options\\|tokens\\|exception\\|catch\\|returns\\)\\>" + limit)) + (1 antlr-font-lock-keyword-face)) + (,(lambda (limit) + (antlr-re-search-forward + "^\\(private\\|public\\|protected\\)\\>\\([ \t]+\\(\\sw+\\)\\)?" + limit)) + (1 font-lock-type-face) ; not XEmacs' java level-3 fruit salad + (3 (if (antlr-upcase-p (char-after (match-beginning 3))) + 'antlr-font-lock-tokendef-face + 'antlr-font-lock-ruledef-face) nil t)) + (,(lambda (limit) + (antlr-re-search-forward "^\\sw+" limit)) + (0 (if (antlr-upcase-p (char-after (match-beginning 0))) + 'antlr-font-lock-tokendef-face + 'antlr-font-lock-ruledef-face) nil t)) + (,(lambda (limit) + ;; not only before a rule ref, also before a literal + (antlr-re-search-forward "\\<\\(\\sw+\\)[ \t]*:" limit)) + (1 font-lock-variable-name-face)) + (,(lambda (limit) + (antlr-re-search-forward "\\<\\(\\sw+[ \t]*=[ \t]*\\)?\\(\\sw+[ \t]*:[ \t]*\\)?\\(\\sw+\\)" limit)) + ;;(1 antlr-font-lock-default-face nil t) ; fool java-font-lock-keywords + (3 (if (antlr-upcase-p (char-after (match-beginning 3))) + 'antlr-font-lock-tokenref-face + 'antlr-font-lock-ruleref-face)))) + "Font-lock keywords for ANTLR's normal grammar code. +See `antlr-font-lock-keywords-alist' for the keywords of actions.") + +(defvar antlr-font-lock-defaults + '(antlr-font-lock-keywords + nil nil ((?_ . "w") (?\( . ".") (?\) . ".")) beginning-of-defun) + "Font-lock defaults used for ANTLR syntax coloring. +The SYNTAX-ALIST element is also used to initialize +`antlr-action-syntax-table'.") + + +;;;=========================================================================== +;;; Internal variables +;;;=========================================================================== + +(defvar antlr-mode-hook nil + "Hook called by `antlr-mode'.") + +;; used for "in Java/C++ code" = syntactic-depth>0 +(defvar antlr-action-syntax-table nil + "Syntax table used for ANTLR action parsing. +Initialized by `java-mode-syntax-table', i.e., the syntax table used for +grammar files, changed by SYNTAX-ALIST in `antlr-font-lock-defaults'. +This table should be selected if you use `buffer-syntactic-context' and +`buffer-syntactic-context-depth' in order not to confuse their +context_cache.") + +(defvar antlr-mode-abbrev-table nil + "Abbreviation table used in `antlr-mode' buffers.") +(define-abbrev-table 'antlr-mode-abbrev-table ()) + + + +;;;;########################################################################## +;;;; The Code +;;;;########################################################################## + + +;;;=========================================================================== +;;; Syntax functions -- Emacs vs XEmacs dependent +;;;=========================================================================== + +;; From help.el (XEmacs-21.1) +(defmacro antlr-with-syntax-table (syntab &rest body) + `(let ((stab (syntax-table))) + (unwind-protect + (progn (set-syntax-table (copy-syntax-table ,syntab)) ,@body) + (set-syntax-table stab)))) +(put 'antlr-with-syntax-table 'lisp-indent-function 1) +(put 'antlr-with-syntax-table 'edebug-form-spec '(form body)) + +(defun antlr-scan-sexps-internal (from count &optional dummy no-error) +;; checkdoc-params: (from count dummy) + "Like `scan-sexps' but with additional arguments. +When optional arg NO-ERROR is non-nil, `scan-sexps' will return nil +instead of signalling an error." + (if no-error + (condition-case nil + (scan-sexps from count) + (t nil)) + (scan-sexps from count))) + +(defun antlr-xemacs-bug-workaround (&rest dummies) +;; checkdoc-params: (dummies) + "Invalidate context_cache for syntactical context information." + ;; XEmacs bug workaround + (save-excursion + (set-buffer (get-buffer-create " ANTLR XEmacs bug workaround")) + (buffer-syntactic-context-depth)) + nil) + +(defun antlr-fast-syntactic-context () + "Return some syntactic context information. +Return `string' if point is within a string, `block-comment' or +`comment' is point is within a comment or the depth within all +parenthesis-syntax delimiters at point otherwise. +WARNING: this may alter `match-data'." + (or (buffer-syntactic-context) (buffer-syntactic-context-depth))) + +(defun antlr-slow-syntactic-context () + "Return some syntactic context information. +Return `string' if point is within a string, `block-comment' or +`comment' is point is within a comment or the depth within all +parenthesis-syntax delimiters at point otherwise. +WARNING: this may alter `match-data'." + (let ((orig (point))) + (beginning-of-defun) + (let ((state (parse-partial-sexp (point) orig))) + (goto-char orig) + (cond ((nth 3 state) 'string) + ((nth 4 state) 'comment) ; block-comment? -- we don't care + (t (car state)))))) + + +;;;=========================================================================== +;;; Misc functions +;;;=========================================================================== + +(defun antlr-upcase-p (char) + "Non-nil, if CHAR is an uppercase character (if CHAR was a char)." + ;; in XEmacs, upcase only works for ASCII + (or (and (<= ?A char) (<= char ?Z)) + (and (<= ?\300 char) (<= char ?\337)))) ; ?\327 is no letter + +(defun antlr-re-search-forward (regexp bound) + "Search forward from point for regular expression REGEXP. +Set point to the end of the occurrence found, and return point. Return +nil if no occurence was found. Do not search within comments, strings +and actions/semantic predicates. BOUND bounds the search; it is a +buffer position. See also the functions `match-beginning', `match-end' +and `replace-match'." + ;; WARNING: Should only be used with `antlr-action-syntax-table'! + (let ((continue t)) + (while (and (re-search-forward regexp bound 'limit) + (save-match-data + (if (eq (antlr-syntactic-context) 0) (setq continue nil) t)))) + (if continue nil (point)))) + +(defun antlr-search-forward (string) + "Search forward from point for STRING. +Set point to the end of the occurrence found, and return point. Return +nil if no occurence was found. Do not search within comments, strings +and actions/semantic predicates." + ;; WARNING: Should only be used with `antlr-action-syntax-table'! + (let ((continue t)) + (while (and (search-forward string nil 'limit) + (if (eq (antlr-syntactic-context) 0) (setq continue nil) t))) + (if continue nil (point)))) + +(defun antlr-search-backward (string) + "Search backward from point for STRING. +Set point to the beginning of the occurrence found, and return point. +Return nil if no occurence was found. Do not search within comments, +strings and actions/semantic predicates." + ;; WARNING: Should only be used with `antlr-action-syntax-table'! + (let ((continue t)) + (while (and (search-backward string nil 'limit) + (if (eq (antlr-syntactic-context) 0) (setq continue nil) t))) + (if continue nil (point)))) + +(defsubst antlr-skip-sexps (count) + "Skip the next COUNT balanced expressions and the comments after it. +Return position before the comments after the last expression." + (goto-char (or (antlr-scan-sexps (point) count nil t) (point-max))) + (prog1 (point) + (c-forward-syntactic-ws))) + + +;;;=========================================================================== +;;; font-lock +;;;=========================================================================== + +(defun antlr-font-lock-keywords () + "Return font-lock keywords for current buffer. +See `antlr-font-lock-additional-keywords', `antlr-language' and +`antlr-font-lock-maximum-decoration'." + (if (eq antlr-font-lock-maximum-decoration 'none) + antlr-font-lock-additional-keywords + (append antlr-font-lock-additional-keywords + (eval (let ((major-mode antlr-language)) ; dynamic + (font-lock-choose-keywords + (cdr (assq antlr-language + antlr-font-lock-keywords-alist)) + (if (eq antlr-font-lock-maximum-decoration 'inherit) + font-lock-maximum-decoration + antlr-font-lock-maximum-decoration))))))) + + +;;;=========================================================================== +;;; imenu support +;;;=========================================================================== + +(defun antlr-imenu-create-index-function () + "Return imenu index-alist for ANTLR gramar files." + (let ((items nil) + (lexer nil) + (parser nil) + (treeparser nil) + (misc nil) + (classes nil) + (semi (point-max))) + ;; Using `imenu-progress-message' would require imenu for compilation -- + ;; nobody is missing these messages... + (antlr-with-syntax-table antlr-action-syntax-table + ;; We stick to the imenu standard and search backwards, although I don't + ;; think this is right. It is slower and more likely not to work during + ;; editing (you are more likely to add functions to the end of the file). + (while semi + (goto-char semi) + (if (setq semi (antlr-search-backward ";")) + (progn (forward-char) (antlr-skip-exception-part t)) + (antlr-skip-file-prelude t)) + (if (looking-at "{") (antlr-skip-sexps 1)) + (if (looking-at "class[ \t]+\\([A-Z\300-\326\330-\337]\\sw*\\)[ \t]+extends[ \t]+\\([A-Z\300-\326\330-\337]\\sw*\\)[ \t]*;") + (progn + (push (cons (match-string 1) + (if imenu-use-markers + (copy-marker (match-beginning 1)) + (match-beginning 1))) + classes) + (if items + (let ((super (match-string 2))) + (cond ((string-equal super "Parser") + (setq parser (nconc items parser))) + ((string-equal super "Lexer") + (setq lexer (nconc items lexer))) + ((string-equal super "TreeParser") + (setq treeparser (nconc items treeparser))) + (t + (setq misc (nconc items misc)))) + (setq items nil)))) + (if (looking-at "p\\(ublic\\|rotected\\|rivate\\)") + (antlr-skip-sexps 1)) + (when (looking-at "\\sw+") + (push (cons (match-string 0) + (if imenu-use-markers + (copy-marker (match-beginning 0)) + (match-beginning 0))) + items))))) + (or items ; outside any class + (prog1 (setq items misc) (setq misc nil)) + (prog1 (setq items parser) (setq parser nil)) + (prog1 (setq items lexer) (setq lexer nil)) + (prog1 (setq items treeparser) (setq treeparser nil))) + (if misc (push (cons "Miscellaneous" misc) items)) + (if treeparser (push (cons "TreeParser" treeparser) items)) + (if lexer (push (cons "Lexer" lexer) items)) + (if parser (push (cons "Parser" parser) items)) + (if classes (cons (cons "Classes" classes) items) items))) + + +;;;=========================================================================== +;;; Parse grammar files (internal functions) +;;;=========================================================================== + +(defun antlr-skip-exception-part (skip-comment) + "Skip exception part of current rule, i.e., everything after `;'. +This also includes the options and tokens part of a grammar class +header. If SKIP-COMMENT is non-nil, also skip the comment after that +part." + (let ((pos (point)) + (class nil)) + (c-forward-syntactic-ws) + (while (looking-at "options\\>\\|tokens\\>") + (setq class t) + (setq pos (antlr-skip-sexps 2))) + (if class + ;; Problem: an action only belongs to a class def, not a normal rule. + ;; But checking the current rule type is too expensive => only expect + ;; an action if we have found an option or tokens part. + (if (looking-at "{") (setq pos (antlr-skip-sexps 1))) + (while (looking-at "exception\\>") + (setq pos (antlr-skip-sexps 1)) + (if (looking-at "\\[") (setq pos (antlr-skip-sexps 1))) + (while (looking-at "catch\\>") + (setq pos (antlr-skip-sexps 3))))) + (or skip-comment (goto-char pos)))) + +(defun antlr-skip-file-prelude (skip-comment) + "Skip the file prelude: the header and file options. +If SKIP-COMMENT is non-nil, also skip the comment after that part." + (let* ((pos (point)) + (pos0 pos)) + (c-forward-syntactic-ws) + (if skip-comment (setq pos0 (point))) + (if (looking-at "header\\>") (setq pos (antlr-skip-sexps 2))) + (if (looking-at "options\\>") (setq pos (antlr-skip-sexps 2))) + (or skip-comment (goto-char pos)) + pos0)) + +(defun antlr-next-rule (arg skip-comment) + "Move forward to next end of rule. Do it ARG many times. +A grammar class header and the file prelude are also considered as a +rule. Negative argument ARG means move back to ARGth preceding end of +rule. The behaviour is not defined when ARG is zero. If SKIP-COMMENT +is non-nil, move to beginning of the rule." + ;; WARNING: Should only be used with `antlr-action-syntax-table'! + ;; PRE: ARG<>0 + (let ((pos (point)) + (beg (point))) + ;; first look whether point is in exception part + (if (antlr-search-backward ";") + (progn + (setq beg (point)) + (forward-char) + (antlr-skip-exception-part skip-comment)) + (antlr-skip-file-prelude skip-comment)) + (if (< arg 0) + (unless (and (< (point) pos) (zerop (incf arg))) + ;; if we have moved backward, we already moved one defun backward + (goto-char beg) ; rewind (to ";" / point) + (while (and arg (<= (incf arg) 0)) + (if (antlr-search-backward ";") + (setq beg (point)) + (when (>= arg -1) + ;; try file prelude: + (setq pos (antlr-skip-file-prelude skip-comment)) + (if (zerop arg) + (if (>= (point) beg) + (goto-char (if (>= pos beg) (point-min) pos))) + (goto-char (if (or (>= (point) beg) (= (point) pos)) + (point-min) pos)))) + (setq arg nil))) + (when arg ; always found a ";" + (forward-char) + (antlr-skip-exception-part skip-comment))) + (if (<= (point) pos) ; moved backward? + (goto-char pos) ; rewind + (decf arg)) ; already moved one defun forward + (unless (zerop arg) + (while (>= (decf arg) 0) + (antlr-search-forward ";")) + (antlr-skip-exception-part skip-comment))))) + +(defun antlr-outside-rule-p () + "Non-nil if point is outside a grammar rule. +Move to the beginning of the current rule if point is inside a rule." + ;; WARNING: Should only be used with `antlr-action-syntax-table'! + (let ((pos (point))) + (antlr-next-rule -1 nil) + (let ((between (or (bobp) (< (point) pos)))) + (c-forward-syntactic-ws) + (and between (> (point) pos) (goto-char pos))))) + + +;;;=========================================================================== +;;; Parse grammar files (commands) +;;;=========================================================================== +;; No (interactive "_") in Emacs... use `zmacs-region-stays'. + +(defun antlr-inside-rule-p () + "Non-nil if point is inside a grammar rule. +A grammar class header and the file prelude are also considered as a +rule." + (save-excursion + (antlr-with-syntax-table antlr-action-syntax-table + (not (antlr-outside-rule-p))))) + +(defun antlr-end-of-rule (&optional arg) + "Move forward to next end of rule. Do it ARG [default: 1] many times. +A grammar class header and the file prelude are also considered as a +rule. Negative argument ARG means move back to ARGth preceding end of +rule. If ARG is zero, run `antlr-end-of-body'." + (interactive "p") + (if (zerop arg) + (antlr-end-of-body) + (antlr-with-syntax-table antlr-action-syntax-table + (antlr-next-rule arg nil)) + (setq zmacs-region-stays t))) + +(defun antlr-beginning-of-rule (&optional arg) + "Move backward to preceding beginning of rule. Do it ARG many times. +A grammar class header and the file prelude are also considered as a +rule. Negative argument ARG means move forward to ARGth next beginning +of rule. If ARG is zero, run `antlr-beginning-of-body'." + (interactive "p") + (if (zerop arg) + (antlr-beginning-of-body) + (antlr-with-syntax-table antlr-action-syntax-table + (antlr-next-rule (- arg) t)) + (setq zmacs-region-stays t))) + +(defun antlr-end-of-body (&optional msg) + "Move to position after the `;' of the current rule. +A grammar class header is also considered as a rule. With optional +prefix arg MSG, move to `:'." + (interactive) + (antlr-with-syntax-table antlr-action-syntax-table + (let ((orig (point))) + (if (antlr-outside-rule-p) + (error "Outside an ANTLR rule")) + (let ((bor (point))) + (when (< (antlr-skip-file-prelude t) (point)) + ;; Yes, we are in the file prelude + (goto-char orig) + (error (or msg "The file prelude is without `;'"))) + (antlr-search-forward ";") + (when msg + (when (< (point) + (progn (goto-char bor) + (or (antlr-search-forward ":") (point-max)))) + (goto-char orig) + (error msg)) + (c-forward-syntactic-ws))))) + (setq zmacs-region-stays t)) + +(defun antlr-beginning-of-body () + "Move to the first element after the `:' of the current rule." + (interactive) + (antlr-end-of-body "Class headers and the file prelude are without `:'")) + + +;;;=========================================================================== +;;; Indentation +;;;=========================================================================== + +(defun antlr-indent-line () + "Indent the current line as ANTLR grammar code. +The indentation of non-comment lines are calculated by `c-basic-offset', +multiplied by: + - the level of the paren/brace/bracket depth, + - plus 0/2/1, depending on the position inside the rule: header, body, + exception part, + - minus 1 if `antlr-indent-item-regexp' matches the beginning of the + line starting from the first non-blank. + +Lines inside block commments are not changed or indented by +`c-indent-line', see `antlr-indent-comment'." + (let ((orig (point)) bol boi indent syntax) + (beginning-of-line) + (setq bol (point)) + (skip-chars-forward " \t") + (setq boi (point)) + ;; check syntax at beginning of indentation ------------------------------ + (antlr-with-syntax-table antlr-action-syntax-table + (antlr-invalidate-context-cache) + (cond ((symbolp (setq syntax (antlr-syntactic-context))) + (setq indent nil)) ; block-comments, strings, (comments) + ((progn + (antlr-next-rule -1 t) + (if (antlr-search-forward ":") (< boi (1- (point))) t)) + (setq indent 0)) ; in rule header + ((if (antlr-search-forward ";") (< boi (point)) t) + (setq indent 2)) ; in rule body + (t + (forward-char) + (antlr-skip-exception-part nil) + (setq indent (if (> (point) boi) 1 0))))) ; in exception part? + ;; compute the corresponding indentation and indent ---------------------- + (if (null indent) + (progn + (goto-char orig) + (and (eq antlr-indent-comment t) + (not (eq syntax 'string)) + (c-indent-line))) + ;; do it ourselves + (goto-char boi) + (antlr-invalidate-context-cache) + (incf indent (antlr-syntactic-context)) + (and (> indent 0) (looking-at antlr-indent-item-regexp) (decf indent)) + (setq indent (* indent c-basic-offset)) + ;; the usual major-mode indent stuff: + (setq orig (- (point-max) orig)) + (unless (= (current-column) indent) + (delete-region bol boi) + (beginning-of-line) + (indent-to indent)) + ;; If initial point was within line's indentation, + ;; position after the indentation. Else stay at same point in text. + (if (> (- (point-max) orig) (point)) + (goto-char (- (point-max) orig)))))) + +(defun antlr-indent-command (&optional arg) + "Indent the current line or insert tabs/spaces. +With optional prefix argument ARG or if the previous command was this +command, insert ARG tabs or spaces according to `indent-tabs-mode'. +Otherwise, indent the current line with `antlr-indent-line'." + (interactive "P") + (if (or arg (eq last-command 'antlr-indent-command)) + (insert-tab arg) + (let ((antlr-indent-comment (and antlr-indent-comment t))) ; dynamic + (antlr-indent-line)))) + + +;;;=========================================================================== +;;; Mode entry +;;;=========================================================================== + +(defun antlr-c-common-init () + "Like `c-common-init' except menu, auto-hungry and c-style stuff." + ;; X/Emacs 20 only + (make-local-variable 'paragraph-start) + (make-local-variable 'paragraph-separate) + (make-local-variable 'paragraph-ignore-fill-prefix) + (make-local-variable 'require-final-newline) + (make-local-variable 'parse-sexp-ignore-comments) + (make-local-variable 'indent-line-function) + (make-local-variable 'indent-region-function) + (make-local-variable 'comment-start) + (make-local-variable 'comment-end) + (make-local-variable 'comment-column) + (make-local-variable 'comment-start-skip) + (make-local-variable 'comment-multi-line) + (make-local-variable 'outline-regexp) + (make-local-variable 'outline-level) + (make-local-variable 'adaptive-fill-regexp) + (make-local-variable 'adaptive-fill-mode) + (make-local-variable 'imenu-generic-expression) ;set in the mode functions + (and (boundp 'comment-line-break-function) + (make-local-variable 'comment-line-break-function)) + ;; Emacs 19.30 and beyond only, AFAIK + (if (boundp 'fill-paragraph-function) + (progn + (make-local-variable 'fill-paragraph-function) + (setq fill-paragraph-function 'c-fill-paragraph))) + ;; now set their values + (setq paragraph-start (concat page-delimiter "\\|$") + paragraph-separate paragraph-start + paragraph-ignore-fill-prefix t + require-final-newline t + parse-sexp-ignore-comments t + indent-line-function 'c-indent-line + indent-region-function 'c-indent-region + outline-regexp "[^#\n\^M]" + outline-level 'c-outline-level + comment-column 32 + comment-start-skip "/\\*+ *\\|// *" + comment-multi-line nil + comment-line-break-function 'c-comment-line-break-function + adaptive-fill-regexp nil + adaptive-fill-mode nil) + ;; we have to do something special for c-offsets-alist so that the + ;; buffer local value has its own alist structure. + (setq c-offsets-alist (copy-alist c-offsets-alist)) + ;; setup the comment indent variable in a Emacs version portable way + ;; ignore any byte compiler warnings you might get here + (make-local-variable 'comment-indent-function) + (setq comment-indent-function 'c-comment-indent)) + +(defun antlr-language-for-option (option-value) + "Find element in `antlr-language-alist' for OPTION-VALUE." + ;; Like (find OPTION-VALUE antlr-language-alist :key 'cddr :test 'member) + (let ((seq antlr-language-alist) + r) + (while seq + (setq r (pop seq)) + (if (member option-value (cddr r)) + (setq seq nil) ; stop + (setq r nil))) ; no result yet + r)) + +;;;###autoload +(defun antlr-mode () + "Major mode for editing ANTLR grammar files. +\\{antlr-mode-map}" + (interactive) + (c-initialize-cc-mode) ; for java syntax table + (kill-all-local-variables) + ;; ANTLR specific ---------------------------------------------------------- + (setq major-mode 'antlr-mode + mode-name "Antlr") + (setq local-abbrev-table antlr-mode-abbrev-table) + (set-syntax-table java-mode-syntax-table) + (unless antlr-action-syntax-table + (let ((slist (nth 3 antlr-font-lock-defaults))) + (setq antlr-action-syntax-table + (copy-syntax-table java-mode-syntax-table)) + (while slist + (modify-syntax-entry (caar slist) (cdar slist) + antlr-action-syntax-table) + (setq slist (cdr slist))))) + (use-local-map antlr-mode-map) + (make-local-variable 'antlr-language) + (unless antlr-language + (save-excursion + (goto-char (point-min)) + (setq antlr-language + (car (or (and (re-search-forward (cdr antlr-language-limit-n-regexp) + (car antlr-language-limit-n-regexp) + t) + (antlr-language-for-option (match-string 1))) + (antlr-language-for-option nil)))))) + (if (stringp (cadr (assq antlr-language antlr-language-alist))) + (setq mode-name + (concat "Antlr/" + (cadr (assq antlr-language antlr-language-alist))))) + ;; indentation, for the C engine ------------------------------------------- + (antlr-c-common-init) + (setq indent-line-function 'antlr-indent-line + indent-region-function nil) ; too lazy + (setq comment-start "// " + comment-end "") + (c-set-style "java") + (if (eq antlr-language 'c++-mode) + (setq c-conditional-key c-C++-conditional-key + c-comment-start-regexp c-C++-comment-start-regexp + c-class-key c-C++-class-key + c-extra-toplevel-key c-C++-extra-toplevel-key + c-access-key c-C++-access-key + c-recognize-knr-p nil) + (setq c-conditional-key c-Java-conditional-key + c-comment-start-regexp c-Java-comment-start-regexp + c-class-key c-Java-class-key + c-method-key nil + c-baseclass-key nil + c-recognize-knr-p nil + c-access-key c-Java-access-key) + (and (boundp 'c-inexpr-class-key) (boundp 'c-Java-inexpr-class-key) + (setq c-inexpr-class-key c-Java-inexpr-class-key))) + ;; various ----------------------------------------------------------------- + (make-local-variable 'font-lock-defaults) + (setq font-lock-defaults antlr-font-lock-defaults) + (easy-menu-add antlr-mode-menu) + (make-local-variable 'imenu-create-index-function) + (setq imenu-create-index-function 'antlr-imenu-create-index-function) + (make-local-variable 'imenu-generic-expression) + (setq imenu-generic-expression t) ; fool stupid test + (and antlr-imenu-name ; there should be a global variable... + (fboundp 'imenu-add-to-menubar) + (imenu-add-to-menubar + (if (stringp antlr-imenu-name) antlr-imenu-name "Index"))) + (antlr-set-tabs) + (run-hooks 'antlr-mode-hook)) + +;;;###autoload +(defun antlr-set-tabs () + "Use ANTLR's convention for TABs according to `antlr-tab-offset-alist'. +Used in `antlr-mode'. Also a useful function in `java-mode-hook'." + (if buffer-file-name + (let ((alist antlr-tab-offset-alist) elem) + (while alist + (setq elem (pop alist)) + (and (or (null (car elem)) (eq (car elem) major-mode)) + (or (null (cadr elem)) + (string-match (cadr elem) buffer-file-name)) + (setq tab-width (caddr elem) + indent-tabs-mode (cadddr elem) + alist nil)))))) + +;;; antlr-mode.el ends here