[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] master 90a6f8d 2/2: * lisp/progmodes/octave.el: Use gramma
From: |
Stefan Monnier |
Subject: |
[Emacs-diffs] master 90a6f8d 2/2: * lisp/progmodes/octave.el: Use grammar more; Handle enumeration fun |
Date: |
Tue, 29 Sep 2015 19:13:50 +0000 |
branch: master
commit 90a6f8d0741eb5391c204f059c845c361e615b49
Author: Stefan Monnier <address@hidden>
Commit: Stefan Monnier <address@hidden>
* lisp/progmodes/octave.el: Use grammar more; Handle enumeration fun
Remove redundant :group keyword args.
(octave-begin-keywords, octave-else-keywords, octave-end-keywords):
Remove variables.
(octave-operator-table, octave-smie-bnf-table): Use let-when-compile to
turn them into compile-time variables.
Auto-generate the "foo ... end" rules from the "foo ... endfoo" rules.
Add rules for break, continue, return, global, and persistent.
Refine the rule for "until".
(octave-smie--funcall-p, octave-smie--end-index-p)
(octave-smie--in-parens-p): New functions.
(octave-smie-backward-token, octave-smie-forward-token): Use them to
distinguish the "enumeration" function and the "end" index from
their corresponding keywords.
(octave--block-offset-keywords): New constant.
(octave-smie-rules): Use it. Adjust rules for new global/persistent
parsing.
(octave-reserved-words): Redefine using octave-smie-grammar.
(octave-font-lock-keywords): Use octave-smie--funcall-p and
octave-smie--end-index-p.
---
lisp/progmodes/octave.el | 316 ++++++++++++++++++++++++----------------------
test/indent/octave.m | 22 ++++
2 files changed, 186 insertions(+), 152 deletions(-)
diff --git a/lisp/progmodes/octave.el b/lisp/progmodes/octave.el
index 70a2b1a..b54b88d 100644
--- a/lisp/progmodes/octave.el
+++ b/lisp/progmodes/octave.el
@@ -82,25 +82,6 @@ Used in `octave-mode' and `inferior-octave-mode' buffers.")
(defvar octave-comment-start-skip "\\(^\\|\\S<\\)\\(?:%!\\|\\s<+\\)\\s-*"
"Octave-specific `comment-start-skip' (which see).")
-(defvar octave-begin-keywords
- '("classdef" "do" "enumeration" "events" "for" "function" "if" "methods"
- "parfor" "properties" "switch" "try" "unwind_protect" "while"))
-
-(defvar octave-else-keywords
- '("case" "catch" "else" "elseif" "otherwise" "unwind_protect_cleanup"))
-
-(defvar octave-end-keywords
- '("endclassdef" "endenumeration" "endevents" "endfor" "endfunction" "endif"
- "endmethods" "endparfor" "endproperties" "endswitch" "end_try_catch"
- "end_unwind_protect" "endwhile" "until" "end"))
-
-(defvar octave-reserved-words
- (append octave-begin-keywords
- octave-else-keywords
- octave-end-keywords
- '("break" "continue" "global" "persistent" "return"))
- "Reserved words in Octave.")
-
(defvar octave-function-header-regexp
(concat "^\\s-*\\_<\\(function\\)\\_>"
"\\([^=;(\n]*=[ \t]*\\|[ \t]*\\)\\(\\(?:\\w\\|\\s_\\)+\\)\\_>")
@@ -231,20 +212,17 @@ parenthetical grouping.")
(defcustom octave-font-lock-texinfo-comment t
"Control whether to highlight the texinfo comment block."
:type 'boolean
- :group 'octave
:version "24.4")
(defcustom octave-blink-matching-block t
"Control the blinking of matching Octave block keywords.
Non-nil means show matching begin of block when inserting a space,
newline or semicolon after an else or end keyword."
- :type 'boolean
- :group 'octave)
+ :type 'boolean)
(defcustom octave-block-offset 2
"Extra indentation applied to statements in Octave block structures."
- :type 'integer
- :group 'octave)
+ :type 'integer)
(defvar octave-block-comment-start
(concat (make-string 2 octave-comment-char) " ")
@@ -252,8 +230,7 @@ newline or semicolon after an else or end keyword."
(defcustom octave-continuation-offset 4
"Extra indentation applied to Octave continuation lines."
- :type 'integer
- :group 'octave)
+ :type 'integer)
(eval-and-compile
(defconst octave-continuation-marker-regexp "\\\\\\|\\.\\.\\."))
@@ -274,109 +251,135 @@ newline or semicolon after an else or end keyword."
(defcustom octave-mode-hook nil
"Hook to be run when Octave mode is started."
- :type 'hook
- :group 'octave)
+ :type 'hook)
(defcustom octave-send-show-buffer t
"Non-nil means display `inferior-octave-buffer' after sending to it."
- :type 'boolean
- :group 'octave)
+ :type 'boolean)
(defcustom octave-send-line-auto-forward t
"Control auto-forward after sending to the inferior Octave process.
Non-nil means always go to the next Octave code line after sending."
- :type 'boolean
- :group 'octave)
+ :type 'boolean)
(defcustom octave-send-echo-input t
"Non-nil means echo input sent to the inferior Octave process."
- :type 'boolean
- :group 'octave)
+ :type 'boolean)
;;; SMIE indentation
(require 'smie)
-;; Use '__operators__' in Octave REPL to get a full list.
-(defconst octave-operator-table
- '((assoc ";" "\n") (assoc ",") ; The doc claims they have equal precedence!?
- (right "=" "+=" "-=" "*=" "/=")
- (assoc "&&") (assoc "||") ; The doc claims they have equal precedence!?
- (assoc "&") (assoc "|") ; The doc claims they have equal precedence!?
- (nonassoc "<" "<=" "==" ">=" ">" "!=" "~=")
- (nonassoc ":") ;No idea what this is.
- (assoc "+" "-")
- (assoc "*" "/" "\\" ".\\" ".*" "./")
- (nonassoc "'" ".'")
- (nonassoc "++" "--" "!" "~") ;And unary "+" and "-".
- (right "^" "**" ".^" ".**")
- ;; It's not really an operator, but for indentation purposes it
- ;; could be convenient to treat it as one.
- (assoc "...")))
-
-(defconst octave-smie-bnf-table
- '((atom)
- ;; We can't distinguish the first element in a sequence with
- ;; precedence grammars, so we can't distinguish the condition
- ;; if the `if' from the subsequent body, for example.
- ;; This has to be done later in the indentation rules.
- (exp (exp "\n" exp)
- ;; We need to mention at least one of the operators in this part
- ;; of the grammar: if the BNF and the operator table have
- ;; no overlap, SMIE can't know how they relate.
- (exp ";" exp)
- ("try" exp "catch" exp "end_try_catch")
- ("try" exp "catch" exp "end")
- ("unwind_protect" exp
- "unwind_protect_cleanup" exp "end_unwind_protect")
- ("unwind_protect" exp "unwind_protect_cleanup" exp "end")
- ("for" exp "endfor")
- ("for" exp "end")
- ("parfor" exp "endparfor")
- ("parfor" exp "end")
- ("do" exp "until" atom)
- ("while" exp "endwhile")
- ("while" exp "end")
- ("if" exp "endif")
- ("if" exp "else" exp "endif")
- ("if" exp "elseif" exp "else" exp "endif")
- ("if" exp "elseif" exp "elseif" exp "else" exp "endif")
- ("if" exp "elseif" exp "elseif" exp "else" exp "end")
- ("switch" exp "case" exp "endswitch")
- ("switch" exp "case" exp "otherwise" exp "endswitch")
- ("switch" exp "case" exp "case" exp "otherwise" exp "endswitch")
- ("switch" exp "case" exp "case" exp "otherwise" exp "end")
- ("function" exp "endfunction")
- ("function" exp "end")
- ("enumeration" exp "endenumeration")
- ("enumeration" exp "end")
- ("events" exp "endevents")
- ("events" exp "end")
- ("methods" exp "endmethods")
- ("methods" exp "end")
- ("properties" exp "endproperties")
- ("properties" exp "end")
- ("classdef" exp "endclassdef")
- ("classdef" exp "end"))
- ;; (fundesc (atom "=" atom))
- ))
+(let-when-compile
+ ((operator-table
+ ;; Use '__operators__' in Octave REPL to get a full list?
+ '((assoc ";" "\n") (assoc ",") ;The doc says they have equal precedence!?
+ (right "=" "+=" "-=" "*=" "/=")
+ (assoc "&&") (assoc "||") ; The doc claims they have equal precedence!?
+ (assoc "&") (assoc "|") ; The doc claims they have equal precedence!?
+ (nonassoc "<" "<=" "==" ">=" ">" "!=" "~=")
+ (nonassoc ":") ;No idea what this is.
+ (assoc "+" "-")
+ (assoc "*" "/" "\\" ".\\" ".*" "./")
+ (nonassoc "'" ".'")
+ (nonassoc "++" "--" "!" "~") ;And unary "+" and "-".
+ (right "^" "**" ".^" ".**")
+ ;; It's not really an operator, but for indentation purposes it
+ ;; could be convenient to treat it as one.
+ (assoc "...")))
+
+ (matchedrules
+ ;; We can't distinguish the first element in a sequence with
+ ;; precedence grammars, so we can't distinguish the condition
+ ;; of the `if' from the subsequent body, for example.
+ ;; This has to be done later in the indentation rules.
+ '(("try" exp "catch" exp "end_try_catch")
+ ("unwind_protect" exp
+ "unwind_protect_cleanup" exp "end_unwind_protect")
+ ("for" exp "endfor")
+ ("parfor" exp "endparfor")
+ ("while" exp "endwhile")
+ ("if" exp "endif")
+ ("if" exp "else" exp "endif")
+ ("if" exp "elseif" exp "else" exp "endif")
+ ("if" exp "elseif" exp "elseif" exp "else" exp "endif")
+ ("switch" exp "case" exp "endswitch")
+ ("switch" exp "case" exp "otherwise" exp "endswitch")
+ ("switch" exp "case" exp "case" exp "otherwise" exp "endswitch")
+ ("function" exp "endfunction")
+ ("enumeration" exp "endenumeration")
+ ("events" exp "endevents")
+ ("methods" exp "endmethods")
+ ("properties" exp "endproperties")
+ ("classdef" exp "endclassdef")
+ ))
+
+ (bnf-table
+ `((atom)
+ ;; FIXME: We don't parse these declarations correctly since
+ ;; SMIE *really* likes to parse "a b = 2 c" as "(a b) = (2 c)".
+ ;; IOW to do it right, we'd need to change octave-smie-*ward-token
+ ;; so that the spaces between vars in var-decls are lexed as
+ ;; something like ",".
+ ;; Doesn't seem worth the trouble/slowdown for now.
+ ;; We could hack smie-rules so as to work around the bad parse,
+ ;; but even that doesn't seem worth the trouble.
+ (var-decls (atom "=" atom)) ;; (var-decls "," var-decls)
+ (single-exp (atom "=" atom))
+ (exp (exp "\n" exp)
+ ;; We need to mention at least one of the operators in this part
+ ;; of the grammar: if the BNF and the operator table have
+ ;; no overlap, SMIE can't know how they relate.
+ (exp ";" exp)
+ ("do" exp "until" single-exp)
+ ,@matchedrules
+ ;; For every rule that ends in "endfoo", add a corresponding
+ ;; rule which uses "end" instead.
+ ,@(mapcar (lambda (rule) (nconc (butlast rule) '("end")))
+ matchedrules)
+ ("global" var-decls) ("persistent" var-decls)
+ ;; These aren't super-important, but having them here
+ ;; makes it easier to extract all keywords.
+ ("break") ("continue") ("return")
+ ;; The following rules do not correspond to valid code AFAIK,
+ ;; but they lead to a grammar that degrades more gracefully
+ ;; on incomplete/incorrect code. It also helps us in
+ ;; computing octave--block-offset-keywords.
+ ("try" exp "end") ("unwind_protect" exp "end")
+ )
+ ;; (fundesc (atom "=" atom))
+ )))
(defconst octave-smie-grammar
- (smie-prec2->grammar
- (smie-merge-prec2s
- (smie-bnf->prec2 octave-smie-bnf-table
- '((assoc "\n" ";")))
+ (eval-when-compile
+ (smie-prec2->grammar
+ (smie-merge-prec2s
+ (smie-bnf->prec2 bnf-table '((assoc "\n" ";")))
+ (smie-precs->prec2 operator-table)))))
- (smie-precs->prec2 octave-operator-table))))
+(defconst octave-operator-regexp
+ (eval-when-compile
+ (regexp-opt (remove "\n" (apply #'append
+ (mapcar #'cdr operator-table)))))))
;; Tokenizing needs to be refined so that ";;" is treated as two
;; tokens and also so as to recognize the \n separator (and
;; corresponding continuation lines).
-(defconst octave-operator-regexp
- (regexp-opt (remove "\n" (apply 'append
- (mapcar 'cdr octave-operator-table)))))
+(defun octave-smie--funcall-p ()
+ "Return non-nil if we're in an expression context. Moves point."
+ (looking-at "[ \t]*("))
+
+(defun octave-smie--end-index-p ()
+ (let ((ppss (syntax-ppss)))
+ (and (nth 1 ppss)
+ (memq (char-after (nth 1 ppss)) '(?\( ?\[ ?\{)))))
+
+(defun octave-smie--in-parens-p ()
+ (let ((ppss (syntax-ppss)))
+ (and (nth 1 ppss)
+ (eq ?\( (char-after (nth 1 ppss))))))
(defun octave-smie-backward-token ()
(let ((pos (point)))
@@ -390,10 +393,7 @@ Non-nil means always go to the next Octave code line after
sending."
(forward-comment (- (point)))
nil)
t)
- ;; Ignore it if it's within parentheses.
- (let ((ppss (syntax-ppss)))
- (not (and (nth 1 ppss)
- (eq ?\( (char-after (nth 1 ppss)))))))
+ (not (octave-smie--in-parens-p)))
(skip-chars-forward " \t")
;; Why bother distinguishing \n and ;?
";") ;;"\n"
@@ -403,7 +403,15 @@ Non-nil means always go to the next Octave code line after
sending."
(goto-char (match-beginning 0))
(match-string-no-properties 0))
(t
- (smie-default-backward-token)))))
+ (let ((tok (smie-default-backward-token)))
+ (cond
+ ((equal tok "enumeration")
+ (if (save-excursion (smie-default-forward-token)
+ (octave-smie--funcall-p))
+ "enumeration (function)"
+ tok))
+ ((equal tok "end") (if (octave-smie--end-index-p) "end (index)" tok))
+ (t tok)))))))
(defun octave-smie-forward-token ()
(skip-chars-forward " \t")
@@ -417,10 +425,7 @@ Non-nil means always go to the next Octave code line after
sending."
(not (or (save-excursion (skip-chars-backward " \t")
;; Only add implicit ; when needed.
(or (bolp) (eq (char-before) ?\;)))
- ;; Ignore it if it's within parentheses.
- (let ((ppss (syntax-ppss)))
- (and (nth 1 ppss)
- (eq ?\( (char-after (nth 1 ppss))))))))
+ (octave-smie--in-parens-p))))
(if (eolp) (forward-char 1) (forward-comment 1))
;; Why bother distinguishing \n and ;?
";") ;;"\n"
@@ -436,7 +441,25 @@ Non-nil means always go to the next Octave code line after
sending."
(goto-char (match-end 0))
(match-string-no-properties 0))
(t
- (smie-default-forward-token))))
+ (let ((tok (smie-default-forward-token)))
+ (cond
+ ((equal tok "enumeration")
+ (if (octave-smie--funcall-p)
+ "enumeration (function)"
+ tok))
+ ((equal tok "end") (if (octave-smie--end-index-p) "end (index)" tok))
+ (t tok))))))
+
+(defconst octave--block-offset-keywords
+ (let* ((end-prec (nth 1 (assoc "end" octave-smie-grammar)))
+ (end-matchers
+ (delq nil
+ (mapcar (lambda (x) (if (eq end-prec (nth 2 x)) (car x)))
+ octave-smie-grammar))))
+ ;; Not sure if it would harm to keep "switch", but the previous code
+ ;; excluded it, presumably because there shouldn't be any code on
+ ;; the lines between "switch" and "case".
+ (delete "switch" end-matchers)))
(defun octave-smie-rules (kind token)
(pcase (cons kind token)
@@ -445,15 +468,12 @@ Non-nil means always go to the next Octave code line
after sending."
;; - changes to octave-block-offset wouldn't take effect immediately.
;; - edebug wouldn't show the use of this variable.
(`(:elem . basic) octave-block-offset)
+ (`(:list-intro . ,(or "global" "persistent")) t)
;; Since "case" is in the same BNF rules as switch..end, SMIE by default
;; aligns it with "switch".
(`(:before . "case") (if (not (smie-rule-sibling-p)) octave-block-offset))
(`(:after . ";")
- (if (smie-rule-parent-p "classdef" "events" "enumeration" "function" "if"
- "while" "else" "elseif" "for" "parfor"
- "properties" "methods" "otherwise" "case"
- "try" "catch" "unwind_protect"
- "unwind_protect_cleanup")
+ (if (apply #'smie-rule-parent-p octave--block-offset-keywords)
(smie-rule-parent octave-block-offset)
;; For (invalid) code between switch and case.
;; (if (smie-rule-parent-p "switch") 4)
@@ -473,28 +493,33 @@ Non-nil means always go to the next Octave code line
after sending."
(comment-choose-indent)))))
+(defvar octave-reserved-words
+ (delq nil
+ (mapcar (lambda (x)
+ (setq x (car x))
+ (and (stringp x) (string-match "\\`[[:alpha:]]" x) x))
+ octave-smie-grammar))
+ "Reserved words in Octave.")
+
(defvar octave-font-lock-keywords
(list
;; Fontify all builtin keywords.
- (cons (concat "\\_<\\("
- (regexp-opt octave-reserved-words)
- "\\)\\_>")
+ (cons (concat "\\_<" (regexp-opt octave-reserved-words) "\\_>")
'font-lock-keyword-face)
- ;; Note: 'end' also serves as the last index in an indexing expression.
+ ;; Note: 'end' also serves as the last index in an indexing expression,
+ ;; and 'enumerate' is also a function.
;; Ref: http://www.mathworks.com/help/matlab/ref/end.html
+ ;; Ref: http://www.mathworks.com/help/matlab/ref/enumeration.html
(list (lambda (limit)
- (while (re-search-forward "\\_<end\\_>" limit 'move)
+ (while (re-search-forward "\\_<en\\(?:d\\|umeratio\\(n\\)\\)\\_>"
+ limit 'move)
(let ((beg (match-beginning 0))
(end (match-end 0)))
(unless (octave-in-string-or-comment-p)
- (condition-case nil
- (progn
- (goto-char beg)
- (backward-up-list)
- (when (memq (char-after) '(?\( ?\[ ?\{))
- (put-text-property beg end 'face nil))
- (goto-char end))
- (error (goto-char end))))))
+ (when (if (match-end 1)
+ (octave-smie--funcall-p)
+ (octave-smie--end-index-p))
+ (put-text-property beg end 'face nil)))))
nil))
;; Fontify all operators.
(cons octave-operator-regexp 'font-lock-builtin-face)
@@ -609,27 +634,23 @@ Key bindings:
(defcustom inferior-octave-program "octave"
"Program invoked by `inferior-octave'."
- :type 'string
- :group 'octave)
+ :type 'string)
(defcustom inferior-octave-buffer "*Inferior Octave*"
"Name of buffer for running an inferior Octave process."
- :type 'string
- :group 'octave)
+ :type 'string)
(defcustom inferior-octave-prompt
;; For Octave >= 3.8, default is always 'octave', see
;; http://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50
"\\(?:^octave\\(?:.bin\\|.exe\\)?\\(?:-[.0-9]+\\)?\\(?::[0-9]+\\)?\\|^debug\\|^\\)>+
"
"Regexp to match prompts for the inferior Octave process."
- :type 'regexp
- :group 'octave)
+ :type 'regexp)
(defcustom inferior-octave-prompt-read-only comint-prompt-read-only
"If non-nil, the Octave prompt is read only.
See `comint-prompt-read-only' for details."
:type 'boolean
- :group 'octave
:version "24.4")
(defcustom inferior-octave-startup-file
@@ -639,7 +660,6 @@ See `comint-prompt-read-only' for details."
The contents of this file are sent to the inferior Octave process on
startup."
:type '(choice (const :tag "None" nil) file)
- :group 'octave
:version "24.4")
(defcustom inferior-octave-startup-args '("-i" "--no-line-editing")
@@ -647,13 +667,11 @@ startup."
For example, for suppressing the startup message and using `traditional'
mode, include \"-q\" and \"--traditional\"."
:type '(repeat string)
- :group 'octave
:version "24.4")
(defcustom inferior-octave-mode-hook nil
"Hook to be run when Inferior Octave mode is started."
- :type 'hook
- :group 'octave)
+ :type 'hook)
(defcustom inferior-octave-error-regexp-alist
'(("error:\\s-*\\(.*?\\) at line \\([0-9]+\\), column \\([0-9]+\\)"
@@ -663,8 +681,7 @@ mode, include \"-q\" and \"--traditional\"."
"Value for `compilation-error-regexp-alist' in inferior octave."
:version "24.4"
:type '(repeat (choice (symbol :tag "Predefined symbol")
- (sexp :tag "Error specification")))
- :group 'octave)
+ (sexp :tag "Error specification"))))
(defvar inferior-octave-compilation-font-lock-keywords
'(("\\_<PASS\\_>" . compilation-info-face)
@@ -995,7 +1012,6 @@ directory and makes this the current buffer's default
directory."
(defcustom inferior-octave-minimal-columns 80
"The minimal column width for the inferior Octave process."
:type 'integer
- :group 'octave
:version "24.4")
(defvar inferior-octave-last-column-width nil)
@@ -1180,8 +1196,7 @@ q: Don't fix\n" func file))
(defface octave-function-comment-block
'((t (:inherit font-lock-doc-face)))
- "Face used to highlight function comment block."
- :group 'octave)
+ "Face used to highlight function comment block.")
(eval-when-compile (require 'texinfo))
@@ -1602,7 +1617,6 @@ code line."
:type '(choice (const :tag "Automatic" auto)
(const :tag "One Line" oneline)
(const :tag "Multi Line" multiline))
- :group 'octave
:version "24.4")
;; (FN SIGNATURE1 SIGNATURE2 ...)
@@ -1661,7 +1675,6 @@ code line."
(defcustom octave-help-buffer "*Octave Help*"
"Buffer name for `octave-help'."
:type 'string
- :group 'octave
:version "24.4")
;; Used in a mode derived from help-mode.
@@ -1786,7 +1799,6 @@ sentence."
"A list of directories for Octave sources.
If the environment variable OCTAVE_SRCDIR is set, it is searched first."
:type '(repeat directory)
- :group 'octave
:version "24.4")
(defun octave-source-directories ()
diff --git a/test/indent/octave.m b/test/indent/octave.m
index a704146..4758f99 100644
--- a/test/indent/octave.m
+++ b/test/indent/octave.m
@@ -1,6 +1,19 @@
## -*- mode: octave; coding: utf-8 -*-
0; # Don't make this a function file
function res = tcomp (fn)
+
+ global x y ...
+ z1 z2
+ persistent x y ...
+ z1 z2
+ global x y = 2 ...
+ z1 z2 # FIXME
+
+ do
+ something
+ until x = ...
+ y
+
%% res = tcomp (fn)
%% imports components and rearranges them.
@@ -10,6 +23,15 @@ function res = tcomp (fn)
data = dlmread(fn, 3, 0);
+ enumeration
+ first (1)
+ second (2)
+ end
+
+ y = enumeration (x); #Beware: "enumeration" can also be a function!
+ y = foo(enumeration (x),
+ 2); #Beware: "enumeration" can also be a function!
+
x = data(:,2:end);
y = 'hello';
z = y';