-;;; smie.el --- Simple Minded Indentation Engine
+;;; smie.el --- Simple Minded Indentation Engine -*- lexical-binding: t -*-
-;; Copyright (C) 2010 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2012 Free Software Foundation, Inc.
;; Author: Stefan Monnier <monnier@iro.umontreal.ca>
;; Keywords: languages, lisp, internal, parsing, indentation
;; building the 2D precedence tables and then computing the precedence levels
;; from it) can be found in pages 187-194 of "Parsing techniques" by Dick Grune
;; and Ceriel Jacobs (BookBody.pdf available at
-;; http://www.cs.vu.nl/~dick/PTAPG.html).
+;; http://dickgrune.com/Books/PTAPG_1st_Edition/).
;;
;; OTOH we had to kill many chickens, read many coffee grounds, and practice
;; untold numbers of black magic spells, to come up with the indentation code.
;; (exp ("IF" exp "ELSE" exp "END") ("CASE" cases "END"))
;; (cases (cases "ELSE" insts) ...)
;; The IF-rule implies ELSE=END and the CASE-rule implies ELSE>END.
-;; FIXME: we could try to resolve such conflicts automatically by changing
-;; the way BNF rules such as the IF-rule is handled. I.e. rather than
-;; IF=ELSE and ELSE=END, we could turn them into IF<ELSE and ELSE>END
-;; and IF=END,
+;; This can be resolved simply with:
+;; (exp ("IF" expelseexp "END") ("CASE" cases "END"))
+;; (expelseexp (exp) (exp "ELSE" exp))
+;; (cases (cases "ELSE" insts) ...)
+;; - Another source of conflict is when a terminator/separator is used to
+;; terminate elements at different levels, as in:
+;; (decls ("VAR" vars) (decls "," decls))
+;; (vars (id) (vars "," vars))
+;; often these can be resolved by making the lexer distinguish the two
+;; kinds of commas, e.g. based on the following token.
;; TODO & BUGS:
;;
-;; - FIXME: I think the behavior on empty lines is wrong. It shouldn't
-;; look at the next token on subsequent lines.
+;; - We could try to resolve conflicts such as the IFexpELSEexpEND -vs-
+;; CASE(casesELSEexp)END automatically by changing the way BNF rules such as
+;; the IF-rule is handled. I.e. rather than IF=ELSE and ELSE=END, we could
+;; turn them into IF<ELSE and ELSE>END and IF=END.
;; - Using the structural information SMIE gives us, it should be possible to
;; implement a `smie-align' command that would automatically figure out what
;; there is to align and how to do it (something like: align the token of
;; - Maybe accept two juxtaposed non-terminals in the BNF under the condition
;; that the first always ends with a terminal, or that the second always
;; starts with a terminal.
+;; - Permit EBNF-style notation.
+;; - If the grammar has conflicts, the only way is to make the lexer return
+;; different tokens for the different cases. This extra work performed by
+;; the lexer can be costly and unnecessary: we perform this extra work every
+;; time we find the conflicting token, regardless of whether or not the
+;; difference between the various situations is relevant to the current
+;; situation. E.g. we may try to determine whether a ";" is a ";-operator"
+;; or a ";-separator" in a case where we're skipping over a "begin..end" pair
+;; where the difference doesn't matter. For frequently occurring tokens and
+;; rarely occurring conflicts, this can be a significant performance problem.
+;; We could try and let the lexer return a "set of possible tokens
+;; plus a refinement function" and then let parser call the refinement
+;; function if needed.
+;; - Make it possible to better specify the behavior in the face of
+;; syntax errors. IOW provide some control over the choice of precedence
+;; levels within the limits of the constraints. E.g. make it possible for
+;; the grammar to specify that "begin..end" has lower precedence than
+;; "Module..EndModule", so that if a "begin" is missing, scanning from the
+;; "end" will stop at "Module" rather than going past it (and similarly,
+;; scanning from "Module" should not stop at a spurious "end").
;;; Code:
+;; FIXME:
+;; - smie-indent-comment doesn't interact well with mis-indented lines (where
+;; the indent rules don't do what the user wants). Not sure what to do.
+
(eval-when-compile (require 'cl))
(defgroup smie nil
;; - a 2 dimensional precedence table (key word "prec2"), is a 2D
;; table recording the precedence relation (can be `<', `=', `>', or
;; nil) between each pair of tokens.
-;; - a precedence-level table (key word "grammar"), which is a alist
+;; - a precedence-level table (key word "grammar"), which is an alist
;; giving for each token its left and right precedence level (a
;; number or nil). This is used in `smie-grammar'.
;; The prec2 tables are only intermediate data structures: the source
;; turns them into a levels table, which is what's used by the rest of
;; the SMIE code.
+(defvar smie-warning-count 0)
+
(defun smie-set-prec2tab (table x y val &optional override)
(assert (and x y))
(let* ((key (cons x y))
;; be able to distinguish the two cases so that overrides
;; don't hide real conflicts.
(puthash key (gethash key override) table)
- (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y)))
+ (display-warning 'smie (format "Conflict: %s %s/%s %s" x old val y))
+ (incf smie-warning-count))
(puthash key val table))))
(put 'smie-precs->prec2 'pure t)
prec2)))
(put 'smie-bnf->prec2 'pure t)
-(defun smie-bnf->prec2 (bnf &rest precs)
+(defun smie-bnf->prec2 (bnf &rest resolvers)
+ "Convert the BNF grammar into a prec2 table.
+BNF is a list of nonterminal definitions of the form:
+ \(NONTERM RHS1 RHS2 ...)
+where each RHS is a (non-empty) list of terminals (aka tokens) or non-terminals.
+Not all grammars are accepted:
+- an RHS cannot be an empty list (this is not needed, since SMIE allows all
+ non-terminals to match the empty string anyway).
+- an RHS cannot have 2 consecutive non-terminals: between each non-terminal
+ needs to be a terminal (aka token). This is a fundamental limitation of
+ the parsing technology used (operator precedence grammar).
+Additionally, conflicts can occur:
+- The returned prec2 table holds constraints between pairs of
+ token, and for any given pair only one constraint can be
+ present, either: T1 < T2, T1 = T2, or T1 > T2.
+- A token can either be an `opener' (something similar to an open-paren),
+ a `closer' (like a close-paren), or `neither' of the two (e.g. an infix
+ operator, or an inner token like \"else\").
+Conflicts can be resolved via RESOLVERS, which is a list of elements that can
+be either:
+- a precs table (see `smie-precs->prec2') to resolve conflicting constraints,
+- a constraint (T1 REL T2) where REL is one of = < or >."
;; FIXME: Add repetition operator like (repeat <separator> <elems>).
;; Maybe also add (or <elem1> <elem2>...) for things like
;; (exp (exp (or "+" "*" "=" ..) exp)).
;; Basically, make it EBNF (except for the specification of a separator in
- ;; the repetition).
- (let ((nts (mapcar 'car bnf)) ;Non-terminals
- (first-ops-table ())
- (last-ops-table ())
- (first-nts-table ())
- (last-nts-table ())
- (prec2 (make-hash-table :test 'equal))
- (override (apply 'smie-merge-prec2s
- (mapcar 'smie-precs->prec2 precs)))
- again)
+ ;; the repetition, maybe).
+ (let* ((nts (mapcar 'car bnf)) ;Non-terminals.
+ (first-ops-table ())
+ (last-ops-table ())
+ (first-nts-table ())
+ (last-nts-table ())
+ (smie-warning-count 0)
+ (prec2 (make-hash-table :test 'equal))
+ (override
+ (let ((precs ())
+ (over (make-hash-table :test 'equal)))
+ (dolist (resolver resolvers)
+ (cond
+ ((and (= 3 (length resolver)) (memq (nth 1 resolver) '(= < >)))
+ (smie-set-prec2tab
+ over (nth 0 resolver) (nth 2 resolver) (nth 1 resolver)))
+ ((memq (caar resolver) '(left right assoc nonassoc))
+ (push resolver precs))
+ (t (error "Unknown resolver %S" resolver))))
+ (apply #'smie-merge-prec2s over
+ (mapcar 'smie-precs->prec2 precs))))
+ again)
(dolist (rules bnf)
(let ((nt (car rules))
(last-ops ())
;; the trouble, and it lets the writer of the BNF
;; be a bit more sloppy by skipping uninteresting base
;; cases which are terminals but not OPs.
- (assert (not (member (cadr rhs) nts)))
+ (when (member (cadr rhs) nts)
+ (error "Adjacent non-terminals: %s %s"
+ (car rhs) (cadr rhs)))
(pushnew (cadr rhs) first-ops)))
(let ((shr (reverse rhs)))
(if (not (member (car shr) nts))
(pushnew (car shr) last-ops)
(pushnew (car shr) last-nts)
(when (consp (cdr shr))
- (assert (not (member (cadr shr) nts)))
+ (when (member (cadr shr) nts)
+ (error "Adjacent non-terminals: %s %s"
+ (cadr shr) (car shr)))
(pushnew (cadr shr) last-ops)))))
(push (cons nt first-ops) first-ops-table)
(push (cons nt last-ops) last-ops-table)
(setq rhs (cdr rhs)))))
;; Keep track of which tokens are openers/closer, so they can get a nil
;; precedence in smie-prec2->grammar.
- (puthash :smie-open/close-alist (smie-bnf-classify bnf) prec2)
- (puthash :smie-closer-alist (smie-bnf-closer-alist bnf) prec2)
+ (puthash :smie-open/close-alist (smie-bnf--classify bnf) prec2)
+ (puthash :smie-closer-alist (smie-bnf--closer-alist bnf) prec2)
+ (if (> smie-warning-count 0)
+ (display-warning
+ 'smie (format "Total: %d warnings" smie-warning-count)))
prec2))
;; (defun smie-prec2-closer-alist (prec2 include-inners)
;; openers)
;; alist)))
-(defun smie-bnf-closer-alist (bnf &optional no-inners)
+(defun smie-bnf--closer-alist (bnf &optional no-inners)
;; We can also build this closer-alist table from a prec2 table,
;; but it takes more work, and the order is unpredictable, which
;; is a problem for smie-close-block.
(pushnew (cons (car rhs) term) alist :test #'equal)))))))
(nreverse alist)))
-(defun smie-bnf-classify (bnf)
+(defun smie-bnf--set-class (table token class)
+ (let ((prev (gethash token table class)))
+ (puthash token
+ (cond
+ ((eq prev class) class)
+ ((eq prev t) t) ;Non-terminal.
+ (t (display-warning
+ 'smie
+ (format "token %s is both %s and %s" token class prev))
+ 'neither))
+ table)))
+
+(defun smie-bnf--classify (bnf)
"Return a table classifying terminals.
-Each terminal can either be an `opener', a `closer', or neither."
+Each terminal can either be an `opener', a `closer', or `neither'."
(let ((table (make-hash-table :test #'equal))
- (nts (mapcar #'car bnf))
(alist '()))
(dolist (category bnf)
- (puthash (car category) 'neither table) ;Remove non-terminals.
+ (puthash (car category) t table)) ;Mark non-terminals.
+ (dolist (category bnf)
(dolist (rhs (cdr category))
(if (null (cdr rhs))
- (puthash (pop rhs) 'neither table)
- (let ((first (pop rhs)))
- (puthash first
- (if (memq (gethash first table) '(nil opener))
- 'opener
- (unless (member first nts)
- (error "SMIE: token %s is both opener and non-opener"
- first))
- 'neither)
- table))
- (while (cdr rhs)
- (puthash (pop rhs) 'neither table)) ;Remove internals.
- (let ((last (pop rhs)))
- (puthash last
- (if (memq (gethash last table) '(nil closer))
- 'closer
- (unless (member last nts)
- (error "SMIE: token %s is both closer and non-closer"
- last))
- 'neither)
- table)))))
+ (smie-bnf--set-class table (pop rhs) 'neither)
+ (smie-bnf--set-class table (pop rhs) 'opener)
+ (while (cdr rhs) ;Remove internals.
+ (smie-bnf--set-class table (pop rhs) 'neither))
+ (smie-bnf--set-class table (pop rhs) 'closer))))
(maphash (lambda (tok v)
(when (memq v '(closer opener))
(push (cons tok v) alist)))
(to (cdar eqs)))
(setq eqs (cdr eqs))
(if (eq to from)
- nil ;Nothing to do.
+ nil ;Nothing to do.
(dolist (other-eq eqs)
(if (eq from (cdr other-eq)) (setcdr other-eq to))
(when (eq from (car other-eq))
(smie-debug--describe-cycle
table (smie-debug--prec2-cycle csts)))))
(incf i 10))
- ;; Propagate equalities back to their source.
+ ;; Propagate equality constraints back to their sources.
(dolist (eq (nreverse eqs))
(when (null (cadr eq))
;; There's an equality constraint, but we still haven't given
(setcar (car eq) (cadr eq))
;; (smie-check-grammar table prec2 'step2)
)
- ;; Finally, fill in the remaining vars (which only appeared on the
- ;; right side of the < constraints).
- (let ((classification-table (gethash :smie-open/close-alist prec2)))
- (dolist (x table)
- ;; When both sides are nil, it means this operator binds very
- ;; very tight, but it's still just an operator, so we give it
- ;; the highest precedence.
- ;; OTOH if only one side is nil, it usually means it's like an
- ;; open-paren, which is very important for indentation purposes,
- ;; so we keep it nil if so, to make it easier to recognize.
- (unless (or (nth 1 x)
- (eq 'opener (cdr (assoc (car x) classification-table))))
- (setf (nth 1 x) i)
- (incf i)) ;See other (incf i) above.
- (unless (or (nth 2 x)
- (eq 'closer (cdr (assoc (car x) classification-table))))
- (setf (nth 2 x) i)
- (incf i))))) ;See other (incf i) above.
+ ;; Finally, fill in the remaining vars (which did not appear on the
+ ;; left side of any < constraint).
+ (dolist (x table)
+ (unless (nth 1 x)
+ (setf (nth 1 x) i)
+ (incf i)) ;See other (incf i) above.
+ (unless (nth 2 x)
+ (setf (nth 2 x) i)
+ (incf i)))) ;See other (incf i) above.
+ ;; Mark closers and openers.
+ (dolist (x (gethash :smie-open/close-alist prec2))
+ (let* ((token (car x))
+ (cons (case (cdr x)
+ (closer (cddr (assoc token table)))
+ (opener (cdr (assoc token table))))))
+ (assert (numberp (car cons)))
+ (setf (car cons) (list (car cons)))))
(let ((ca (gethash :smie-closer-alist prec2)))
(when ca (push (cons :smie-closer-alist ca) table)))
;; (smie-check-grammar table prec2 'step3)
OP-BACK is the accessor to the backward level of the level data.
HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the
first token we see is an operator, skip over its left-hand-side argument.
+HALFSEXP can also be a token, in which case it means to parse as if
+we had just successfully passed this token.
Possible return values:
(FORW-LEVEL POS TOKEN): we couldn't skip TOKEN because its back-level
is too high. FORW-LEVEL is the forw-level of TOKEN,
(nil POS TOKEN): we skipped over a paren-like pair.
nil: we skipped over an identifier, matched parentheses, ..."
(catch 'return
- (let ((levels ()))
+ (let ((levels
+ (if (stringp halfsexp)
+ (prog1 (list (cdr (assoc halfsexp smie-grammar)))
+ (setq halfsexp nil)))))
(while
(let* ((pos (point))
(token (funcall next-token))
;; Keep looking as long as we haven't matched the
;; topmost operator.
(levels
- (if (numberp (funcall op-forw toklevels))
- (push toklevels levels)))
+ (cond
+ ((numberp (funcall op-forw toklevels))
+ (push toklevels levels))
+ ;; FIXME: For some languages, we can express the grammar
+ ;; OK, but next-sexp doesn't stop where we'd want it to.
+ ;; E.g. in SML, we'd want to stop right in front of
+ ;; "local" if we're scanning (both forward and backward)
+ ;; from a "val/fun/..." at the same level.
+ ;; Same for Pascal/Modula2's "procedure" w.r.t
+ ;; "type/var/const".
+ ;;
+ ;; ((and (functionp (cadr (funcall op-forw toklevels)))
+ ;; (funcall (cadr (funcall op-forw toklevels))
+ ;; levels))
+ ;; (setq levels nil))
+ ))
;; We matched the topmost operator. If the new operator
;; is the last in the corresponding BNF rule, we're done.
((not (numberp (funcall op-forw toklevels)))
"Skip over one sexp.
HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the
first token we see is an operator, skip over its left-hand-side argument.
+HALFSEXP can also be a token, in which case we should skip the text
+assuming it is the left-hand-side argument of that token.
Possible return values:
(LEFT-LEVEL POS TOKEN): we couldn't skip TOKEN because its right-level
is too high. LEFT-LEVEL is the left-level of TOKEN,
(defun smie-forward-sexp (&optional halfsexp)
"Skip over one sexp.
HALFSEXP if non-nil, means skip over a partial sexp if needed. I.e. if the
-first token we see is an operator, skip over its left-hand-side argument.
+first token we see is an operator, skip over its right-hand-side argument.
+HALFSEXP can also be a token, in which case we should skip the text
+assuming it is the right-hand-side argument of that token.
Possible return values:
(RIGHT-LEVEL POS TOKEN): we couldn't skip TOKEN because its left-level
is too high. RIGHT-LEVEL is the right-level of TOKEN,
(indirect-function 'smie-op-left)
halfsexp))
-;;; Miscellanous commands using the precedence parser.
+;;; Miscellaneous commands using the precedence parser.
(defun smie-backward-sexp-command (&optional n)
"Move backward through N logical elements."
(push (car other) found))))))
(cond
((null found) (error "No known closer for opener %s" open))
- ;; FIXME: what should we do if there are various closers?
+ ;; What should we do if there are various closers?
(t (car found))))))))))
(unless (save-excursion (skip-chars-backward " \t") (bolp))
(newline))
;; anything else than this trigger char, lest we'd blink
;; both when inserting the trigger char and when
;; inserting a subsequent trigger char like SPC.
- (or (eq (point) pos)
+ (or (eq (char-before) last-command-event)
(not (memq (char-before)
smie-blink-matching-triggers)))
(or smie-blink-matching-inners
OFFSET can be:
nil use the default indentation rule.
-`(column . COLUMN) indent to column COLUMN.
+\(column . COLUMN) indent to column COLUMN.
NUMBER offset by NUMBER, relative to a base token
which is the current token for :after and
its parent for :before.
(unless (numberp (cadr (assoc tok smie-grammar)))
(goto-char pos))
(setq smie--parent
- (smie-backward-sexp 'halfsexp))))))
+ (or (smie-backward-sexp 'halfsexp)
+ (let (res)
+ (while (null (setq res (smie-backward-sexp))))
+ (list nil (point) (nth 2 res)))))))))
(defun smie-rule-parent-p (&rest parents)
"Return non-nil if the current token's parent is among PARENTS.
;; line, in which case we want to align it with its enclosing parent.
(cond
((and (eq method :before) (smie-rule-bolp) (not (smie-rule-sibling-p)))
- ;; FIXME: Rather than consult the number of spaces, we could *set* the
- ;; number of spaces so as to align the separator with the close-paren
- ;; while aligning the content with the rest.
(let ((parent-col (cdr (smie-rule-parent)))
(parent-pos-col ;FIXME: we knew this when computing smie--parent.
(save-excursion
(smie-indent-virtual)) ;:not-hanging
(scan-error nil)))))
-(defun smie-indent-keyword ()
- ;; Align closing token with the corresponding opening one.
- ;; (e.g. "of" with "case", or "in" with "let").
+(defun smie-indent-keyword (&optional token)
+ "Indent point based on the token that follows it immediately.
+If TOKEN is non-nil, assume that that is the token that follows point.
+Returns either a column number or nil if it considers that indentation
+should not be computed on the basis of the following token."
(save-excursion
(let* ((pos (point))
- (toklevels (smie-indent-forward-token))
- (token (pop toklevels)))
+ (toklevels
+ (if token
+ (assoc token smie-grammar)
+ (let* ((res (smie-indent-forward-token)))
+ ;; Ignore tokens on subsequent lines.
+ (if (and (< pos (line-beginning-position))
+ ;; Make sure `token' also *starts* on another line.
+ (save-excursion
+ (smie-indent-backward-token)
+ (< pos (line-beginning-position))))
+ nil
+ (goto-char pos)
+ res)))))
+ (setq token (pop toklevels))
(cond
- ((< pos (line-beginning-position))
- ;; The token we just read is actually not on the line where we started.
- nil)
+ ((null (cdr toklevels)) nil) ;Not a keyword.
((not (numberp (car toklevels)))
- (save-excursion
- (goto-char pos)
- ;; Different cases:
- ;; - smie-indent--bolp: "indent according to others".
- ;; - common hanging: "indent according to others".
- ;; - SML-let hanging: "indent like parent".
- ;; - if-after-else: "indent-like parent".
- ;; - middle-of-line: "trust current position".
- (cond
- ((null (cdr toklevels)) nil) ;Not a keyword.
- ((smie-indent--rule :before token))
- ((smie-indent--bolp) ;I.e. non-virtual indent.
- ;; For an open-paren-like thingy at BOL, always indent only
- ;; based on other rules (typically smie-indent-after-keyword).
- nil)
- (t
- ;; By default use point unless we're hanging.
- (unless (smie-indent--hanging-p) (current-column))))))
+ ;; Different cases:
+ ;; - smie-indent--bolp: "indent according to others".
+ ;; - common hanging: "indent according to others".
+ ;; - SML-let hanging: "indent like parent".
+ ;; - if-after-else: "indent-like parent".
+ ;; - middle-of-line: "trust current position".
+ (cond
+ ((smie-indent--rule :before token))
+ ((smie-indent--bolp) ;I.e. non-virtual indent.
+ ;; For an open-paren-like thingy at BOL, always indent only
+ ;; based on other rules (typically smie-indent-after-keyword).
+ nil)
+ (t
+ ;; By default use point unless we're hanging.
+ (unless (smie-indent--hanging-p) (current-column)))))
(t
;; FIXME: This still looks too much like black magic!!
- (let* ((parent (smie-backward-sexp 'halfsexp)))
+ (let* ((parent (smie-backward-sexp token)))
;; Different behaviors:
;; - align with parent.
;; - parent + offset.
(and (nth 4 (syntax-ppss))
'noindent))
+(defun smie-indent-inside-string ()
+ (and (nth 3 (syntax-ppss))
+ 'noindent))
+
(defun smie-indent-after-keyword ()
;; Indentation right after a special keyword.
(save-excursion
(defvar smie-indent-functions
'(smie-indent-fixindent smie-indent-bob smie-indent-close
- smie-indent-comment smie-indent-comment-continue smie-indent-comment-close
- smie-indent-comment-inside smie-indent-keyword smie-indent-after-keyword
+ smie-indent-comment smie-indent-comment-continue smie-indent-comment-close
+ smie-indent-comment-inside smie-indent-inside-string
+ smie-indent-keyword smie-indent-after-keyword
smie-indent-exps)
"Functions to compute the indentation.
Each function is called with no argument, shouldn't move point, and should
(save-excursion (indent-line-to indent))
(indent-line-to indent)))))
+(defun smie-auto-fill ()
+ (let ((fc (current-fill-column)))
+ (while (and fc (> (current-column) fc))
+ (cond
+ ((not (or (nth 8 (save-excursion
+ (syntax-ppss (line-beginning-position))))
+ (nth 8 (syntax-ppss))))
+ (save-excursion
+ (beginning-of-line)
+ (smie-indent-forward-token)
+ (let ((bsf (point))
+ (gain 0)
+ curcol)
+ (while (<= (setq curcol (current-column)) fc)
+ ;; FIXME? `smie-indent-calculate' can (and often will)
+ ;; return a result that actually depends on the presence/absence
+ ;; of a newline, so the gain computed here may not be accurate,
+ ;; but in practice it seems to works well enough.
+ (let* ((newcol (smie-indent-calculate))
+ (newgain (- curcol newcol)))
+ (when (> newgain gain)
+ (setq gain newgain)
+ (setq bsf (point))))
+ (smie-indent-forward-token))
+ (when (> gain 0)
+ (goto-char bsf)
+ (newline-and-indent)))))
+ (t (do-auto-fill))))))
+
+
(defun smie-setup (grammar rules-function &rest keywords)
"Setup SMIE navigation and indentation.
GRAMMAR is a grammar table generated by `smie-prec2->grammar'.
(set (make-local-variable 'smie-rules-function) rules-function)
(set (make-local-variable 'smie-grammar) grammar)
(set (make-local-variable 'indent-line-function) 'smie-indent-line)
+ (set (make-local-variable 'normal-auto-fill-function) 'smie-auto-fill)
(set (make-local-variable 'forward-sexp-function)
'smie-forward-sexp-command)
(while keywords
(while (setq closer (pop closers))
(unless (and closers
;; FIXME: this eliminates prefixes of other
- ;; closers, but we should probably elimnate
- ;; prefixes of other keywords as well.
+ ;; closers, but we should probably
+ ;; eliminate prefixes of other keywords
+ ;; as well.
(string-prefix-p closer (car closers)))
(push (aref closer (1- (length closer))) triggers)))
(delete-dups triggers)))))))