aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorStefan Monnier2011-11-18 11:30:43 -0500
committerStefan Monnier2011-11-18 11:30:43 -0500
commit2ad52c605cfbf8254a9d14e7e4c64f6486414734 (patch)
treec6fd7bb061cf5b1835a75ebc1f2067fb8a3c96a7 /lisp
parentb50a28de8707794ff4b4b755af3173cd19004976 (diff)
downloademacs-2ad52c605cfbf8254a9d14e7e4c64f6486414734.tar.gz
emacs-2ad52c605cfbf8254a9d14e7e4c64f6486414734.zip
* lisp/emacs-lisp/smie.el: Improve warnings and conflict detection.
(smie-warning-count): New var. (smie-set-prec2tab): Use it. (smie-bnf->prec2): Improve warnings. Add docstring. (smie-bnf--closer-alist): Rename from smie-bnf-closer-alist. (smie-bnf--set-class): New function. (smie-bnf--classify): Rename from smie-bnf-classify. Rewrite to fix corner case.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/ChangeLog9
-rw-r--r--lisp/emacs-lisp/smie.el155
2 files changed, 118 insertions, 46 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index bedc440359c..69e5d5571d2 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,14 @@
12011-11-18 Stefan Monnier <monnier@iro.umontreal.ca> 12011-11-18 Stefan Monnier <monnier@iro.umontreal.ca>
2 2
3 * emacs-lisp/smie.el: Improve warnings and conflict detection.
4 (smie-warning-count): New var.
5 (smie-set-prec2tab): Use it.
6 (smie-bnf->prec2): Improve warnings. Add docstring.
7 (smie-bnf--closer-alist): Rename from smie-bnf-closer-alist.
8 (smie-bnf--set-class): New function.
9 (smie-bnf--classify): Rename from smie-bnf-classify. Rewrite to fix
10 corner case.
11
3 * progmodes/compile.el: Obey compilation-first-column in dest buffer. 12 * progmodes/compile.el: Obey compilation-first-column in dest buffer.
4 (compilation-error-properties, compilation-move-to-column): 13 (compilation-error-properties, compilation-move-to-column):
5 Handle compilation-first-column while in the target buffer. 14 Handle compilation-first-column while in the target buffer.
diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el
index 265328631e9..d43ba6c0d3e 100644
--- a/lisp/emacs-lisp/smie.el
+++ b/lisp/emacs-lisp/smie.el
@@ -69,13 +69,23 @@
69;; (exp ("IF" exp "ELSE" exp "END") ("CASE" cases "END")) 69;; (exp ("IF" exp "ELSE" exp "END") ("CASE" cases "END"))
70;; (cases (cases "ELSE" insts) ...) 70;; (cases (cases "ELSE" insts) ...)
71;; The IF-rule implies ELSE=END and the CASE-rule implies ELSE>END. 71;; The IF-rule implies ELSE=END and the CASE-rule implies ELSE>END.
72;; FIXME: we could try to resolve such conflicts automatically by changing 72;; This can be resolved simply with:
73;; the way BNF rules such as the IF-rule is handled. I.e. rather than 73;; (exp ("IF" expelseexp "END") ("CASE" cases "END"))
74;; IF=ELSE and ELSE=END, we could turn them into IF<ELSE and ELSE>END 74;; (expelseexp (exp) (exp "ELSE" exp))
75;; and IF=END, 75;; (cases (cases "ELSE" insts) ...)
76;; - Another source of conflict is when a terminator/separator is used to
77;; terminate elements at different levels, as in:
78;; (decls ("VAR" vars) (decls "," decls))
79;; (vars (id) (vars "," vars))
80;; often these can be resolved by making the lexer distinguish the two
81;; kinds of commas, e.g. based on the following token.
76 82
77;; TODO & BUGS: 83;; TODO & BUGS:
78;; 84;;
85;; - We could try to resolve conflicts such as the IFexpELSEexpEND -vs-
86;; CASE(casesELSEexp)END automatically by changing the way BNF rules such as
87;; the IF-rule is handled. I.e. rather than IF=ELSE and ELSE=END, we could
88;; turn them into IF<ELSE and ELSE>END and IF=END.
79;; - Using the structural information SMIE gives us, it should be possible to 89;; - Using the structural information SMIE gives us, it should be possible to
80;; implement a `smie-align' command that would automatically figure out what 90;; implement a `smie-align' command that would automatically figure out what
81;; there is to align and how to do it (something like: align the token of 91;; there is to align and how to do it (something like: align the token of
@@ -107,6 +117,10 @@
107 117
108;;; Code: 118;;; Code:
109 119
120;; FIXME:
121;; - smie-indent-comment doesn't interact well with mis-indented lines (where
122;; the indent rules don't do what the user wants). Not sure what to do.
123
110(eval-when-compile (require 'cl)) 124(eval-when-compile (require 'cl))
111 125
112(defgroup smie nil 126(defgroup smie nil
@@ -138,6 +152,8 @@
138;; turns them into a levels table, which is what's used by the rest of 152;; turns them into a levels table, which is what's used by the rest of
139;; the SMIE code. 153;; the SMIE code.
140 154
155(defvar smie-warning-count 0)
156
141(defun smie-set-prec2tab (table x y val &optional override) 157(defun smie-set-prec2tab (table x y val &optional override)
142 (assert (and x y)) 158 (assert (and x y))
143 (let* ((key (cons x y)) 159 (let* ((key (cons x y))
@@ -149,7 +165,8 @@
149 ;; be able to distinguish the two cases so that overrides 165 ;; be able to distinguish the two cases so that overrides
150 ;; don't hide real conflicts. 166 ;; don't hide real conflicts.
151 (puthash key (gethash key override) table) 167 (puthash key (gethash key override) table)
152 (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y))) 168 (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y))
169 (incf smie-warning-count))
153 (puthash key val table)))) 170 (puthash key val table))))
154 171
155(put 'smie-precs->prec2 'pure t) 172(put 'smie-precs->prec2 'pure t)
@@ -193,21 +210,54 @@ one of those elements share the same precedence level and associativity."
193 prec2))) 210 prec2)))
194 211
195(put 'smie-bnf->prec2 'pure t) 212(put 'smie-bnf->prec2 'pure t)
196(defun smie-bnf->prec2 (bnf &rest precs) 213(defun smie-bnf->prec2 (bnf &rest resolvers)
214 "Convert the BNF grammar into a prec2 table.
215BNF is a list of nonterminal definitions of the form:
216 \(NONTERM RHS1 RHS2 ...)
217where each RHS is a (non-empty) list of terminals (aka tokens) or non-terminals.
218Not all grammars are accepted:
219- an RHS cannot be an empty list (this is not needed, since SMIE allows all
220 non-terminals to match the empty string anyway).
221- an RHS cannot have 2 consecutive non-terminals: between each non-terminal
222 needs to be a terminal (aka token). This is a fundamental limitation of
223 the parsing technology used (operator precedence grammar).
224Additionally, conflicts can occur:
225- The returned prec2 table holds constraints between pairs of
226 token, and for any given pair only one constraint can be
227 present, either: T1 < T2, T1 = T2, or T1 > T2.
228- A token can either be an `opener' (something similar to an open-paren),
229 a `closer' (like a close-paren), or `neither' of the two (e.g. an infix
230 operator, or an inner token like \"else\").
231Conflicts can be resolved via RESOLVERS, which is a list of elements that can
232be either:
233- a precs table (see `smie-precs->prec2') to resolve conflicting constraints,
234- a constraint (T1 REL T2) where REL is one of = < or >."
197 ;; FIXME: Add repetition operator like (repeat <separator> <elems>). 235 ;; FIXME: Add repetition operator like (repeat <separator> <elems>).
198 ;; Maybe also add (or <elem1> <elem2>...) for things like 236 ;; Maybe also add (or <elem1> <elem2>...) for things like
199 ;; (exp (exp (or "+" "*" "=" ..) exp)). 237 ;; (exp (exp (or "+" "*" "=" ..) exp)).
200 ;; Basically, make it EBNF (except for the specification of a separator in 238 ;; Basically, make it EBNF (except for the specification of a separator in
201 ;; the repetition, maybe). 239 ;; the repetition, maybe).
202 (let ((nts (mapcar 'car bnf)) ;Non-terminals 240 (let* ((nts (mapcar 'car bnf)) ;Non-terminals.
203 (first-ops-table ()) 241 (first-ops-table ())
204 (last-ops-table ()) 242 (last-ops-table ())
205 (first-nts-table ()) 243 (first-nts-table ())
206 (last-nts-table ()) 244 (last-nts-table ())
207 (prec2 (make-hash-table :test 'equal)) 245 (smie-warning-count 0)
208 (override (apply 'smie-merge-prec2s 246 (prec2 (make-hash-table :test 'equal))
209 (mapcar 'smie-precs->prec2 precs))) 247 (override
210 again) 248 (let ((precs ())
249 (over (make-hash-table :test 'equal)))
250 (dolist (resolver resolvers)
251 (cond
252 ((and (= 3 (length resolver)) (memq (nth 1 resolver) '(= < >)))
253 (smie-set-prec2tab
254 over (nth 0 resolver) (nth 2 resolver) (nth 1 resolver)))
255 ((memq (caar resolver) '(left right assoc nonassoc))
256 (push resolver precs))
257 (t (error "Unknown resolver %S" resolver))))
258 (apply #'smie-merge-prec2s over
259 (mapcar 'smie-precs->prec2 precs))))
260 again)
211 (dolist (rules bnf) 261 (dolist (rules bnf)
212 (let ((nt (car rules)) 262 (let ((nt (car rules))
213 (last-ops ()) 263 (last-ops ())
@@ -287,8 +337,11 @@ one of those elements share the same precedence level and associativity."
287 (setq rhs (cdr rhs))))) 337 (setq rhs (cdr rhs)))))
288 ;; Keep track of which tokens are openers/closer, so they can get a nil 338 ;; Keep track of which tokens are openers/closer, so they can get a nil
289 ;; precedence in smie-prec2->grammar. 339 ;; precedence in smie-prec2->grammar.
290 (puthash :smie-open/close-alist (smie-bnf-classify bnf) prec2) 340 (puthash :smie-open/close-alist (smie-bnf--classify bnf) prec2)
291 (puthash :smie-closer-alist (smie-bnf-closer-alist bnf) prec2) 341 (puthash :smie-closer-alist (smie-bnf--closer-alist bnf) prec2)
342 (if (> smie-warning-count 0)
343 (display-warning
344 'smie (format "Total: %d warnings" smie-warning-count)))
292 prec2)) 345 prec2))
293 346
294;; (defun smie-prec2-closer-alist (prec2 include-inners) 347;; (defun smie-prec2-closer-alist (prec2 include-inners)
@@ -343,7 +396,7 @@ one of those elements share the same precedence level and associativity."
343;; openers) 396;; openers)
344;; alist))) 397;; alist)))
345 398
346(defun smie-bnf-closer-alist (bnf &optional no-inners) 399(defun smie-bnf--closer-alist (bnf &optional no-inners)
347 ;; We can also build this closer-alist table from a prec2 table, 400 ;; We can also build this closer-alist table from a prec2 table,
348 ;; but it takes more work, and the order is unpredictable, which 401 ;; but it takes more work, and the order is unpredictable, which
349 ;; is a problem for smie-close-block. 402 ;; is a problem for smie-close-block.
@@ -371,37 +424,33 @@ from the table, e.g. the table will not include things like (\"if\" . \"else\").
371 (pushnew (cons (car rhs) term) alist :test #'equal))))))) 424 (pushnew (cons (car rhs) term) alist :test #'equal)))))))
372 (nreverse alist))) 425 (nreverse alist)))
373 426
374(defun smie-bnf-classify (bnf) 427(defun smie-bnf--set-class (table token class)
428 (let ((prev (gethash token table class)))
429 (puthash token
430 (cond
431 ((eq prev class) class)
432 ((eq prev t) t) ;Non-terminal.
433 (t (display-warning
434 'smie
435 (format "token %s is both %s and %s" token class prev))
436 'neither))
437 table)))
438
439(defun smie-bnf--classify (bnf)
375 "Return a table classifying terminals. 440 "Return a table classifying terminals.
376Each terminal can either be an `opener', a `closer', or neither." 441Each terminal can either be an `opener', a `closer', or `neither'."
377 (let ((table (make-hash-table :test #'equal)) 442 (let ((table (make-hash-table :test #'equal))
378 (nts (mapcar #'car bnf))
379 (alist '())) 443 (alist '()))
380 (dolist (category bnf) 444 (dolist (category bnf)
381 (puthash (car category) 'neither table) ;Remove non-terminals. 445 (puthash (car category) t table)) ;Mark non-terminals.
446 (dolist (category bnf)
382 (dolist (rhs (cdr category)) 447 (dolist (rhs (cdr category))
383 (if (null (cdr rhs)) 448 (if (null (cdr rhs))
384 (puthash (pop rhs) 'neither table) 449 (smie-bnf--set-class table (pop rhs) 'neither)
385 (let ((first (pop rhs))) 450 (smie-bnf--set-class table (pop rhs) 'opener)
386 (puthash first 451 (while (cdr rhs) ;Remove internals.
387 (if (memq (gethash first table) '(nil opener)) 452 (smie-bnf--set-class table (pop rhs) 'neither))
388 'opener 453 (smie-bnf--set-class table (pop rhs) 'closer))))
389 (unless (member first nts)
390 (error "SMIE: token %s is both opener and non-opener"
391 first))
392 'neither)
393 table))
394 (while (cdr rhs)
395 (puthash (pop rhs) 'neither table)) ;Remove internals.
396 (let ((last (pop rhs)))
397 (puthash last
398 (if (memq (gethash last table) '(nil closer))
399 'closer
400 (unless (member last nts)
401 (error "SMIE: token %s is both closer and non-closer"
402 last))
403 'neither)
404 table)))))
405 (maphash (lambda (tok v) 454 (maphash (lambda (tok v)
406 (when (memq v '(closer opener)) 455 (when (memq v '(closer opener))
407 (push (cons tok v) alist))) 456 (push (cons tok v) alist)))
@@ -692,8 +741,22 @@ Possible return values:
692 ;; Keep looking as long as we haven't matched the 741 ;; Keep looking as long as we haven't matched the
693 ;; topmost operator. 742 ;; topmost operator.
694 (levels 743 (levels
695 (if (numberp (funcall op-forw toklevels)) 744 (cond
696 (push toklevels levels))) 745 ((numberp (funcall op-forw toklevels))
746 (push toklevels levels))
747 ;; FIXME: For some languages, we can express the grammar
748 ;; OK, but next-sexp doesn't stop where we'd want it to.
749 ;; E.g. in SML, we'd want to stop right in front of
750 ;; "local" if we're scanning (both forward and backward)
751 ;; from a "val/fun/..." at the same level.
752 ;; Same for Pascal/Modula2's "procedure" w.r.t
753 ;; "type/var/const".
754 ;;
755 ;; ((and (functionp (cadr (funcall op-forw toklevels)))
756 ;; (funcall (cadr (funcall op-forw toklevels))
757 ;; levels))
758 ;; (setq levels nil))
759 ))
697 ;; We matched the topmost operator. If the new operator 760 ;; We matched the topmost operator. If the new operator
698 ;; is the last in the corresponding BNF rule, we're done. 761 ;; is the last in the corresponding BNF rule, we're done.
699 ((not (numberp (funcall op-forw toklevels))) 762 ((not (numberp (funcall op-forw toklevels)))
@@ -980,7 +1043,7 @@ function should return nil for arguments it does not expect.
980 1043
981OFFSET can be: 1044OFFSET can be:
982nil use the default indentation rule. 1045nil use the default indentation rule.
983`(column . COLUMN) indent to column COLUMN. 1046\(column . COLUMN) indent to column COLUMN.
984NUMBER offset by NUMBER, relative to a base token 1047NUMBER offset by NUMBER, relative to a base token
985 which is the current token for :after and 1048 which is the current token for :after and
986 its parent for :before. 1049 its parent for :before.