This is a multi-part message in MIME format.
--------------F93F7E72348E2F23CC7D1D40
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

Manuel says

> One of our students just pointed out an IMHO rather problematic clause in the > 
>layout rule ... So, I guess (I hope!!) there is a nifty trick that lets you
> achieve the same effect by using only conditions depending on local
> information ...

Attached is Haskell code which handles the layout rule reasonably well as a
separate pass between scanning and parsing (though it is Haskell 1.4 rather
than 98 and imperfect).
-- 
Ian                        [EMAIL PROTECTED], http://www.cs.bris.ac.uk/~ian
--------------F93F7E72348E2F23CC7D1D40
Content-Type: text/plain; charset=us-ascii; name="Layout.hs"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="Layout.hs"

{------------------------------------------------------------------------------
                                LAYOUT ANALYSIS

The layout function deals with the layout conventions of Haskell 1.4, inserting
extra "{", ";" and "}" tokens to represent implicit blocks.  The inserted
tokens are marked as implicit, and are inserted as early as possible in the
token stream, in order to promote well-phrased and well-positioned error
messages in case of trouble.  The layout function never fails; it is left up to
a parser to detect errors.

The Haskell standard says that a block (or layout list) is terminated "whenever
the syntactic category containing the layout list ends, that is, if an illegal
lexeme is encountered at a point where a close brace would be legal".  This can
only be implememented easily if layout processing is combined with parsing.
Here, layout processing is done separately, so an approximation to the standard
is achieved by keeping track of brackets.  See the end of this file for
examples where the layout function deviates from the standard.

ISSUES TO BE RESOLVED

-- "case" terminated by "where" may be common enough to make a special case
-- "case" terminated by "," may be worth dealing with
-- check that "let"s inside "do" (which don't have "in") are handled OK
-- check explicit blocks, and their interaction with implicit ones

Ian Holyer  @(#) $Id: Layout.hs,v 1.2 1998/10/26 15:18:39 ian Exp $
------------------------------------------------------------------------------}

module Layout (layout) where

import Haskell
import Lex

-- Start layout processing.  If the source does not begin with "module" or "{",
-- then there is an implicit surrounding block.  Here and elsewhere, a
-- lookahead past possible comments is done so that a token can be inserted
-- before the comments if necessary; also, the end-of-file token makes it
-- unnecessary to check for an empty token stream.

layout :: [Token] -> [Token]
layout ts =
   if s == "module" then comments ++ scanExplicit [] (tok:rest) else
   openBlock [] (Tok "}" 1 0 Implicit) ts
   where
   comments = takeWhile isComment ts
   tok @ (Tok s r c k) : rest = dropWhile isComment ts

-- A stack of tokens is used to keep track of the surrounding blocks.  For each
-- block, its opening "{" token is pushed onto the stack.  In an implicit
-- block, the brackets "(",")" and "[","]" and "case","of" and "let","in" and
-- "if","then","else" are tracked by putting the opening bracket on the stack
-- until the matching closing bracket is found.  Each opening bracket is stored
-- on the stack with the indent for the current block in place of its actual
-- column.

type Stack = [Token]

-- Scan the source tokens while in an explicit block (or while not in any
-- blocks) when layout is inactive.  Look for an explicit close block token, or
-- a keyword which indicates the beginning of a new block. Treat field selector
-- as an explicit block.

scanExplicit :: Stack -> [Token] -> [Token]
scanExplicit stack [] = []
scanExplicit stack (t@(Tok s r c k) : ts1) = case s of
   "}" -> t : closeBlock stack t ts1
   "where" -> t : openBlock stack t ts1
   "let" -> t : openBlock stack t ts1
   "do" -> t : openBlock stack t ts1
   "of" -> t : openBlock stack t ts1
   "{" -> openBlock stack undefined (t:ts1)
   _ -> t : scanExplicit stack ts1

-- Scan the source tokens while in an implicit block, with layout active.  The
-- parameters are the stack, the last token dealt with, and the remaining
-- tokens.  The block is terminated by indenting or by a suitable closing
-- bracket.  Treat field selector as an explicit block.

scanImplicit :: Stack -> Token -> [Token] -> [Token]
scanImplicit stack@(Tok bs br bc bk : stack1) last@(Tok ls lr lc lk) ts =
   if c < bc || k == EndToken then close else
   if c == bc && r > lr then newline else
   case s of
      "where" -> open
      "let" -> pushopen
      "do" -> open
      "of" -> popopen "case"
      "in" -> pop "let"
      "(" -> push
      ")" -> pop "("
      "[" -> push
      "]" -> pop "["
      "case" -> push
      "if" -> push
      "then" -> poppush "if"
      "else" -> pop "then"
      "}" -> close
      "{" -> openBlock stack undefined (tok:rest)
      _ -> continue
   where
   comments = takeWhile isComment ts
   tok @ (Tok s r c k) : rest = dropWhile isComment ts
   continue =
      comments ++ tok : scanImplicit stack tok rest
   push =
      comments ++ tok : scanImplicit (Tok s r bc k : stack) tok rest
   pushopen =
      comments ++ tok : openBlock  (Tok s r bc k : stack) tok rest
   pop s =
      if bs == s then comments ++ tok : scanImplicit stack1 tok rest else
      if bs == "{" then close else continue
   popopen s =
      if bs == s then comments ++ tok : openBlock stack1 tok rest else
      if bs == "{" then close else continue
   poppush s =
      if bs == s then
         comments ++ tok : scanImplicit (Tok s r bc k : stack1) tok rest else
      if bs == "{" then close else push
   open =
      comments ++ tok : openBlock stack tok rest
   close =
      let end = Tok "}" lr (lc + length ls) Implicit in
      end : closeBlock stack end ts
   newline =
      let semi = Tok ";" lr (lc + length ls) Implicit in
      semi : scanImplicit stack tok ts

-- Check for the start of a block, given the stack, the last token seen, and
-- the remaining tokens.  If it is implicit, insert an implicit "{" token after
-- the last token seen, and put it on the stack.  The copy on the stack is
-- positioned according to the first non-comment token in the block, in order
-- to record the block indent for later indent checking.

openBlock :: Stack -> Token -> [Token] -> [Token]
openBlock stack last ts =
   if null rest then comments else
   if s == "{" then comments ++ tok : scanExplicit (tok : stack) (tail rest)
   else
   Tok "{" lr (lc + length ls) Implicit : comments ++
      scanImplicit (Tok "{" r c Implicit : stack) tok rest
   where
   Tok ls lr lc lk = last
   comments = takeWhile isComment ts
   rest = dropWhile isComment ts
   tok @ (Tok s r c k) = head rest

-- Close the current block, discarding all stacked brackets and the "{" token.
-- Resume processing of an outer block, whether explicit or implicit, given the
-- stack, the token last seen, and the remaining tokens.

closeBlock :: Stack -> Token -> [Token] -> [Token]
closeBlock [] last ts = scanExplicit [] ts
closeBlock (Tok "{" r c k : stack1) last ts = resume stack1 last ts
closeBlock (tok : stack1) last ts = closeBlock stack1 last ts

resume :: Stack -> Token -> [Token] -> [Token]
resume [] last ts = scanExplicit [] ts
resume stack@(Tok "{" r c Keyword : stack1) last ts = scanExplicit stack ts
resume stack last ts = scanImplicit stack last ts

isComment (Tok _ _ _ Comment) = True
isComment (Tok _ _ _ _) = False

{------------------------------------------------------------------------------
Here is an example where this module does not conform to the standard.

   f x = case x of
      1 -> y + 1
      2 -> y + 2
      where y = 3

The case construct should be closed by the "where" since this cannot be the
first token of a case alternative.  A case construct cannot automatically be
closed when a "where" is encountered though, because a "where" clause can be
attached to a case alternative.  The above example has to be written by
indenting the case alternative further (relative to the "where").

Other bizarre examples are:

   a = n where n = 42 ; ; b = 43              -- where terminated by 2nd ";"
   c = (case 1 of 1->2, 3)                    -- case terminated by comma
   d = case x of 1->y where {y=2} where x=1   -- ditto by second where
   e = case 1 of 1->1 :: Int + 1              -- ditto by +
------------------------------------------------------------------------------}

--------------F93F7E72348E2F23CC7D1D40--



Reply via email to