]> code.delx.au - gnu-emacs/blobdiff - lisp/emacs-lisp/smie.el
* lisp/emacs-lisp/autoload.el (autoload-make-program): Remove, unused.
[gnu-emacs] / lisp / emacs-lisp / smie.el
index 09095521b491806f41e084bf2ac788574aabfc6d..2a12f03e5145562dad5eb413bd00ac4f9d01c120 100644 (file)
@@ -1,6 +1,6 @@
-;;; 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
@@ -56,7 +56,7 @@
 ;; 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)
@@ -175,21 +210,54 @@ one of those elements share the same precedence level and associativity."
       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 ())
@@ -211,14 +279,18 @@ one of those elements share the same precedence level and associativity."
               ;; 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)
@@ -265,8 +337,11 @@ one of those elements share the same precedence level and associativity."
           (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)
@@ -321,7 +396,7 @@ one of those elements share the same precedence level and associativity."
 ;;                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.
@@ -349,28 +424,33 @@ from the table, e.g. the table will not include things like (\"if\" . \"else\").
                 (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))
         (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 '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 '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)))
@@ -461,7 +541,7 @@ PREC2 is a table as returned by `smie-precs->prec2' or
               (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))
@@ -499,7 +579,7 @@ PREC2 is a table as returned by `smie-precs->prec2' or
                    (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
@@ -514,24 +594,23 @@ PREC2 is a table as returned by `smie-precs->prec2' or
         (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)
@@ -544,9 +623,9 @@ PREC2 is a table as returned by `smie-precs->prec2' or
 This list is normally built by `smie-prec2->grammar'.
 Each element is of the form (TOKEN LEFT-LEVEL RIGHT-LEVEL).
 Parsing is done using an operator precedence parser.
-LEFT-LEVEL and RIGHT-LEVEL can be either numbers or nil, where nil
+LEFT-LEVEL and RIGHT-LEVEL can be either numbers or a list, where a list
 means that this operator does not bind on the corresponding side,
-i.e. a LEFT-LEVEL of nil means this is a token that behaves somewhat like
+e.g. a LEFT-LEVEL of nil means this is a token that behaves somewhat like
 an open-paren, whereas a RIGHT-LEVEL of nil would correspond to something
 like a close-paren.")
 
@@ -602,6 +681,8 @@ OP-FORW is the accessor to the forward level of the level data.
 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,
@@ -610,7 +691,10 @@ Possible return values:
   (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))
@@ -630,9 +714,10 @@ Possible return values:
                 (if (eq pos (point))
                     ;; We did not move, so let's abort the loop.
                     (throw 'return (list t (point))))))
-             ((null (funcall op-back toklevels))
+             ((not (numberp (funcall op-back toklevels)))
               ;; A token like a paren-close.
-              (assert (funcall op-forw toklevels)) ;Otherwise, why mention it?
+              (assert (numberp     ; Otherwise, why mention it in smie-grammar.
+                       (funcall op-forw toklevels)))
               (push toklevels levels))
              (t
               (while (and levels (< (funcall op-back toklevels)
@@ -640,7 +725,7 @@ Possible return values:
                 (setq levels (cdr levels)))
               (cond
                ((null levels)
-                (if (and halfsexp (funcall op-forw toklevels))
+                (if (and halfsexp (numberp (funcall op-forw toklevels)))
                     (push toklevels levels)
                   (throw 'return
                          (prog1 (list (or (car toklevels) t) (point) token)
@@ -656,11 +741,25 @@ Possible return values:
                    ;; Keep looking as long as we haven't matched the
                    ;; topmost operator.
                    (levels
-                    (if (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.
-                   ((null (funcall op-forw toklevels))
+                   ((not (numberp (funcall op-forw toklevels)))
                     ;; It is the last element, let's stop here.
                     (throw 'return (list nil (point) token)))
                    ;; If the new operator is not the last in the BNF rule,
@@ -687,6 +786,8 @@ Possible return values:
   "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,
@@ -704,7 +805,9 @@ Possible return values:
 (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,
@@ -719,7 +822,7 @@ Possible return values:
    (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."
@@ -765,7 +868,7 @@ Possible return values:
                 ;; intervention, e.g. for Octave's use of `until'
                 ;; as a pseudo-closer of `do'.
                 (closer)
-                ((or (equal levels '(nil)) (nth 1 (car levels)))
+                ((or (equal levels '(nil)) (numberp (nth 1 (car levels))))
                  (error "Doesn't look like a block"))
                 (t
                  ;; Now that smie-setup automatically sets smie-closer-alist
@@ -776,12 +879,12 @@ Possible return values:
                        (when (and (eq (nth 2 level) (nth 1 other))
                                   (not (memq other seen)))
                          (push other seen)
-                         (if (nth 2 other)
+                         (if (numberp (nth 2 other))
                              (push other levels)
                            (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))
@@ -817,8 +920,8 @@ This command assumes point is not in a string or comment."
                   (progn (goto-char start) (down-list inc) nil)
                 (forward-sexp inc)
                 (/= (point) pos)))
-             ((and levels (null (nth (+ 1 offset) levels))) nil)
-             ((and levels (null (nth (- 2 offset) levels)))
+             ((and levels (not (numberp (nth (+ 1 offset) levels)))) nil)
+             ((and levels (not (numberp (nth (- 2 offset) levels))))
               (let ((end (point)))
                 (goto-char start)
                 (signal 'scan-error
@@ -899,11 +1002,11 @@ This uses SMIE's tables and is expected to be placed on `post-self-insert-hook'.
                      ;; 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
-                         (null (nth 2 (assoc token smie-grammar)))))
+                         (not (numberp (nth 2 (assoc token smie-grammar))))))
             ;; The major mode might set blink-matching-check-function
             ;; buffer-locally so that interactive calls to
             ;; blink-matching-open work right, but let's not presume
@@ -940,7 +1043,7 @@ function should return nil for arguments it does not expect.
 
 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.
@@ -979,10 +1082,13 @@ the beginning of a line."
       (save-excursion
         (let* ((pos (point))
                (tok (funcall smie-forward-token-function)))
-          (unless (cadr (assoc tok smie-grammar))
+          (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.
@@ -1026,7 +1132,7 @@ Only meaningful when called from within `smie-rules-function'."
              ;; rules-function, so it gives it a chance to tweak
              ;; indentation (e.g. by forcing indentation relative to
              ;; its own parent, as in fn a => fn b => fn c =>).
-             (if (or (null (car smie--parent)) (smie-indent--hanging-p))
+             (if (or (listp (car smie--parent)) (smie-indent--hanging-p))
                  (smie-indent-virtual) (current-column))))))
 
 (defvar smie-rule-separator-outdent 2)
@@ -1084,9 +1190,6 @@ Only meaningful when called from within `smie-rules-function'."
   ;; 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
@@ -1215,35 +1318,48 @@ in order to figure out the indentation of some other (further down) point."
             (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)))
-      (if (null (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)))))
-
+           (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
+       ((null (cdr toklevels)) nil)     ;Not a keyword.
+       ((not (numberp (car toklevels)))
+        ;; 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.
@@ -1321,7 +1437,7 @@ in order to figure out the indentation of some other (further down) point."
               ;; So we use a heuristic here, which is that we only use virtual
               ;; if the parent is tightly linked to the child token (they're
               ;; part of the same BNF rule).
-              (if (car parent) (current-column) (smie-indent-virtual))))))))))
+              (if (car parent) (current-column) (smie-indent-virtual)))))))))))
 
 (defun smie-indent-comment ()
   "Compute indentation of a comment."
@@ -1377,6 +1493,10 @@ in order to figure out the indentation of some other (further down) point."
   (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
@@ -1389,11 +1509,11 @@ in order to figure out the indentation of some other (further down) point."
        ;; The default indentation after a keyword/operator is
        ;; 0 for infix, t for prefix, and use another rule
        ;; for postfix.
-       ((null (nth 2 toklevel)) nil)        ;A closer.
-       ((or (null (nth 1 toklevel))         ;An opener.
-            (rassoc tok smie-closer-alist)) ;An inner.
+       ((not (numberp (nth 2 toklevel))) nil)                   ;A closer.
+       ((or (not (numberp (nth 1 toklevel)))                    ;An opener.
+            (rassoc tok smie-closer-alist))                     ;An inner.
         (+ (smie-indent-virtual) (smie-indent--offset 'basic))) ;
-       (t (smie-indent-virtual))))))    ;An infix.
+       (t (smie-indent-virtual))))))                            ;An infix.
 
 (defun smie-indent-exps ()
   ;; Indentation of sequences of simple expressions without
@@ -1450,8 +1570,9 @@ in order to figure out the indentation of some other (further down) point."
 
 (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
@@ -1521,8 +1642,9 @@ KEYWORDS are additional arguments, which can use the following 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)))))))