aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
Diffstat (limited to 'lisp')
-rw-r--r--lisp/ChangeLog5
-rw-r--r--lisp/emacs-lisp/smie.el178
2 files changed, 182 insertions, 1 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index dd698a0ef40..09424462f21 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,10 @@
12010-09-03 Stefan Monnier <monnier@iro.umontreal.ca> 12010-09-03 Stefan Monnier <monnier@iro.umontreal.ca>
2 2
3 Provide blink-matching support to SMIE.
4 * emacs-lisp/smie.el (smie-bnf-closer-alist): New function.
5 (smie-blink-matching-triggers, smie-blink-matching-inners): New vars.
6 (smie-blink-matching-check, smie-blink-matching-open): New functions.
7
3 * simple.el (newline): Fix last change to properly remove itself from 8 * simple.el (newline): Fix last change to properly remove itself from
4 the hook. 9 the hook.
5 10
diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el
index e8b5c2448e8..c6df851b0e5 100644
--- a/lisp/emacs-lisp/smie.el
+++ b/lisp/emacs-lisp/smie.el
@@ -75,6 +75,26 @@
75 75
76;;; Building precedence level tables from BNF specs. 76;;; Building precedence level tables from BNF specs.
77 77
78;; We have 4 different representations of a "grammar":
79;; - a BNF table, which is a list of BNF rules of the form
80;; (NONTERM RHS1 ... RHSn) where each RHS is a list of terminals (tokens)
81;; or nonterminals. Any element in these lists which does not appear as
82;; the `car' of a BNF rule is taken to be a terminal.
83;; - A list of precedences (key word "precs"), is a list, sorted
84;; from lowest to highest precedence, of precedence classes that
85;; have the form (ASSOCIATIVITY TERMINAL1 .. TERMINALn), where
86;; ASSOCIATIVITY can be `assoc', `left', `right' or `nonassoc'.
87;; - a 2 dimensional precedence table (key word "prec2"), is a 2D
88;; table recording the precedence relation (can be `<', `=', `>', or
89;; nil) between each pair of tokens.
90;; - a precedence-level table (key word "levels"), while is a alist
91;; giving for each token its left and right precedence level (a
92;; number or nil). This is used in `smie-op-levels'.
93;; The prec2 tables are only intermediate data structures: the source
94;; code normally provides a mix of BNF and precs tables, and then
95;; turns them into a levels table, which is what's used by the rest of
96;; the SMIE code.
97
78(defun smie-set-prec2tab (table x y val &optional override) 98(defun smie-set-prec2tab (table x y val &optional override)
79 (assert (and x y)) 99 (assert (and x y))
80 (let* ((key (cons x y)) 100 (let* ((key (cons x y))
@@ -206,6 +226,87 @@ one of those elements share the same precedence level and associativity."
206 (setq rhs (cdr rhs))))) 226 (setq rhs (cdr rhs)))))
207 prec2)) 227 prec2))
208 228
229;; (defun smie-prec2-closer-alist (prec2 include-inners)
230;; "Build a closer-alist from a PREC2 table.
231;; The return value is in the same form as `smie-closer-alist'.
232;; INCLUDE-INNERS if non-nil means that inner keywords will be included
233;; in the table, e.g. the table will include things like (\"if\" . \"else\")."
234;; (let* ((non-openers '())
235;; (non-closers '())
236;; ;; For each keyword, this gives the matching openers, if any.
237;; (openers (make-hash-table :test 'equal))
238;; (closers '())
239;; (done nil))
240;; ;; First, find the non-openers and non-closers.
241;; (maphash (lambda (k v)
242;; (unless (or (eq v '<) (member (cdr k) non-openers))
243;; (push (cdr k) non-openers))
244;; (unless (or (eq v '>) (member (car k) non-closers))
245;; (push (car k) non-closers)))
246;; prec2)
247;; ;; Then find the openers and closers.
248;; (maphash (lambda (k _)
249;; (unless (member (car k) non-openers)
250;; (puthash (car k) (list (car k)) openers))
251;; (unless (or (member (cdr k) non-closers)
252;; (member (cdr k) closers))
253;; (push (cdr k) closers)))
254;; prec2)
255;; ;; Then collect the matching elements.
256;; (while (not done)
257;; (setq done t)
258;; (maphash (lambda (k v)
259;; (when (eq v '=)
260;; (let ((aopeners (gethash (car k) openers))
261;; (dopeners (gethash (cdr k) openers))
262;; (new nil))
263;; (dolist (o aopeners)
264;; (unless (member o dopeners)
265;; (setq new t)
266;; (push o dopeners)))
267;; (when new
268;; (setq done nil)
269;; (puthash (cdr k) dopeners openers)))))
270;; prec2))
271;; ;; Finally, dump the resulting table.
272;; (let ((alist '()))
273;; (maphash (lambda (k v)
274;; (when (or include-inners (member k closers))
275;; (dolist (opener v)
276;; (unless (equal opener k)
277;; (push (cons opener k) alist)))))
278;; openers)
279;; alist)))
280
281(defun smie-bnf-closer-alist (bnf &optional no-inners)
282 ;; We can also build this closer-alist table from a prec2 table,
283 ;; but it takes more work, and the order is unpredictable, which
284 ;; is a problem for smie-close-block.
285 ;; More convenient would be to build it from a levels table since we
286 ;; always have this table (contrary to the BNF), but it has all the
287 ;; disadvantages of the prec2 case plus the disadvantage that the levels
288 ;; table has lost some info which would result in extra invalid pairs.
289 "Build a closer-alist from a BNF table.
290The return value is in the same form as `smie-closer-alist'.
291NO-INNERS if non-nil means that inner keywords will be excluded
292from the table, e.g. the table will not include things like (\"if\" . \"else\")."
293 (let ((nts (mapcar #'car bnf)) ;non terminals.
294 (alist '()))
295 (dolist (nt bnf)
296 (dolist (rhs (cdr nt))
297 (unless (or (< (length rhs) 2) (member (car rhs) nts))
298 (if no-inners
299 (let ((last (car (last rhs))))
300 (unless (member last nts)
301 (pushnew (cons (car rhs) last) alist :test #'equal)))
302 ;; Reverse so that the "real" closer gets there first,
303 ;; which is important for smie-close-block.
304 (dolist (term (reverse (cdr rhs)))
305 (unless (member term nts)
306 (pushnew (cons (car rhs) term) alist :test #'equal)))))))
307 (nreverse alist)))
308
309
209(defun smie-prec2-levels (prec2) 310(defun smie-prec2-levels (prec2)
210 ;; FIXME: Rather than only return an alist of precedence levels, we should 311 ;; FIXME: Rather than only return an alist of precedence levels, we should
211 ;; also extract other useful data from it: 312 ;; also extract other useful data from it:
@@ -223,7 +324,7 @@ PREC2 is a table as returned by `smie-precs-precedence-table' or
223`smie-bnf-precedence-table'." 324`smie-bnf-precedence-table'."
224 ;; For each operator, we create two "variables" (corresponding to 325 ;; For each operator, we create two "variables" (corresponding to
225 ;; the left and right precedence level), which are represented by 326 ;; the left and right precedence level), which are represented by
226 ;; cons cells. Those are the vary cons cells that appear in the 327 ;; cons cells. Those are the very cons cells that appear in the
227 ;; final `table'. The value of each "variable" is kept in the `car'. 328 ;; final `table'. The value of each "variable" is kept in the `car'.
228 (let ((table ()) 329 (let ((table ())
229 (csts ()) 330 (csts ())
@@ -596,6 +697,81 @@ This command assumes point is not in a string or comment."
596 pos end)))) 697 pos end))))
597 (t))))))) 698 (t)))))))
598 699
700(defvar smie-blink-matching-triggers '(?\s ?\n)
701 "Chars which might trigger `blink-matching-open'.
702These can include the final chars of end-tokens, or chars that are
703typically inserted right after an end token.
704I.e. a good choice can be:
705 (delete-dups
706 (mapcar (lambda (kw) (aref (cdr kw) (1- (length (cdr kw)))))
707 smie-closer-alist))")
708
709(defcustom smie-blink-matching-inners t
710 "Whether SMIE should blink to matching opener for inner keywords.
711If non-nil, it will blink not only for \"begin..end\" but also for \"if...else\"."
712 :type 'boolean)
713
714(defun smie-blink-matching-check (start end)
715 (save-excursion
716 (goto-char end)
717 (let ((ender (funcall smie-backward-token-function)))
718 (cond
719 ((not (and ender (rassoc ender smie-closer-alist)))
720 ;; This not is one of the begin..end we know how to check.
721 (blink-matching-check-mismatch start end))
722 ((not start) t)
723 (t
724 (goto-char start)
725 (let ((starter (funcall smie-forward-token-function)))
726 (not (member (cons starter ender) smie-closer-alist))))))))
727
728(defun smie-blink-matching-open ()
729 "Blink the matching opener when applicable.
730This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'."
731 (when (and blink-matching-paren
732 smie-closer-alist ; Optimization.
733 (eq (char-before) last-command-event) ; Sanity check.
734 (memq last-command-event smie-blink-matching-triggers)
735 (save-excursion
736 ;; FIXME: Here we assume that closers all end
737 ;; with a word-syntax char.
738 (unless (eq ?\w (char-syntax last-command-event))
739 (forward-char -1))
740 (and (looking-at "\\>")
741 (not (nth 8 (syntax-ppss))))))
742 (save-excursion
743 (let ((pos (point))
744 (token (funcall smie-backward-token-function)))
745 (if (= 1 (length token))
746 ;; The trigger char is itself a token but is not
747 ;; one of the closers (e.g. ?\; in Octave mode),
748 ;; so go back to the previous token
749 (setq token (save-excursion
750 (funcall smie-backward-token-function)))
751 (goto-char pos))
752 ;; Here we assume that smie-backward-token-function
753 ;; returns a token that is a string and whose content
754 ;; match the buffer's representation of this token.
755 (when (and (> (length token) 1) (stringp token)
756 (memq (aref token (1- (length token)))
757 smie-blink-matching-triggers)
758 (not (eq (aref token (1- (length token)))
759 last-command-event)))
760 ;; Token ends with a trigger char, so don't blink for
761 ;; anything else than this trigger char, lest we'd blink
762 ;; both when inserting the trigger char and when inserting a
763 ;; subsequent SPC.
764 (setq token nil))
765 (when (and (rassoc token smie-closer-alist)
766 (or smie-blink-matching-inners
767 (null (nth 2 (assoc token smie-op-levels)))))
768 ;; The major mode might set blink-matching-check-function
769 ;; buffer-locally so that interactive calls to
770 ;; blink-matching-open work right, but let's not presume
771 ;; that's the case.
772 (let ((blink-matching-check-function #'smie-blink-matching-check))
773 (blink-matching-open)))))))
774
599;;; The indentation engine. 775;;; The indentation engine.
600 776
601(defcustom smie-indent-basic 4 777(defcustom smie-indent-basic 4