emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

SMIE documentation


From: Stefan Monnier
Subject: SMIE documentation
Date: Sun, 28 Nov 2010 15:36:26 -0500
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/24.0.50 (gnu/linux)

While Savannah is down, maybe someone will feel like checking my attempt
at documenting SMIE.  See patch below.  I intend to install it on the
emacs-23 branch, in case it matters.


        Stefan


=== modified file 'doc/lispref/modes.texi'
--- doc/lispref/modes.texi      2010-08-22 19:30:26 +0000
+++ doc/lispref/modes.texi      2010-11-28 20:30:57 +0000
@@ -27,6 +27,7 @@
 * Imenu::              How a mode can provide a menu
                          of definitions in the buffer.
 * Font Lock Mode::     How modes can highlight text according to syntax.
+* Auto-Indentation::            How to teach Emacs to indent for a major mode.
 * Desktop Save Mode::  How modes can have buffer state saved between
                          Emacs sessions.
 @end menu
@@ -333,7 +333,7 @@
 programming language, indentation of text according to structure is
 probably useful.  So the mode should set @code{indent-line-function}
 to a suitable function, and probably customize other variables
-for indentation.
+for indentation.  @xref{Auto-Indentation}.
 
 @item
 @cindex keymaps in modes
@@ -3223,6 +3215,651 @@
 reasonably fast.
 @end defvar
 
address@hidden Auto-Indentation
address@hidden Auto-indention of code
+
+For programming languages, an important feature of a major mode is to
+provide automatic indentation.  This is controlled in Emacs by
address@hidden (@pxref{Mode-Specific Indent}).
+Writing a good indentation function can be difficult and to a large
+extent it is still a black art.
+
+Many major mode authors will start by writing a simple indentation
+function that works for simple cases, for example by comparing with the
+indentation of the previous text line.  For most programming languages
+that are not really line-based, this tends to scale very poorly:
+improving such a function to let it handle more diverse situations tends
+to become more and more difficult, resulting in the end with a large,
+complex, unmaintainable indentation function which nobody dares to touch.
+
+A good indentation function will usually need to actually parse the
+text, according to the syntax of the language.  Luckily, it is not
+necessary to parse the text in as much detail as would be needed
+for a compiler, but on the other hand, the parser embedded in the
+indentation code will want to be somewhat friendly to syntactically
+incorrect code.
+
+Good maintainable indentation functions usually fall into 2 categories:
+either parsing forward from some ``safe'' starting point until the
+position of interest, or parsing backward from the position of interest.
+Neither of the two is a clearly better choice than the other: parsing
+backward is often more difficult than parsing forward because
+programming languages's syntax is designed to be parsed forward, but for
+the purpose of indentation it has the advantage of not needing to
+guess a ``safe'' starting point, and it generally enjoys the property
+that only a minimum of text will be analyzed to decide of the
+indentation of a line, so indentation will tend to be unaffected by
+syntax errors in some earlier unrelated piece of code.  Parsing forward
+on the other hand is usually easier and has the advantage of making it
+possible to reindent efficiently a whole region at a time,
+with a single parse.
+
+Rather than write your own indentation function from scratch, it is
+often preferable to try and reuse some existing ones or to rely
+on a generic indentation engine.  There are sadly few such
+engines.  The CC-mode indentation code (used for C, C++, Java, Awk
+and a few other such modes) has been made more generic over the years,
+so if your language seems somewhat similar to one of those languages,
+you might try to use that engine.  @c FIXME: documentation?
+Another one is SMIE which tries to take an approach in the spirit
+of Lisp sexps and adapt it to non-Lisp languages.
+
address@hidden
+* SMIE::                        A simple minded indentation engine
address@hidden menu
+
address@hidden SMIE
address@hidden Simple Minded Indentation Engine
+
+SMIE is a package that provides a generic navigation and indentation
+engine.  Based on a very simple parser using an ``operator
+precedence grammar'', it lets major modes extend the sexp-based
+navigation to non-Lisp languages as well as provide a simple to use but
+reliable auto-indentation.
+
+Operator precedence grammar is a very primitive technology for parsing
+compared to some of the more common techniques used in compilers.
+It has the following characteristics: algorithmically efficient, its
+parsing power is very limited, and it is largely unable to detect syntax
+errors, but it has the advantage of being able to parse forward just as
+well as backward.  In practice that means that we can use it for
+indentation based on backward parsing, that it can provide both
address@hidden and @code{backward-sexp} functionality, and also
+that it will naturally work on syntactically incorrect code without any
+extra effort; but it also means that most programming languages cannot
+be parsed correctly, at least not without resorting to some
+special tricks.
+
address@hidden
+* SMIE setup::                  SMIE setup and features
+* Operator Precedence Grammars::  A very simple parsing technique
+* SMIE Grammar::                Defining the grammar of a language
+* SMIE Lexer::                  Defining what are tokens
+* SMIE Tricks::                 Working around the parser's limitations
+* SMIE Indentation::            Specifying indentation rules
+* SMIE Indentation Helpers::    Helper functions for indentation rules
+* SMIE Indentation Example::    Sample indentation rules
address@hidden menu
+
address@hidden SMIE setup
address@hidden SMIE Setup and Features
+
+SMIE is meant to be a one-stop shop for structural navigation and
+various other features which rely on the syntactic structure of code,
+mainly automatic indentation.  The main entry point is @code{smie-setup}
+which is a function typically called while setting up a major mode.
+
address@hidden smie-setup grammar rules-function &rest keywords
+Setup SMIE navigation and indentation.
address@hidden is a grammar table generated by @code{smie-prec2->grammar}.
address@hidden is a set of indentation rules for use on
address@hidden
address@hidden are additional arguments, which can use the following keywords:
address@hidden
address@hidden
address@hidden:forward-token} @var{fun}: Specify the forward lexer to use.
address@hidden
address@hidden:backward-token} @var{fun}: Specify the backward lexer to use.
address@hidden itemize
address@hidden defun
+
+Calling this function is sufficient to make commands such as
address@hidden, @code{backward-sexp}, and @code{transpose-sexps}
+be able to properly handle structural elements other than just the paired
+parentheses already handled by syntax tables.  E.g. if the provided
+grammar is precise enough, @code{transpose-sexps} can correctly
+transpose the two arguments of a @code{+} operator, taking into account
+the precedence rules of the language.
+
+It is also sufficient to make TAB indentation work in the expected way,
+and provides some commands you can bind in the major mode keymap.
+Other packages could also use the data it sets up to provide
+structure-aware alignment or make @code{blink-matching-paren} work for
+things like @code{begin...end}.
+
address@hidden Command smie-close-block
+This command closes the most recently opened (and not yet closed) block.
address@hidden deffn
+
address@hidden Command smie-down-list &optional arg
+This command is like @code{down-list} but also pays attention to nesting
+of tokens other than parentheses, such as @code{begin...end}.
address@hidden deffn
+
address@hidden Operator Precedence Grammars
address@hidden Operator Precedence Grammars
+
+SMIE's precedence grammars simply give to each token a pair of
+precedences: the left-precedence and the right-precedence.  We say
address@hidden < T2} if the right-precedence of token @code{T1} is less than
+the left-precedence of token @code{T2}.  A good way to read this
address@hidden<} is as a kind of parenthesis: if we find @code{... T1 something
+T2 ...}  then it should be parsed as @code{... T1 (something T2 ...}
+rather than as @code{... T1 something) T2 ...} which would be the case
+if we had @code{T1 > T2}.  If we have @code{T1 = T2}, it means that
+token T2 follows token T1 in the same syntactic construction, so
+typically we have @code{"begin" = "end"}.  Such pairs of precedences are
+sufficient to express left-associativity or right-associativity of infix
+operators, nesting of tokens like parentheses and many other cases.
+
address@hidden @defvar smie-grammar
address@hidden This variable is an alist specifying the left and right 
precedence of
address@hidden each token.  It is meant to be initialized with the use of one 
of the
address@hidden functions below.
address@hidden @end defvar
+
address@hidden smie-prec2->grammar table
+This function takes a @emph{prec2} grammar @var{table} and returns an
+alist suitable for use in @code{smie-setup}.  The @emph{prec2}
address@hidden is itself meant to be built by one of the functions below.
address@hidden defun
+
address@hidden smie-merge-prec2s &rest tables
+This function takes several @emph{prec2} @var{tables} and merges them
+into a new @emph{prec2} table.
address@hidden defun
+
address@hidden smie-precs->prec2 precs
+This function builds a @emph{prec2} table from a table of precedences
address@hidden  @var{precs} should be a list, sorted by precedence (for
+example @code{"+"} will come before @code{"*"}), of elements of the form
address@hidden(@var{assoc} @var{op} ...)}, where @var{op} are tokens that
+act as operators and @var{assoc} is their associativity which can be
+either @code{left}, @code{right}, @code{assoc}, or @code{nonassoc}.
+All operators in one of those elements share the same precedence level
+and associativity.
address@hidden defun
+
address@hidden smie-bnf->prec2 bnf &rest resolvers
+This function lets you specify the grammar using a BNF notation.
+It takes a @var{bnf} description of the grammar along with a set of
+conflict resolution rules @var{resolvers} and
+returns a @emph{prec2} table.
+
address@hidden is a list of nonterminal definitions of the form
address@hidden(@var{nonterm} @var{rhs1} @var{rhs2} ...)} where each @var{rhs}
+is a (non-empty) list of terminals (aka tokens) or non-terminals.
+
+Not all grammars are accepted:
address@hidden
address@hidden
+An @var{rhs} cannot be an empty list (this is not needed, since SMIE
+allows all non-terminals to match the empty string anyway).
address@hidden
+An @var{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).
address@hidden itemize
+
+Additionally, conflicts can occur:
address@hidden
address@hidden
+The returned @emph{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.
address@hidden
+A token can either be an @code{opener} (something similar to an open-paren),
+a @code{closer} (like a close-paren), or @code{neither} of the two
+(e.g. an infix operator, or an inner token like @code{"else"}).
address@hidden itemize
+
+Precedence conflicts can be resolved via @var{resolvers}, which is a list of
address@hidden tables (see @code{smie-precs->prec2}): for each precedence
+conflicts, if those @code{precs} tables specify a particular constraint,
+then this constraint is used instead of reporting a conflict.
address@hidden defun
+
address@hidden SMIE Grammar
address@hidden Defining the Grammar of a Language
+
+The usual way to define the SMIE grammar of a language is by
+defining a new global variable holding the precedence table by
+giving a set of BNF rules.
+For example:
address@hidden
address@hidden
+(require 'smie)
+(defvar sample-smie-grammar
+  (smie-prec2->grammar
+   (smie-bnf->prec2
address@hidden group
address@hidden
+    '((id)
+      (inst ("begin" insts "end")
+            ("if" exp "then" inst "else" inst)
+            (id ":=" exp)
+            (exp))
+      (insts (insts ";" insts) (inst))
+      (exp (exp "+" exp)
+           (exp "*" exp)
+           ("(" exps ")"))
+      (exps (exps "," exps) (exp)))
address@hidden group
address@hidden
+    '((assoc ";"))
+    '((assoc ","))
+    '((assoc "+") (assoc "*")))))
address@hidden group
address@hidden example
+
address@hidden
+A few things to note:
+
address@hidden
address@hidden
+The above grammar did not explicitly mention the syntax of function
+calls: SMIE will automatically allow any sequence of sexps, such as
+identifiers, balanced parentheses, or @code{begin ... end} blocks
+to appear anywhere anyway.
address@hidden
+The grammar category @code{id} has no right hand side: this does not
+mean that it can only match the empty string, since as mentioned any
+sequence of sexp can appear anywhere anyway.
address@hidden
+Because non terminals cannot appear consecutively in the BNF grammar, it
+is difficult to correctly handle tokens that act as terminators, so the
+above grammar treats @code{";"} as a statement @emph{separator} instead,
+which SMIE can handle very well.
address@hidden
+Separators used in sequences (such as @code{","} and @code{";"} above)
+are best defined with BNF rules such as @code{(foo (foo "separator" foo) ...)}
+which generate precedence conflicts which are then resolved by giving
+them an explicit @code{(assoc "separator")}.
address@hidden
+The @code{("(" exps ")")} rule was not needed to pair up parens, since
+SMIE will pair up any chars that are marked as having paren syntax in
+the syntax table.  What this rule does instead (together with the
+definition of @code{exps}) is to make it clear that @code{","} will not
+appear outside of parentheses.
address@hidden
+Rather than have a single @emph{precs} table to resolve conflicts, it is
+preferable to use several tables, so as to let the BNF part of the
+grammar specify relative precedences where possible.
address@hidden
+Unless there is a very good reason to prefer @code{left} or
address@hidden, it is usually preferable to mark operators as associative
+with @code{assoc}.  For that reason @code{"+"} and @code{"*"} are
+defined above as @code{assoc}, although the language defines them
+formally as left associative.
address@hidden itemize
+
address@hidden SMIE Lexer
address@hidden Defining What is a Token
+
+SMIE comes with a predefined lexical analyzer which uses syntax tables
+in the following way: any sequence of chars that have word or symbol
+syntax is considered as a token, and so is any sequence of chars that
+have punctuation syntax.  This is often a good starting point but is
+rarely actually correct for any given language.  For example, it will
+consider @code{"2,+3"} as being composed of 3 tokens: @code{"2"},
address@hidden",+"}, and @code{"3"}.
+
+To describe the lexing rules of your language to SMIE, you will need
+2 functions, one to fetch the next token, and another to fetch the
+previous token.  Those functions will usually first skip whitespace and
+comments and then look at the next chunk of text to see if it
+is a special token, if so it should skip it and return a description of
+this token.  Usually this is simply the string extracted from the
+buffer, but this is not necessarily the case.
+For example:
address@hidden
address@hidden
+(defvar sample-keywords-regexp
+  (regexp-opt '("+" "*" "," ";" ">" ">=" "<" "<=" ":=" "=")))
address@hidden group
address@hidden
+(defun sample-smie-forward-token ()
+  (forward-comment (point-max))
+  (cond
+   ((looking-at sample-keywords-regexp)
+    (goto-char (match-end 0))
+    (match-string-no-properties 0))
+   (t (buffer-substring-no-properties
+       (point)
+       (progn (skip-syntax-forward "w_")
+              (point))))))
address@hidden group
address@hidden
+(defun sample-smie-backward-token ()
+  (forward-comment (- (point)))
+  (cond
+   ((looking-back sample-keywords-regexp (- (point) 2) t)
+    (goto-char (match-beginning 0))
+    (match-string-no-properties 0))
+   (t (buffer-substring-no-properties
+       (point)
+       (progn (skip-syntax-backward "w_")
+              (point))))))
address@hidden group
address@hidden example
+
+Notice how those lexers return the empty string when in front of
+parentheses.  This is because SMIE will automatically take care of the
+parentheses defined in the syntax table.  More specifically if the lexer
+returns nil or an empty string, SMIE will try to handle the corresponding
+text as an sexp according to syntax tables.
+
address@hidden SMIE Tricks
address@hidden Living With a Weak Parser
+
+The parsing technique used by SMIE does not allow tokens to behave
+different in different contexts.  For most programming languages, this
+will manifest itself by precedence conflicts when converting the BNF
+grammar.
+
+Sometimes, those conflicts can be worked around by expressing
+the grammar slightly differently.  For example for Modula-2 it might
+seem natural to have a BNF grammar that looks like:
+
address@hidden
+  ...
+  (inst ("IF" exp "THEN" insts "ELSE" insts "END")
+        ("CASE" exp "OF" cases "END")
+        ...)
+  (cases (cases "|" cases) (caselabel ":" insts) ("ELSE" insts))
+  ...
address@hidden example
+
+But this will create conflicts for @code{"ELSE"} since the IF rule
+implies (among many other things) that @code{"ELSE" = "END"}, but on the
+other hand, since @code{"ELSE"} appears within @code{cases} which
+appears left of @code{"END"}, we also have @code{"ELSE" > "END"}.
+We can solve it either by using:
address@hidden
+  ...
+  (inst ("IF" exp "THEN" insts "ELSE" insts "END")
+        ("CASE" exp "OF" cases "END")
+        ("CASE" exp "OF" cases "ELSE" insts "END")
+        ...)
+  (cases (cases "|" cases) (caselabel ":" insts))
+  ...
address@hidden example
+or
address@hidden
+  ...
+  (inst ("IF" exp "THEN" else "END")
+        ("CASE" exp "OF" cases "END")
+        ...)
+  (else (insts "ELSE" insts))
+  (cases (cases "|" cases) (caselabel ":" insts) (else))
+  ...
address@hidden example
+
+Some other times, after careful consideration you may conclude that
+those conflicts are not serious and simply resolve them via the
address@hidden argument of @code{smie-bnf->prec2}.  This is typically
+the case for separators and associative infix operators where we
+add a resolver like @code{'((assoc "|"))}.  Another case where this can
+happen is for the classic @emph{dangling else} problem where we will use
address@hidden'((assoc "else" "then"))}.  It can also happen for cases where the
+conflict is real and cannot really be resolved, but it is unlikely to
+pose problem in practice.
+
+Finally, in many cases some conflicts will remain despite all efforts to
+restructure the grammar.  Do not despair: while the parser cannot be
+made more clever, you can make the lexer as smart as you want.  So, the
+solution is then to look at the tokens involved in the conflict and to
+split one of those tokens into 2 (or more) different tokens.  E.g. if
+the grammar needs to distinguish between two incompatible uses of the
+token @code{"begin"}, make the lexer return different tokens (say
address@hidden"begin-fun"} and @code{"begin-plain"}) depending on what kind of
address@hidden"begin"} it finds.  This pushes the work of distinguishing the
+different cases to the lexer, which will thus have to look at the
+surrounding text to find ad-hoc clues.
+
address@hidden SMIE Indentation
address@hidden Specifying Indentation Rules
+
+Based on the provided grammar, SMIE will be able to provide automatic
+indentation without any extra effort.  But in practice, this default
+indentation style will probably not be good enough.  You will want to
+tweak it in many different cases.
+
+SMIE indentation is based on the idea that indentation rules should be
+as local as possible.  To this end, it relies on the idea of
address@hidden indentation, which is the indentation that a particular
+program point would have if it were at the beginning of a line.
+Of course, if that program point is indeed at the beginning of a line,
+the virtual indentation of that point is its current indentation.
+But if not, then SMIE will use the indentation algorithm to compute the
+virtual indentation of that point.  Now in practice, the virtual
+indentation of a program point does not have to be identical to the
+indentation it would have if we inserted a newline before it.  To see
+how this works, the SMIE rule for indentation
+after a @address@hidden in C will not care whether the @address@hidden is 
standing
+on a line of its own or is at the end of the preceding line.  Instead,
+these different cases will be handled in the indentation rule that
+decides how to indent before a @address@hidden
+
+An other important concept is the notion of @emph{parent}: The
address@hidden of a token, is the head token of the most closely
+enclosing syntactic construct.  For example, the parent of an
address@hidden will be the @code{if} to which it belongs, and the parent of
+an @code{if}, in turn, will be the lead token of the surrounding
+construct.  The command @code{backward-sexp} will jump from a token to
+its parent, but with some caveats: for @emph{openers} (tokens which
+start a construct, like @code{if}) you need to start before, while for
+others you need to start with point after the token; and
address@hidden will stop with point before the parent token if
+that is the @emph{opener} of the token of interest and otherwise it will
+stop with point after the parent token.
+
+SMIE indentation rules are specified with a function that takes two
+arguments @var{method} and @var{arg} where the meaning of @var{arg} and the
+expected return value depends on @var{method}.
+
address@hidden can be:
address@hidden
address@hidden
address@hidden:after}, in which case @var{arg} is a token and the function
+should return the @var{offset} to use for indentation after @var{arg}.
address@hidden
address@hidden:before}, in which case @var{arg} is a token and the function
+should return the @var{offset} to use to indent @var{arg} itself.
address@hidden
address@hidden:elem}, in which case the function should return either the offset
+to use to indent function arguments (if @var{arg} is the symbol
address@hidden) or the basic indentation step (if @var{arg} is the symbol
address@hidden).
address@hidden
address@hidden:list-intro}, in which case @var{arg} is a token and the function
+should return non-nil if the token is followed by a list of expressions
+(not separated by any token) rather than an expression.
address@hidden itemize
+
+When @var{arg} is a token, the function is called with point just before
+that token.  A return value of nil always means to fallback on the
+default behavior, so the function should return nil for arguments it
+does not expect.
+
address@hidden can be:
address@hidden
address@hidden
address@hidden: use the default indentation rule.
address@hidden
address@hidden(column . @var{column})}: indent to column @var{column}.
address@hidden
address@hidden: offset by @var{number}, relative to a base token which is
+the current token for @code{:after} and its parent for @code{:before}.
address@hidden itemize
+
address@hidden SMIE Indentation Helpers
address@hidden Helper Functions for Indentation Rules
+
+SMIE provides various functions designed specifically for use in the
+indentation rules function (several of those function will break if used
+in another context).  These functions all start with the prefix
address@hidden
+
address@hidden smie-rule-bolp
+Return address@hidden if the current token is the first on the line.
address@hidden defun
+
address@hidden smie-rule-hanging-p
+Return address@hidden if the current token is @emph{hanging}.
+A token is @emph{hanging} if it is at the last token on the line
+and if it is preceded by other tokens: a lone token on a line is not
+considered as hanging.
address@hidden defun
+
address@hidden smie-rule-next-p &rest tokens
+Return address@hidden if the next token is among @var{tokens}.
address@hidden defun
+
address@hidden smie-rule-prev-p &rest tokens
+Return address@hidden if the previous token is among @var{tokens}.
address@hidden defun
+
address@hidden smie-rule-parent-p &rest parents
+Return address@hidden if the current token's parent is among @var{parents}.
address@hidden defun
+
address@hidden smie-rule-sibling-p
+Return non-nil if the parent is actually a sibling.
+This is the case for example when the parent of a @code{","} is just the
+previous @code{","}.
address@hidden defun
+
address@hidden smie-rule-parent &optional offset
+Return the proper offset to align with the parent.
+If address@hidden, @var{offset} should be an integer giving an
+additional offset to apply.
address@hidden defun
+
address@hidden smie-rule-separator method
+Indent current token as a @emph{separator}.
+
+By @emph{separator}, we mean here a token whose sole purpose is to
+separate various elements within some enclosing syntactic construct, and
+which does not have any semantic significance in itself (i.e. it would
+typically no exist as a node in an abstract syntax tree).
+
+Such a token is expected to have an associative syntax and be closely
+tied to its syntactic parent.  Typical examples are @code{","} in lists
+of arguments (enclosed inside parentheses), or @code{";"} in sequences
+of instructions (enclosed in a @address@hidden@}} or @code{begin...end}
+block).
+
address@hidden should be the method name that was passed to
+`smie-rules-function'.
address@hidden defun
+
address@hidden SMIE Indentation Example
address@hidden Sample Indentation Rules
+
+Here is an example of an indentation function:
+
address@hidden
+(defun sample-smie-rules (kind token)
+  (case kind
+    (:elem (case token
+             (basic sample-indent-basic)))
+    (:after
+     (cond
+      ((equal token ",") (smie-rule-separator kind))
+      ((equal token ":=") sample-indent-basic)))
+    (:before
+     (cond
+      ((equal token ",") (smie-rule-separator kind))
+      ((member token '("begin" "(" "@{"))
+       (if (smie-rule-hanging-p) (smie-rule-parent)))
+      ((equal token "if")
+       (and (not (smie-rule-bolp)) (smie-rule-prev-p "else")
+            (smie-rule-parent)))))))
address@hidden example
+
address@hidden
+A few things to note:
+
address@hidden
address@hidden
+The first case indicates what is the basic indentation increment
+to use.  If @code{sample-indent-basic} is nil, then it defaults to the
+global setting @code{smie-indent-basic}.  The major mode could have set
address@hidden buffer-locally instead, but that is discouraged.
+
address@hidden
+The two (identical) rules for the token @code{","} make SMIE try to be
+more clever when the comma separator is placed at the beginning of
+lines; more specifically, it tries to outdent the separator so as to
+align the code after the comma; for example:
+
address@hidden
+x = longfunctionname (
+        arg1
+      , arg2
+    );
address@hidden example
+
address@hidden
+The rule for indentation after @code{":="} is there because otherwise
+SMIE would treat @code{":="} as an infix operator and would align the
+right argument with the left one.
+
address@hidden
+The rule for indentation before @code{"begin"} is an example of the use
+of virtual indentation: this rule is only used when @code{"begin"} is
+hanging, which can only happen when it is not at the beginning
+of a line, so clearly this is not used when indenting @code{"begin"}
+itself but only when indenting something relative to this
address@hidden"begin"}.  Concretely, this rule changes the indentation from:
+
address@hidden
+    if x > 0 then begin
+            dosomething(x);
+        end
address@hidden example
+to
address@hidden
+    if x > 0 then begin
+        dosomething(x);
+    end
address@hidden example
+
address@hidden
+The rule for indentation before @code{"if"} is a similar example, where
+the purpose is to treat @code{"else if"} as a single unit, so as to
+align a sequence of tests rather than indent each test further to the
+right.  This function chose to only do this in the case where the
address@hidden"if"} is not placed on a separate line, hence the
address@hidden test.
+
+If we know that the @code{"else"} is always aligned with its @code{"if"}
+and is always at the beginning of a line, we can use a more efficient
+rule:
address@hidden
+((equal token "if")
+ (and (not (smie-rule-bolp)) (smie-rule-prev-p "else")
+      (save-excursion
+        (sample-smie-backward-token)  ;Jump before the "else".
+        (cons 'column (current-column)))))
address@hidden example
+
+The advantage of this formulation is that it will reuse the indentation
+of the previous @code{"else"}, rather than having to go all the way back
+to the first @code{"if"} of the sequence.
address@hidden itemize
+
 @node Desktop Save Mode
 @section Desktop Save Mode
 @cindex desktop save mode
@@ -3276,5 +3913,7 @@
 @end defvar
 
 @ignore
-   arch-tag: 4c7bff41-36e6-4da6-9e7f-9b9289e27c8e
+   Local Variables:
+   fill-column: 72
+   End:
 @end ignore

=== modified file 'doc/lispref/text.texi'
--- doc/lispref/text.texi       2010-11-21 18:07:47 +0000
+++ doc/lispref/text.texi       2010-11-25 20:54:41 +0000
@@ -2205,11 +2205,11 @@
 @defvar indent-line-function
 This variable's value is the function to be used by @key{TAB} (and
 various commands) to indent the current line.  The command
address@hidden does no more than call this function.
address@hidden does little more than call this function.
 
 In Lisp mode, the value is the symbol @code{lisp-indent-line}; in C
 mode, @code{c-indent-line}; in Fortran mode, @code{fortran-indent-line}.
-The default value is @code{indent-relative}.
+The default value is @code{indent-relative}.  @xref{Auto-Indentation}.
 @end defvar
 
 @deffn Command indent-according-to-mode




reply via email to

[Prev in Thread] Current Thread [Next in Thread]