diff options
| author | Stefan Monnier | 2011-11-18 11:30:43 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2011-11-18 11:30:43 -0500 |
| commit | 2ad52c605cfbf8254a9d14e7e4c64f6486414734 (patch) | |
| tree | c6fd7bb061cf5b1835a75ebc1f2067fb8a3c96a7 /lisp | |
| parent | b50a28de8707794ff4b4b755af3173cd19004976 (diff) | |
| download | emacs-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/ChangeLog | 9 | ||||
| -rw-r--r-- | lisp/emacs-lisp/smie.el | 155 |
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 @@ | |||
| 1 | 2011-11-18 Stefan Monnier <monnier@iro.umontreal.ca> | 1 | 2011-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. | ||
| 215 | BNF is a list of nonterminal definitions of the form: | ||
| 216 | \(NONTERM RHS1 RHS2 ...) | ||
| 217 | where each RHS is a (non-empty) list of terminals (aka tokens) or non-terminals. | ||
| 218 | Not 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). | ||
| 224 | Additionally, 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\"). | ||
| 231 | Conflicts can be resolved via RESOLVERS, which is a list of elements that can | ||
| 232 | be 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. |
| 376 | Each terminal can either be an `opener', a `closer', or neither." | 441 | Each 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 | ||
| 981 | OFFSET can be: | 1044 | OFFSET can be: |
| 982 | nil use the default indentation rule. | 1045 | nil use the default indentation rule. |
| 983 | `(column . COLUMN) indent to column COLUMN. | 1046 | \(column . COLUMN) indent to column COLUMN. |
| 984 | NUMBER offset by NUMBER, relative to a base token | 1047 | NUMBER 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. |