Resources
- http://en.wikibooks.org/wiki/Haskell/Indentation
- http://www.haskell.org/onlinereport/haskell2010/haskellch10.html#x17-17800010.3
The layout keywords are let, where, of and do. The expressions
introduced by them need to be kept indented at the same level.
(gleaned from Parser.y.pp in the ghc sources)
let
HsLet
HsLet (HsLocalBinds id) (LHsExpr id) :: HsExpr id
^^keep aligned
LetStmt
LetStmt (HsLocalBindsLR idL idR) :: StmtLR idL idR
^^keep aligned
where
HsModule
-- not relevant to layout
ClassDecl :: TyClDecl
ClassDecl ....
ClsInstD :: InstDecl
ClsInstD typ binds sigs [fam_insts]
^^the binds, sigs, fam_insts should all align
GRHSs
GRHS [LStmt id] (LHsExpr id)
^^keep aligned
TyDecl :: TyClDecl
TyDecl name vars defn fvs
^^keep aligned
[The `where` is in the defn]
FamInstDecl
FamInstDecl tycon pats defn fvs
^^keep aligned
[The `where` is in the defn]
of
HsCase :: HsExpr
HsCase (LHsExpr id) (MatchGroup id)
^^keep aligned
do
DoExpr :: HsExpr
HsDo (HsStmtContext Name) [LStmt id] PostTcType
^^keep aligned
Perform bottom-up traversal, ensuring that each of the above elements is aligned in its column.
The effect of layout is specified in this section by describing how to add braces and semicolons to a laid-out program. The specification takes the form of a function L that performs the translation. The input to L is:
-
A stream of lexemes as specified by the lexical syntax in the Haskell report, with the following additional tokens:
-
If a
let,where,do, orofkeyword is not followed by the lexeme{, the token{n}is inserted after the keyword, where n is the indentation of the next lexeme if there is one, or 0 if the end of file has been reached. -
If the first lexeme of a module is not
{or module, then it is preceded by{n}where n is the indentation of the lexeme. Where the start of a lexeme is preceded only by white space on the same line, this lexeme is preceded by < n > where n is the indentation of the lexeme, provided that it is not, as a consequence of the first two rules, preceded by {n}. (NB: a string literal may span multiple lines – Section 2.6. So in the fragmentf = ("Hello
\Bill", "Jake")There is no < n > inserted before the \Bill, because it is not the beginning of a complete lexeme; nor before the ,, because it is not preceded only by white space.)
-
Algorithm
L (< n >: ts) (m : ms) = ; : (L ts (m : ms)) -- if m = n
= } : (L (< n >: ts) ms) -- if n < m
L (< n >: ts) ms = L ts ms
L ({n} : ts) (m : ms) = { : (L ts (n : m : ms)) -- if n > m (Note 1)
L ({n} : ts) [] = { : (L ts [n]) -- if n > 0 (Note 1)
L ({n} : ts) ms = { : } : (L (< n >: ts) ms) -- (Note 2)
L (} : ts) (0 : ms) = } : (L ts ms) -- (Note 3)
L (} : ts) ms = parse-error -- (Note 3)
L ({ : ts) ms = { : (L ts (0 : ms)) -- (Note 4)
L (t : ts) (m : ms) = } : (L (t : ts) ms) -- if m /= 0 and parse-error(t)
-- (Note 5)
L (t : ts) ms = t : (L ts ms)
L [] [] = []
L [] (m : ms) = } : L [] ms -- if m /= 0 (Note 6)
Note 1
A nested context must be further indented than the enclosing context (n > m). If not, L fails, and the compiler should indicate a layout error.
Note 2
If the first token after a where (say) is not indented more than the enclosing layout context, then the block must be empty, so empty braces are inserted. The {n} token is replaced by < n >, to mimic the situation if the empty braces had been explicit.
Note 3
By matching against 0 for the current layout context, we ensure that an explicit close brace can only match an explicit open brace. A parse error results if an explicit close brace matches an implicit open brace.
Note 4
This clause means that all brace pairs are treated as explicit layout contexts, including labelled construction and update (Section 3.15). This is a difference between this formulation and Haskell 1.4.
Note 5
The side condition parse-error(t) is to be interpreted as follows: if the tokens generated so far by L together with the next token t represent an invalid prefix of the Haskell grammar, and the tokens generated so far by L followed by the token “}” represent a valid prefix of the Haskell grammar, then parse-error(t) is true.
The test m /= 0 checks that an implicitly-added closing brace would match an implicit open brace.
Note 6
At the end of the input, any pending close-braces are inserted. It is an error at this point to be within a non-layout context (i.e. m = 0).