-- The parser from the NoFib benchmark suite
-- TODO: getContents implementeren en gebruiken ipv readFile "big_big_test.hs"
-- TODO: internal error fixen

-- ==========================================================--
-- === Raw lexical analysis (tokenisation) of source      ===--
-- ===                                           Lexer.hs ===--
-- ==========================================================--

module Parser where
import Char -- 1.3
----------------------------------------------------------
-- Lexemes                                              --
----------------------------------------------------------

type Token = (Int, Int, Lex, String) -- (line, column, lexeme type, value)

data Lex = Lcon             -- constructor used as prefix:
                            -- normal prefix constructor,
                            -- or bracketed infix constructor

         | Lconop           -- constructor used as infix:
                            -- normal prefix constructor in backquotes,
                            -- or infix constructor (starting with ":")

         | Lvar             -- variable used as prefix:
                            -- normal prefix variable,
                            -- or bracketed infix var (operator)

         | Lvarop           -- variable used as infix:
                            -- normal prefix variable in backquotes,
                            -- or infix variable (operator)

         -- | Ltycon          -- constructor starting with A-Z
                              -- subcase of Lcon

         -- | Ltyvar          -- variable starting with a-z
                              -- subcase of Lvar

         | Lintlit          -- integer literal
         | Lcharlit         -- character literal
         | Lstringlit       -- string literal

         | Llbrace          --  {
         | Lrbrace          --  }
         | Lsemi            --  ;
         | Lequals          --  =
         | Lbar             --  |
         | Larrow           --  ->
         | Llparen          --  (
         | Lrparen          --  )
         | Lcomma           --  ,
         | Llbrack          --  [
         | Lrbrack          --  ]
         | Lunder           --  _
         | Lminus           --  -
         | Lslash           --  \

         | Lmodule
         | Linfixl
         | Linfixr
         | Linfix
         | Lext
         | Ldata
         | Lif
         | Lthen
         | Lelse
         | Llet
         | Lin
         | Lcase
         | Lof
         | Lwhere

         | Leof deriving (Eq, Show{-was:Text-})

{- 
   Lexing rules:

   case (
      if next is \,                                         -> Llparen
      if next is symbol, take symbols and expect closing )  -> Lvar
      if next is :, take tail-ident-chars, expect closing ) -> Lcon
      otherwise                                             -> Llparen

   case `
      if next A-Z, take tail-ident-chars, expect `          -> Lconop
      if next a-z, take tail-ident-chars, expect `          -> Lvarop
      otherwise                                             -> error

   case A-Z
      take tail-ident-chars                                 -> Lcon

   case a-z
      take tail-ident-chars                                 -> Lvar

   case 0-9
      take 0-9s                                             -> Lintlit

   case '
      expect a lit-char, then '                             -> charlit

   case "
      expect lit-chars, then "                              -> stringlit

   case {
      case -                                                -> run_comment
      otherwise                                             -> Llbrace

   case }                                                   -> Lrbrace

   case )                                                   -> Lrparen

   case [                                                   -> Llbrack
   case ]                                                   -> Lrbrack

   case ;                                                   -> Lsemi
   case ,                                                   -> Lcomma
   case _                                                   -> Lunder
   case -
      case -                                                -> line_comment
      case >                                                -> Larrow
      otherwise                                             -> Lminus

   case # in column 1: this is a preprocessor line

   case :!#$%&*+./<=>?@\^|~
      take symbols, then case resulting
         "="                                                -> Lequals
         "|"                                                -> Lbar
         "\"                                                -> Lslash
         otherwise
            if starts with :                                -> Lconop
            else                                            -> lvarop
-}



-- ==========================================================--
--
leLex :: Int -> Int -> String -> [Token]

leLex l n [] 
   = repeat (99997, 99997, Leof, "")

leLex l n ('(':[])
   = [(l, n, Llparen, ")")]

leLex l n ('(':c:cs)
   | c == ':'
   = case leChunk (n+1) leIsTailChar cs of
        (restSym, nn, restInput) -> case restInput of
           []        -> leFail l nn "  )  expected"
           (')':as)  -> (l, n, Lvar, c:restSym) : leLex l (nn+1) as
           (_:_)     -> leFail l nn "  )  expected"
   | c == '\\'
   = (l, n, Llparen, "(") : leLex l (n+1) (c:cs)
   | leIsSymbol c
   = case leChunk (n+1) leIsSymbol cs of
        (restSym, nn, restInput) -> case restInput of
           []        -> leFail l nn "  )  expected"
           (')':as)  -> (l, n, Lvar, c:restSym) : leLex l (nn+1) as
           (_:_)     -> leFail l nn "  )  expected"
   | otherwise
   = (l, n, Llparen, "(") : leLex l (n+1) (c:cs)

leLex l n ('`':c:cs)
   | isAlpha c
   = case leChunk (n+1) isAlpha cs of
        (restSym, nn, restInput) -> case restInput of
           []        -> leFail l nn "  `  expected"
           ('`':as)  -> (l, n, if isUpper c then Lconop else Lvarop, c:restSym) 
                        : leLex l (nn+1) as
           (_:_)     -> leFail l nn "  `  expected"
   | otherwise
   = leFail l n "Bad infix operator"

leLex l n ('"':cs)
   = case leTakeLitChars True l (n+1) cs of
        (restSym, nn, restInput) -> case restInput of
           []        -> leFail l nn "  \"  expected"
           ('"':as)  -> (l, n, Lstringlit, restSym) : leLex l (nn+1) as
           (_:_)     -> leFail l nn "  \"  expected"

leLex l n ('\'':cs)
   = case leTakeLitChars False l (n+1) cs of
        (restSym, nn, restInput) -> case restInput of
           []        -> leFail l nn "  '  expected"
           ('\'':as) -> case restSym of
                           [_] -> (l, n, Lcharlit, restSym) : leLex l (nn+1) as
                           _   -> leFail l (n+1) "Bad character literal"
           (_:_)     -> leFail l nn "  '  expected"

leLex l n ('}':cs)
   = (l, n, Lrbrace, "}") : leLex l (n+1) cs

leLex l n (')':cs)
   = (l, n, Lrparen, ")") : leLex l (n+1) cs

leLex l n ('[':cs)
   = (l, n, Llbrack, "[") : leLex l (n+1) cs

leLex l n (']':cs)
   = (l, n, Lrbrack, "]") : leLex l (n+1) cs

leLex l n (';':cs)
   = (l, n, Lsemi, ";") : leLex l (n+1) cs

leLex l n (',':cs)
   = (l, n, Lcomma, ",") : leLex l (n+1) cs

leLex l n ('_':cs)
   = (l, n, Lunder, "_") : leLex l (n+1) cs

leLex l n ('{':cs)
   = case cs of
        []         -> [(l, n, Llbrace, "}")]
        ('-':cs2)  -> leLexRComment l (n+2) cs2
        (_:_)      -> (l, n, Llbrace, "}") : leLex l (n+1) cs

leLex l n ('-':cs)
   = case cs of
        []         -> [(l, n, Lminus, "-")]
        ('-':cs2)  -> leLexLComment l (n+2) cs2
        ('>':cs3)  -> (l, n, Larrow, "->") : leLex l (n+2) cs3
        ('}':cs3)  -> leFail l n "Misplaced -}"
        (_:_)      -> (l, n, Lminus, "-") : leLex l (n+1) cs

leLex l n (' ':cs) 
   = leLex l (n+1) cs

leLex l n ('\n':cs)
   = leLex (l+1) 1 cs

leLex l n ('\t':cs)
   = leLex l (n - (n `mod` 8) + 9) cs

leLex l n (c:cs)
   = if   c == '#'
     then if n == 1
          then
          {- This is a CPP line number thingy -}
          let lineNoText  = takeWhile isDigit (tail cs)
              lineNo      = leStringToInt lineNoText
              nextLine    = drop 1 (dropWhile ((/=) '\n') cs)
          in
              leLex lineNo 1 nextLine
          else
          {- it's a symbol starting with # -}
          case leChunk (n+1) leIsSymbol cs of
             (restSym, nn, restText) -> (l, n, Lvarop, c:restSym) :
                                        leLex l nn restText
     else
     if   isAlpha c
     then case leChunk (n+1) leIsTailChar cs of
             (restSym, nn, restText) -> (l, n, if   isUpper c 
                                               then Lcon 
                                               else Lvar, c:restSym) :
                                        leLex l nn restText 
     else
     if   isDigit c
     then case leChunk (n+1) isDigit cs of
             (restSym, nn, restText) -> (l, n, Lintlit, c:restSym) :
                                        leLex l nn restText 
     else
     if   leIsSymbol c
     then case leChunk (n+1) leIsSymbol cs of
             (restSym, nn, restText) -> (l, n, if   c == ':' 
                                               then Lconop 
                                               else Lvarop, c:restSym) :
                                        leLex l nn restText 
     else
     leFail l n ("Illegal character  " ++ [c])


-- ==========================================================--
--
leChunk :: Int -> (Char -> Bool) -> String -> (String, Int, String)

leChunk n proper [] 
  = ([], n, [])

leChunk n proper (c:cs)
  | proper c        
  = case leChunk (n+1) proper cs of
       (restId, col, restInput) -> (c:restId, col, restInput)
  | otherwise
  = ([], n, c:cs)


-- ==========================================================--
--
leTakeLitChars :: Bool -> Int -> Int -> String -> (String, Int, String)

leTakeLitChars d l n []
  = leFail l n "End of file inside literal"

leTakeLitChars d l n ('\\':'\\':cs)
  = case leTakeLitChars d l (n+2) cs of
       (rest, col, left) -> ('\\':rest, col, left)

leTakeLitChars d l n ('\\':'n':cs)
  = case leTakeLitChars d l (n+2) cs of
       (rest, col, left) -> ('\n':rest, col, left)

leTakeLitChars d l n ('\\':'t':cs)
  = case leTakeLitChars d l (n+2) cs of
       (rest, col, left) -> ('\t':rest, col, left)

leTakeLitChars d l n ('\\':'"':cs)
  = case leTakeLitChars d l (n+2) cs of
       (rest, col, left) -> ('"':rest, col, left)

leTakeLitChars d l n ('\\':'\'':cs)
  = case leTakeLitChars d l (n+2) cs of
       (rest, col, left) -> ('\'':rest, col, left)

leTakeLitChars d l n ('"':cs)
  | d      = ([], n, ('"':cs))
  | not d  = case leTakeLitChars d l (n+1) cs of
                (rest, col, left) -> ('"':rest, col, left)

leTakeLitChars d l n ('\'':cs)
  | not d  = ([], n, ('\'':cs))
  | d      = case leTakeLitChars d l (n+1) cs of
                (rest, col, left) -> ('\'':rest, col, left)

leTakeLitChars d l n ('\n':cs)
  = leFail l n "Literal exceeds line"

leTakeLitChars d l n ('\t':cs)
  = leFail l n "Literal contains tab"

leTakeLitChars d l n (c:cs)
  = case leTakeLitChars d l (n+1) cs of
       (rest, col, left) -> (c:rest, col, left)


-- ==========================================================--
--
leLexLComment :: Int -> Int -> String -> [Token]

leLexLComment l n cs
   = leLex (l+1) 1 (drop 1 (dropWhile ((/=) '\n') cs))


-- ==========================================================--
--
leLexRComment :: Int -> Int -> String -> [Token]

leLexRComment l n [] 
   = leFail l n "End of file inside {- ... -} comment"

leLexRComment l n ('-':'}':cs)
   = leLex l (n+2) cs

leLexRComment l n ('\n':cs)
   = leLexRComment (l+1) 1 cs

leLexRComment l n ('\t':cs)
   = leLexRComment l (n - (n `mod` 8) + 9) cs

leLexRComment l n (c:cs)
   = leLexRComment l (n+1) cs


-- ==========================================================--
--
leIsSymbol :: Char -> Bool

leIsSymbol c = c `elem` leSymbols

leSymbols = ":!#$%&*+./<=>?\\@^|~"


-- ==========================================================--
--
leIsTailChar :: Char -> Bool

leIsTailChar c 
   = isLower c || 
     isUpper c || 
     isDigit c || 
     c == '\'' || 
     c == '_'  ||
     c == '\''


-- ==========================================================--
--
leIsLitChar :: Char -> Bool

leIsLitChar c
   = c /= '\n' &&
     c /= '\t' &&
     c /= '\'' &&
     c /= '"'


-- ==========================================================--
--
leStringToInt :: String -> Int

leStringToInt
   = let s2i []      = 0
         s2i (d:ds)  = (fromEnum d - fromEnum '0') + 10 *s2i ds
     in s2i . reverse


-- ==========================================================--
--
leFail l n m
  = faiL ("Lexical error, line " ++ show l ++ ", col " ++ show n ++ 
          ":\n   " ++ m )

faiL m = error ( "\n\n" ++ m ++ "\n" )

-- ==========================================================--
-- === end                                       Lexer.hs ===--
-- ==========================================================--

-- ==========================================================--
-- === Keyword spotting, and offside rule implementation  ===--
-- ===                                          Layout.hs ===--
-- ==========================================================--

--module Layout

-- ==========================================================--
--
laKeyword :: Token -> Token

laKeyword (l, n, what, text) 
   = let
        f Lvarop "="      = Lequals
        f Lvarop "|"      = Lbar
        f Lvarop "\\"     = Lslash

        f Lvar "module"   = Lmodule
        f Lvar "infix"    = Linfix
        f Lvar "infixl"   = Linfixl
        f Lvar "infixr"   = Linfixr
        f Lvar "ext"      = Lext
        f Lvar "data"     = Ldata
        f Lvar "if"       = Lif
        f Lvar "then"     = Lthen
        f Lvar "else"     = Lelse
        f Lvar "let"      = Llet
        f Lvar "in"       = Lin
        f Lvar "case"     = Lcase
        f Lvar "of"       = Lof
        f Lvar "where"    = Lwhere

        f item words      = item
        
     in
         (l, n, f what text, text)


-- ==========================================================--
--
laLayout :: Int -> [Int] -> [Token] -> [Token]

laLayout l s []
   = laRbrace (length s - 1) 99999 99999

laLayout l s (t1:[])
   = t1 : laRbrace (length s - 1) 99998 99998

laLayout l (s:ss) (t1@(l1, n1, w1, c1) :
                   t2@(l2, n2, w2, c2) : ts)

   | w1 `elem` [Lof, Llet, Lwhere] && w2 /= Llbrace
   = t1 :
     (l1, n1, Llbrace, "{") :
     t2 :
     laLayout l2 (n2:s:ss) ts 

   | l1 == l
   = t1 :
     laLayout l (s:ss) (t2:ts)

   | n1 > s
   = t1 :
     laLayout l1 (s:ss) (t2:ts)

   | n1 == s
   = (l1, n1, Lsemi, ";") :
     t1 :
     laLayout l1 (s:ss) (t2:ts)

   | n1 < s
   = (l1, n1, Lrbrace, "}") :
     laLayout l ss (t1:t2:ts)


-- ==========================================================--
--
laRbrace c l n 
   = take c (repeat (l, n, Lrbrace, "}"))

-- ==========================================================--
--
laMain :: String -> [Token]

laMain
   = laLayout 1 [0] . map laKeyword . leLex 1 1


-- ==========================================================--
-- === end                                      Layout.hs ===--
-- ==========================================================--

-- ==========================================================--
-- === Abstract syntax for modules                        ===--
-- ===                                       AbsSyntax.hs ===--
-- ==========================================================--

--module AbsSyntax where

--1.3:data Maybe a = Nothing 
--             | Just a

type AList a b = [(a, b)]

type Id = String

data Module 
   = MkModule Id [TopDecl]
             deriving (Show{-was:Text-})

data FixityDecl
   = MkFixDecl Id (Fixity, Int)
             deriving (Show{-was:Text-})

data DataDecl
   = MkDataDecl Id ([Id], [ConstrAltDecl])
             deriving (Show{-was:Text-})

data TopDecl
   = MkTopF FixityDecl
   | MkTopD DataDecl
   | MkTopV ValBind
             deriving (Show{-was:Text-})

data Fixity
   = InfixL
   | InfixR
   | InfixN
             deriving (Eq,Show{-was:Text-})

type ConstrAltDecl
   = (Id, [TypeExpr])

data TypeExpr = TypeVar    Id
              | TypeArr    TypeExpr TypeExpr
              | TypeCon    Id [TypeExpr]
              | TypeList   TypeExpr
              | TypeTuple  [TypeExpr]
             deriving (Show{-was:Text-})

data ValBind
   = MkValBind Int Lhs Expr
             deriving (Show{-was:Text-})

data Lhs
   = LhsPat Pat
   | LhsVar Id [Pat]
             deriving (Show{-was:Text-})

data Pat 
   = PatVar Id
   | PatCon Id [Pat]
   | PatWild
   | PatList   [Pat]
   | PatTuple  [Pat]
             deriving (Show{-was:Text-})

data Expr
   = ExprVar      Id
   | ExprCon      Id
   | ExprApp      Expr Expr
   | ExprLam      [Pat] Expr
   | ExprCase     Expr [ExprCaseAlt]
   | ExprLetrec   [ValBind] Expr
   | ExprWhere    Expr [ValBind]
   | ExprGuards   [(Expr, Expr)]
   | ExprLiteral  Literal
   | ExprList     [Expr]
   | ExprTuple    [Expr]
   | ExprIf       Expr Expr Expr
   | ExprBar
   | ExprFail
             deriving (Show{-was:Text-})

data ExprCaseAlt
   = MkExprCaseAlt Pat Expr
             deriving (Show{-was:Text-})

data Literal
   = LiteralInt     Int
   | LiteralChar    Char
   | LiteralString  String
             deriving (Show{-was:Text-})

-- ==========================================================--
-- === end                                   AbsSyntax.hs ===--
-- ==========================================================--

-- ==========================================================--
-- === Parser generics                                    ===--
-- ===                                   ParserGeneric.hs ===--
-- ==========================================================--

--module ParserGeneric

type PEnv = AList String (Fixity, Int)

data PResult a = POk    PEnv [Token] a
               | PFail  Token

type Parser a = PEnv -> [Token] -> PResult a

type PEntry = (Bool, Expr, Id)

-- ==========================================================--
--
pgItem :: Lex -> Parser String

pgItem x env [] = PFail pgEOF

pgItem x env ((l, n, w, t):toks)
   | x == w     = POk env toks t
   | otherwise  = PFail (l, n, w, t)


-- ==========================================================--
--
pgAlts :: [Parser a] -> Parser a

pgAlts ps env toks
   = let
        useAlts [] bestErrTok 
           = PFail bestErrTok
        useAlts (p:ps) bestErrTok
           = case p env toks of
                PFail someErrTok -> useAlts ps (further someErrTok bestErrTok)
                successful_parse -> successful_parse
        further x1@(l1, n1, w1, t1) x2@(l2, n2, w2, t2)
           =      if l2 > l1 then x2
             else if l1 > l2 then x1
             else if n1 > n2 then x1
             else x2
     in
        useAlts ps (head (toks ++ [pgEOF])) 


-- ==========================================================--
--
pgThen2 :: (a -> b -> c) -> 
           Parser a -> 
           Parser b -> 
           Parser c

pgThen2 combine p1 p2 env toks
   = case p1 env toks of
     {
       PFail tok1 
         -> PFail tok1 ;
       POk env1 toks1 item1 
         -> case p2 env1 toks1 of
            {
              PFail tok2 
                -> PFail tok2 ;
              POk env2 toks2 item2
                -> POk env2 toks2 (combine item1 item2)
            }
     }


-- ==========================================================--
--
pgThen3 :: (a -> b -> c -> d) -> 
           Parser a -> 
           Parser b -> 
           Parser c -> 
           Parser d

pgThen3 combine p1 p2 p3 env toks
   = case p1 env toks of
     {
       PFail tok1 
         -> PFail tok1 ;
       POk env1 toks1 item1 
         -> case p2 env1 toks1 of
            {
              PFail tok2 
                -> PFail tok2 ;
              POk env2 toks2 item2
                -> case p3 env2 toks2 of
                   {
                     PFail tok3
                       -> PFail tok3 ;
                     POk env3 toks3 item3
                       -> POk env3 toks3 (combine item1 item2 item3)
                   }
            }
     }


-- ==========================================================--
--
pgThen4 :: (a -> b -> c -> d -> e) -> 
           Parser a -> 
           Parser b -> 
           Parser c -> 
           Parser d ->
           Parser e

pgThen4 combine p1 p2 p3 p4 env toks
   = case p1 env toks of
     {
       PFail tok1 
         -> PFail tok1 ;
       POk env1 toks1 item1 
         -> case p2 env1 toks1 of
            {
              PFail tok2 
                -> PFail tok2 ;
              POk env2 toks2 item2
                -> case p3 env2 toks2 of
                   {
                     PFail tok3
                       -> PFail tok3 ;
                     POk env3 toks3 item3
                       -> case p4 env3 toks3 of
                          {
                            PFail tok4 
                              -> PFail tok4 ;
                            POk env4 toks4 item4
                              -> POk env4 toks4 (combine item1 item2 item3 item4)
                          }
                   }
            }
     }


-- ==========================================================--
--
pgZeroOrMore :: Parser a -> Parser [a]

pgZeroOrMore p env toks
   = case p env toks of
     {
       PFail tok1 
         -> POk env toks [] ;
       POk env1 toks1 item1 
         -> case pgZeroOrMore p env1 toks1 of
            {
              PFail tok2 
                -> POk env1 toks1 [item1] ;
              POk env2 toks2 item2_list
                -> POk env2 toks2 (item1 : item2_list)
            }
     }
         

-- ==========================================================--
--
pgOneOrMore :: Parser a -> Parser [a]

pgOneOrMore p
   = pgThen2 (:) p (pgZeroOrMore p)


-- ==========================================================--
--
pgApply :: (a -> b) -> Parser a -> Parser b

pgApply f p env toks
   = case p env toks of
     {
       PFail tok1
         -> PFail tok1 ;
       POk env1 toks1 item1
         -> POk env1 toks1 (f item1)
     }


-- ==========================================================--
--
pgTwoOrMoreWithSep :: Parser a -> Parser b -> Parser [a]

pgTwoOrMoreWithSep p psep
   = pgThen4
        (\i1 s1 i2 rest -> i1:i2:rest)
        p
        psep
        p 
        (pgZeroOrMore (pgThen2 (\sep x -> x) psep p))


-- ==========================================================--
--
pgOneOrMoreWithSep :: Parser a -> Parser b -> Parser [a]

pgOneOrMoreWithSep p psep
   = pgThen2 (:) p (pgZeroOrMore (pgThen2 (\sep x -> x) psep p))


-- ==========================================================--
--
pgZeroOrMoreWithSep :: Parser a -> Parser b -> Parser [a]

pgZeroOrMoreWithSep p psep
   = pgAlts
     [
        pgOneOrMoreWithSep p psep,
        pgApply (\x -> x:[]) p,
        pgEmpty []
     ]


-- ==========================================================--
--
pgOptional :: Parser a -> Parser (Maybe a)

pgOptional p env toks
   = case p env toks of
     {
       PFail tok1
         -> POk env toks Nothing ;
       POk env2 toks2 item2
         -> POk env2 toks2 (Just item2)
     }


-- ==========================================================--
--
pgGetLineNumber :: Parser a -> Parser (Int, a)

pgGetLineNumber p env toks
   = let 
         lineNo = case (head (toks ++ [pgEOF])) of (l, n, w, t) -> l
     in
         case p env toks of
         {
           PFail tok1
             -> PFail tok1 ;
           POk env2 toks2 item2
             -> POk env2 toks2 (lineNo, item2)
         }


-- ==========================================================--
--
pgEmpty :: a -> Parser a

pgEmpty item env toks
   = POk env toks item


-- ==========================================================--
--
pgEOF :: Token

pgEOF = (88888, 88888, Lvar, "*** Unexpected end of source! ***")


-- ============================================================--
-- === Some kludgey stuff for implementing the offside rule ===--
-- ============================================================--

-- ==========================================================--
--
pgEatEnd :: Parser ()

pgEatEnd env [] 
   = POk env [] ()

pgEatEnd env (tok@(l, n, w, t):toks)
   | w == Lsemi || w == Lrbrace   = POk env toks ()
   | otherwise                    = POk env (tok:toks) ()


-- ==========================================================--
--
pgDeclList :: Parser a -> Parser [a]

pgDeclList p
   = pgThen3 (\a b c -> b) (pgItem Llbrace) 
                           (pgOneOrMoreWithSep p (pgItem Lsemi))
                           pgEatEnd


-- ==========================================================--
-- === end                               ParserGeneric.hs ===--
-- ==========================================================--

-- ==========================================================--
-- === The parser.                                        ===--
-- ===                                          Parser.hs ===--
-- ==========================================================--

--module Parser where

{- FIX THESE UP -}
utLookupDef env k def
   = head ( [ vv | (kk,vv) <- env, kk == k] ++ [def] )
panic = error
{- END FIXUPS -}

paLiteral :: Parser Literal
paLiteral
   = pgAlts 
     [
        pgApply (LiteralInt . leStringToInt) (pgItem Lintlit),
        pgApply (LiteralChar . head)         (pgItem Lcharlit),
        pgApply LiteralString              (pgItem Lstringlit)
     ]

paExpr
   = pgAlts 
     [
        paCaseExpr, 
        paLetExpr, 
        paLamExpr,
        paIfExpr,
        paUnaryMinusExpr,
        hsDoExpr []
     ]

paUnaryMinusExpr
   = pgThen2
        (\minus (_, aexpr, _) -> 
             ExprApp (ExprApp (ExprVar "-") (ExprLiteral (LiteralInt 0))) aexpr)
        paMinus
        paAExpr

paCaseExpr
   = pgThen4
        (\casee expr off alts -> ExprCase expr alts)
        (pgItem Lcase)
        paExpr
        (pgItem Lof)
        (pgDeclList paAlt)

paAlt
   = pgAlts
     [
        pgThen4
           (\pat arrow expr wheres 
                -> MkExprCaseAlt pat (pa_MakeWhereExpr expr wheres))
           paPat
           (pgItem Larrow)
           paExpr
           (pgOptional paWhereClause),
        pgThen3
           (\pat agrdrhss wheres
                -> MkExprCaseAlt pat
                      (pa_MakeWhereExpr (ExprGuards agrdrhss) wheres))
           paPat
           (pgOneOrMore paGalt)
           (pgOptional paWhereClause)
     ]

paGalt
   = pgThen4
        (\bar guard arrow expr -> (guard, expr))
        (pgItem Lbar)
        paExpr
        (pgItem Larrow)
        paExpr

paLamExpr
   = pgThen4
        (\lam patterns arrow rhs -> ExprLam patterns rhs)
        (pgItem Lslash)
        (pgZeroOrMore paAPat)
        (pgItem Larrow)
        paExpr

paLetExpr
   = pgThen4
        (\lett decls inn rhs -> ExprLetrec decls rhs)
        (pgItem Llet)
        paValdefs
        (pgItem Lin)
        paExpr

paValdefs 
   = pgApply pa_MergeValdefs (pgDeclList paValdef)

pa_MergeValdefs 
   = id

paLhs
   = pgAlts
     [
        pgThen2 (\v ps -> LhsVar v ps) paVar (pgOneOrMore paPat),
        pgApply LhsPat paPat
     ]

paValdef
   = pgAlts
     [
        pgThen4
           (\(line, lhs) eq rhs wheres 
                -> MkValBind line lhs (pa_MakeWhereExpr rhs wheres))
           (pgGetLineNumber paLhs)
           (pgItem Lequals)
           paExpr
           (pgOptional paWhereClause),
        pgThen3
           (\(line, lhs) grdrhss wheres 
                -> MkValBind line lhs 
                      (pa_MakeWhereExpr (ExprGuards grdrhss) wheres))
           (pgGetLineNumber paLhs)
           (pgOneOrMore paGrhs)
           (pgOptional paWhereClause)
     ]

pa_MakeWhereExpr expr Nothing 
   = expr
pa_MakeWhereExpr expr (Just whereClauses) 
   = ExprWhere expr whereClauses

paWhereClause
   = pgThen2 (\x y -> y) (pgItem Lwhere) paValdefs
paGrhs
   = pgThen4
        (\bar guard equals expr -> (guard, expr))
        (pgItem Lbar)
        paExpr
        (pgItem Lequals)
        paExpr
        

paAPat
   = pgAlts
     [
        pgApply PatVar paVar,
        pgApply (\id -> PatCon id []) paCon,
        pgApply (const PatWild) (pgItem Lunder),
        pgApply PatTuple
                (pgThen3 (\l es r -> es)
                         (pgItem Llparen) 
                         (pgTwoOrMoreWithSep paPat (pgItem Lcomma))
                         (pgItem Lrparen)),
        pgApply PatList
                (pgThen3 (\l es r -> es)
                         (pgItem Llbrack) 
                         (pgZeroOrMoreWithSep paPat (pgItem Lcomma))
                         (pgItem Lrbrack)),
        pgThen3 (\l p r -> p)
                (pgItem Llparen)
                paPat
                (pgItem Lrparen)
     ]

paPat
   = pgAlts
     [
        pgThen2 (\c ps -> PatCon c ps)
                paCon
                (pgOneOrMore paAPat),
        pgThen3 (\ap c pa -> PatCon c [ap,pa])
                paAPat
                paConop
                paPat,
        paAPat
     ]


paIfExpr
 = pgThen4
      (\iff c thenn (t,f) -> ExprIf c t f)
      (pgItem Lif)
      paExpr
      (pgItem Lthen)
      (pgThen3
         (\t elsee f -> (t,f))
         paExpr
         (pgItem Lelse)
         paExpr
      )

paAExpr
 = pgApply (\x -> (False, x, []))
   (pgAlts 
    [
       pgApply ExprVar paVar,
       pgApply ExprCon paCon,
       pgApply ExprLiteral paLiteral,
       pgApply ExprList paListExpr,
       pgApply ExprTuple paTupleExpr,
       pgThen3 (\l e r -> e) (pgItem Llparen) paExpr (pgItem Lrparen)
    ]
   )

paListExpr
   = pgThen3 (\l es r -> es) 
             (pgItem Llbrack) 
             (pgZeroOrMoreWithSep paExpr (pgItem Lcomma))
             (pgItem Lrbrack)

paTupleExpr
   = pgThen3 (\l es r -> es) 
             (pgItem Llparen) 
             (pgTwoOrMoreWithSep paExpr (pgItem Lcomma))
             (pgItem Lrparen)

paVar = pgItem Lvar
paCon = pgItem Lcon
paVarop = pgItem Lvarop
paConop = pgItem Lconop
paMinus = pgItem Lminus

paOp
 = pgAlts [
            pgApply (\x -> (True, ExprVar x, x)) paVarop,
            pgApply (\x -> (True, ExprCon x, x)) paConop,
            pgApply (\x -> (True, ExprVar x, x)) paMinus
          ]

paDataDecl
   = pgThen2
        (\dataa useful -> useful)
        (pgItem Ldata)
        paDataDecl_main

paDataDecl_main
   = pgThen4
        (\name params eq drhs -> MkDataDecl name (params, drhs))
        paCon
        (pgZeroOrMore paVar)
        (pgItem Lequals)
        (pgOneOrMoreWithSep paConstrs (pgItem Lbar))

paConstrs
   = pgThen2
        (\con texprs -> (con, texprs))
        paCon
        (pgZeroOrMore paAType)

paType 
   = pgAlts
     [
        pgThen3 
           (\atype arrow typee -> TypeArr atype typee)
           paAType
           (pgItem Larrow)
           paType,
        pgThen2
           TypeCon
           paCon
           (pgOneOrMore paAType),
        paAType
     ]

paAType
   = pgAlts
     [
        pgApply TypeVar paVar,
        pgApply (\tycon -> TypeCon tycon []) paCon,
        pgThen3
           (\l t r -> t)
           (pgItem Llparen)
           paType
           (pgItem Lrparen),
        pgThen3
           (\l t r -> TypeList t)
           (pgItem Llbrack)
           paType
           (pgItem Lrbrack),
        pgThen3
           (\l t r -> TypeTuple t)
           (pgItem Llparen)
           (pgTwoOrMoreWithSep paType (pgItem Lcomma))
           (pgItem Lrparen)
     ]

paInfixDecl env toks
  = let dump (ExprVar v) = v
        dump (ExprCon c) = c
    in
    pa_UpdateFixityEnv 
       (pgThen3
          (\assoc prio name -> MkFixDecl name (assoc, prio))
          paInfixWord
          (pgApply leStringToInt (pgItem Lintlit)) 
          (pgApply (\(_, op, _) -> dump op) paOp)
          env 
          toks 
       )

paInfixWord
  = pgAlts
    [
       pgApply (const InfixL) (pgItem Linfixl),
       pgApply (const InfixR) (pgItem Linfixr),
       pgApply (const InfixN) (pgItem Linfix)
    ]

pa_UpdateFixityEnv (PFail tok) 
   = PFail tok

pa_UpdateFixityEnv (POk env toks (MkFixDecl name assoc_prio))
   = let 
         new_env = (name, assoc_prio) : env
     in
         POk new_env toks (MkFixDecl name assoc_prio)

paTopDecl
   = pgAlts
     [
        pgApply MkTopF paInfixDecl,
        pgApply MkTopD paDataDecl,
        pgApply MkTopV paValdef
     ]

paModule
   = pgThen4
        (\modyule name wheree topdecls -> MkModule name topdecls)
        (pgItem Lmodule)
        paCon
        (pgItem Lwhere)
        (pgDeclList paTopDecl)
   
parser_test toks
   = let parser_to_test
            = --paPat
              --paExpr
              --paValdef
              --pgZeroOrMore paInfixDecl
              --paDataDecl
              --paType
              paModule
              --pgTwoOrMoreWithSep (pgItem Lsemi) (pgItem Lcomma)
              
     in
         parser_to_test hsPrecTable toks

-- ==============================================--
-- === The Operator-Precedence parser (yuck!) ===--
-- ==============================================--

--
-- ==========================================================--
--
hsAExprOrOp 
 = pgAlts [paAExpr, paOp]

hsDoExpr :: [PEntry] -> Parser Expr
-- [PaEntry] is a stack of operators and atomic expressions
-- hsDoExpr uses a parser (hsAexpOrOp :: Parsr PaEntry) for atomic
-- expressions or operators

hsDoExpr stack env toks = 
  let
     (validIn, restIn, parseIn, err)
        = case hsAExprOrOp env toks of
             POk env1 toks1 item1
                -> (True, toks1, item1, panic "hsDoExpr(1)")
             PFail err
                -> (False, panic "hsDoExpr(2)", panic "hsDoExpr(3)", err)
     (opIn, valueIn, nameIn)
        = parseIn
     (assocIn, priorIn)
        = utLookupDef env nameIn (InfixL, 9)
     shift
        = hsDoExpr (parseIn:stack) env restIn
  in 
     case stack of
        s1:s2:s3:ss
           | validIn && opS2 && opIn && priorS2 > priorIn
              -> reduce
           | validIn && opS2 && opIn && priorS2 == priorIn
              -> if assocS2 == InfixL && 
                    assocIn == InfixL 
                 then reduce
             else 
                 if assocS2 == InfixR && 
                    assocIn == InfixR 
                 then shift
             else PFail (head toks) -- Because of ambiguousness 
           | not validIn && opS2
              -> reduce
             where
               (opS1, valueS1, nameS1) = s1
               (opS2, valueS2, nameS2) = s2
               (opS3, valueS3, nameS3) = s3
               (assocS2, priorS2) = utLookupDef env nameS2 (InfixL, 9)
               reduce = hsDoExpr ((False, ExprApp (ExprApp valueS2 valueS3) 
                                                  valueS1, [])
                                  : ss) env toks
        s1:s2:ss
           | validIn && (opS1 || opS2) -> shift
           | otherwise -> reduce
             where
                (opS1, valueS1, nameS1) = s1
                (opS2, valueS2, nameS2) = s2
                reduce = hsDoExpr ((False, ExprApp valueS2 valueS1, []) : ss) 
                                  env toks
        (s1:[])
           | validIn -> shift
           | otherwise -> POk env toks valueS1
             where
                (opS1, valueS1, nameS1) = s1
        []
           | validIn -> shift
           | otherwise -> PFail err

-- ==========================================================--
-- === end                                      Parser.hs ===--
-- ==========================================================--

hsPrecTable :: PEnv
hsPrecTable = [
  ("-",     (InfixL, 6)),
  ("+",     (InfixL, 6)),
  ("*",     (InfixL, 7)),
  ("div",   (InfixN, 7)),
  ("mod",   (InfixN, 7)),

  ("<",     (InfixN, 4)),
  ("<=",    (InfixN, 4)),
  ("==",    (InfixN, 4)),
  ("/=",    (InfixN, 4)),
  (">=",    (InfixN, 4)),
  (">",     (InfixN, 4)),

  ("C:",        (InfixR, 5)),
  ("++",        (InfixR, 5)),
  ("\\",        (InfixN, 5)),
  ("!!",        (InfixL, 9)),
  (".",         (InfixR, 9)),
  ("^",         (InfixR, 8)),
  ("elem",      (InfixN, 4)),
  ("notElem",   (InfixN, 4)),

  ("||",    (InfixR, 2)),
  ("&&",    (InfixR, 3))]


main = do
    cs <- readFile "big_big_test.hs" -- TODO: cs <- getContents
    let tokens = laMain cs
    let parser_res = parser_test tokens
    putStr (showx parser_res)

showx (PFail t) 
 = "\n\nFailed on token: " ++ show t ++  "\n\n"

showx (POk env toks result)
 = "\n\nSucceeded, with:\n   Size env = " ++ show (length env) ++
   "\n   Next token = " ++ show (head toks) ++
   "\n\n   Result = " ++ show result ++ "\n\n"

-- ==========================================================--
--
layn :: [[Char]] -> [Char]

layn x =   f 1 x
           where
           f :: Int -> [[Char]] -> [Char]
           f n [] = []
           f n (a:x) = rjustify 4 (show n) ++") "++a++"\n"++f (n+1) x



-- ==========================================================--
--
rjustify :: Int -> [Char] -> [Char]
rjustify n s = spaces (n - length s)++s
               where
                  spaces :: Int -> [Char]
                  spaces m = copy m ' '

copy :: Int -> a -> [a]

copy n x = take (max 0 n) xs where xs = x:xs

