packages feed

sugar-0.0.1: src/Sugar/Parser.hs

{-# LANGUAGE TupleSections, DeriveGeneric, OverloadedStrings, CPP, NamedFieldPuns #-}
module Sugar.Parser
  ( Token(..)
  , TokenStep
  , TokenNote
  , ParseError
  , Parser(..)
  , flatten
  , sugarParse
  , sugarParseTopLevel
  , sugarParseMap
  , sugarParseList
  ) where

import Control.Monad
import Control.Applicative

import qualified Data.Text as T

import Sugar.Types
import Sugar.Lexer

data Token
  = Token'Unit TokenNote
  | Token'String String TokenNote
  | Token'List [TokenStep] Wrap TokenNote
  | Token'Map [(TokenStep,TokenStep)] TokenNote
  deriving (Show, Eq)

type TokenStep = (SourceLocation, Token)
type TokenNote = Maybe [TokenStep]
type ParseError = (Maybe SourceLocation, String)

newtype Parser a = Parser
  { runParser :: [LexemeStep] -> ([LexemeStep], Either ParseError a) }

instance Functor Parser where
  fmap f (Parser g) = Parser $ \ts -> let (ts', x) = g ts in (ts', fmap f x)

instance Applicative Parser where
  pure x = Parser $ \ts -> (ts, Right x)
  p <*> q = Parser $ \ts -> let (ts',x) = runParser p ts in case x of
    Left a -> (ts', Left a)
    Right b -> runParser (fmap b q) ts'

instance Alternative Parser where
  empty = Parser $ \ts -> (ts, Left (Nothing, ""))
  p1 <|> p2 = Parser $ \ts -> case runParser p1 ts of
    (ts', Left err)
      | ts' == ts   -> runParser p2 ts
      | otherwise -> (ts', Left err)
    success -> success

instance Monad Parser where
  (Parser p) >>= f = Parser $ \ts -> let (ts',x) = p ts in case x of
    Left a -> (ts', Left a)
    Right b -> runParser (f b) ts'

--

flatten :: TokenStep -> Sugar
flatten (_, s) = case s of
  Token'Unit note -> Sugar'Unit (fmap flatten <$> note)
  Token'String str note -> Sugar'Text (T.pack str) (fmap flatten <$> note)
  Token'List elems wrap note -> Sugar'List (flatten <$> elems) wrap (fmap flatten <$> note)
  Token'Map elems note -> Sugar'Map ((\(x,y) -> (flatten x, flatten y)) <$> elems) (fmap flatten <$> note)

--

sugarParse :: Parser TokenStep
sugarParse = do
  (loc, tkn) <- peek
  case tkn of
    Lexeme'Start -> sugarParse
    Lexeme'OpenCurl -> sugarParseMap
    Lexeme'OpenParen -> try sugarParseUnit <|> sugarParseParenList
    Lexeme'OpenSquare -> sugarParseSquareList
    Lexeme'QuoteStart -> sugarParseQuote
    Lexeme'StringStart -> sugarParseText
    Lexeme'SingleLineComment -> nextLexeme *> ignoreUntilNewLine loc *> sugarParse
    Lexeme'MultiLineCommentStart -> nextLexeme *> ignoreUntilMultilineCommentEnd 0 *> sugarParse
    _ -> sugarParseUnexpected (loc, tkn)

ignoreUntilNewLine :: SourceLocation -> Parser ()
ignoreUntilNewLine sl = do
  tkn' <- tryPeek
  case tkn' of
    Nothing -> return ()
    Just (loc,_) -> if slLine loc == slLine sl
      then nextLexeme *> ignoreUntilNewLine loc
      else return ()

ignoreUntilMultilineCommentEnd :: Int -> Parser ()
ignoreUntilMultilineCommentEnd nested = do
  tkn' <- tryPeek
  case tkn' of
    Nothing -> sugarParseExpected "`|#` to close multi-line comment"
    Just (_,Lexeme'MultiLineCommentStart) -> nextLexeme *> ignoreUntilMultilineCommentEnd (nested + 1)
    Just (_,Lexeme'MultiLineCommentEnd) -> do
      void nextLexeme
      when (nested > 0) $ ignoreUntilMultilineCommentEnd (nested - 1)
    Just (_,_) -> nextLexeme *> ignoreUntilMultilineCommentEnd nested

sugarParseUnexpected :: LexemeStep -> Parser TokenStep
sugarParseUnexpected (loc, tkn) = Parser $ \ts -> (ts, Left (Just loc, "Unexpected: " ++ show tkn))

sugarParseExpected :: String -> Parser ()
sugarParseExpected expected =  Parser $ \ts -> (ts, Left (Nothing, "Expected: " ++ expected))

sugarParseNote :: Parser (Maybe [TokenStep])
sugarParseNote = do
  tkn' <- tryPeek
  case tkn' of
    Nothing -> return Nothing
    Just (_,tkn) -> case tkn of
      Lexeme'OpenAngle -> fmap pure $ between (lexeme Lexeme'OpenAngle) (lexeme Lexeme'CloseAngle) (many sugarParse)
      _ -> pure Nothing

sugarParseUnit :: Parser TokenStep
sugarParseUnit = do
  (sl,  _) <- lexeme Lexeme'OpenParen
  (sl', _) <- lexeme Lexeme'CloseParen
  if slColumn sl + 1 ==  slColumn sl' -- no space between parens
    then do
      note <- sugarParseNote
      let tkn = Token'Unit note
      pure (sl, tkn)
    else
      empty

sugarParseTopLevel :: Parser TokenStep
sugarParseTopLevel = sugarParseTopLevelMap

sugarParseMap :: Parser TokenStep
sugarParseMap = do
  (sl, _) <- lexeme Lexeme'OpenCurl
  elems <- many ((,) <$> sugarParse <*> sugarParse)
  void $ lexeme Lexeme'CloseCurl
  note <- sugarParseNote
  let tkn = Token'Map elems note
  pure (sl, tkn)

sugarParseTopLevelMap :: Parser TokenStep
sugarParseTopLevelMap = do
  elems <- many ((,) <$> sugarParse <*> sugarParse)
  let tkn = Token'Map elems Nothing
  case elems of
    (((sl,_), _):_) -> return (sl, tkn)
    [] -> return (SourceLocation 0 0, tkn)

sugarParseList :: Parser TokenStep
sugarParseList = try sugarParseSquareList <|> sugarParseParenList

sugarParseSquareList :: Parser TokenStep
sugarParseSquareList = do
  (sl, _) <- lexeme Lexeme'OpenSquare
  elems <- many sugarParse
  void $ lexeme Lexeme'CloseSquare
  note <- sugarParseNote
  let tkn = Token'List elems Wrap'Square note
  pure (sl, tkn)

sugarParseParenList :: Parser TokenStep
sugarParseParenList = do
  (sl, _) <- lexeme Lexeme'OpenParen
  elems <- many sugarParse
  void $ lexeme Lexeme'CloseParen
  note <- sugarParseNote
  let tkn = Token'List elems Wrap'Paren note
  pure (sl, tkn)

sugarParseQuote :: Parser TokenStep
sugarParseQuote = do
  (sl, _) <- lexeme Lexeme'QuoteStart
  s <- lexemeQuoteString
  void $ lexeme Lexeme'QuoteEnd
  note <- sugarParseNote
  let tkn = Token'String s note
  pure (sl, tkn)

sugarParseText :: Parser TokenStep
sugarParseText = do
  (sl, _) <- lexeme Lexeme'StringStart
  s <- lexemeString
  note <- sugarParseNote
  let tkn = Token'String s note
  pure (sl, tkn)

lexemeQuoteString :: Parser String
lexemeQuoteString = Parser $ \ts -> case ts of
  [] -> (ts, Left (Nothing, "lexemeQuoteString end"))
  (x:xs) -> case snd x of
    Lexeme'QuotedString s -> (xs, Right s)
    _ -> (xs, Left (Just $ fst x, "lexemeQuoteString none"))

lexemeString :: Parser String
lexemeString = Parser $ \ts -> case ts of
  [] -> (ts, Left (Nothing, "lexemeString end"))
  (x:xs) -> case snd x of
    Lexeme'String s -> (xs, Right s)
    _ -> (xs, Left (Just $ fst x, "lexemeString none"))

lexeme :: Lexeme -> Parser LexemeStep
lexeme t = Parser $ \ts -> case ts of
  [] -> (ts, Left (Nothing, "lexeme none"))
  (x:xs) -> if t == (snd x) then (xs, Right x) else (xs, Left (Just $ fst x, "lexeme no match"))

peek :: Parser LexemeStep
peek = Parser $ \ts -> case ts of
  [] -> (ts, Left (Nothing, "peek"))
  (x:_) -> (ts, Right $ x)

tryPeek :: Parser (Maybe LexemeStep)
tryPeek = Parser $ \ts -> case ts of
  [] -> (ts, Right Nothing)
  (x:_) -> (ts, Right $ Just x)

nextLexeme :: Parser LexemeStep
nextLexeme = Parser $ \ts -> case ts of
  [] -> ([], Left (Nothing, "nextLexeme"))
  (x:xs) -> (xs, Right x)

between :: Applicative m => m open -> m close -> m a -> m a
between open close p = open *> p <* close

try :: Parser a -> Parser a
try p = Parser $ \ts -> case runParser p ts of
  (_, Left a) -> (ts, Left a)
  (ts', Right b)  -> (ts', Right b)

{-
parseError :: String -> String -> Parser a
parseError descr tag = Parser $ \ts -> case ts of
    [] -> (ts, Left $ (Nothing, msg))
    ((loc,_):_) -> (ts, Left $ (Just loc, msg))
  where
    msg = tag ++ ": " ++ descr

choice :: String -> [Parser a] -> Parser a
choice description = foldr (<|>) noMatch
  where noMatch = parseError description "no match"
-}