packages feed

hdiff-0.0.0: executables/Languages/Clojure/Parser.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DataKinds #-}

module Languages.Clojure.Parser
    ( parseTop
    , parse
    , parseFile
    , parseTest
    , parseAsExprList

    -- AST
    , Expr(..)
    , FormTy(..)
    , CollTy(..)
    , Term(..)
    , Tag(..)
    , SepExprList(..)
    , Sep(..)
    ) where

import Text.Parsec hiding (Empty)
import Text.Parsec.Token hiding (braces, parens, brackets, identifier, operator)
import Text.Parsec.Language
import Data.Char hiding (Space)
import qualified Data.Text as T
import Data.Proxy

import Languages.Clojure.AST

lexer = makeTokenParser javaStyle
  { identStart = alphaNum <|> oneOf "_':*-&."
  , identLetter = alphaNum <|> oneOf ":_.'-/^?!><*#\"\\" <|> satisfy isSymbol
  }

parseSeq :: Parsec String () Expr
parseSeq = do
  p1 <- parseExpr
  whiteSpace lexer
  p2 <- (try parseSeq <|> parseEmptyProgram)
  return $ Seq p1 p2 

parseTop = whiteSpace lexer *> (try parseSeq <|> parseEmptyProgram) <* eof

parseAsExprList = do
  top <- parseTop
  return $ walkSeq top
  -- whiteSpace lexer *> (many parseSingleExpr) <* whiteSpace lexer <* eof
walkSeq (Seq a (Empty )) = a : []
walkSeq (Seq a b ) = a : walkSeq b
walkSeq (Empty ) = [Empty ]
walkSeq e = error $ "nowalk" ++ show e

parseExpr = choice
  [ try parseSpecial
  , try parseDispatch
  , parseCollection
  , parseComment
  , parseTerm
  ]

parseEmptyProgram = do
  return $ Empty 

parseTerm = do
    term <- parseTaggedString
    return $ Term term 

parseCollection = choice [ parseParens, parseVec, parseSet ]

parseSpecial = do
  ident <- parseSpecialIdent
  expr <- parseExpr
  return $ Special ident expr 

parseSpecialIdent = choice
  [ Quote <$ char '\''
  , SQuote <$ char '`'
  , UnQuote <$ char '~'
  , DeRef <$ char '@'
  , Meta <$ char '^'
  ]

parseTaggedString = choice [parseString, parseVar]

parseDispatch = do
  char '#'
  disp <- parseDispatchable
  return $ Dispatch disp
  where
    parseDispatchable = choice
      [ parseExpr
      , parseRegExp
      , parseTaggedLit
      ]
    --- ref: https://yobriefca.se/blog/2014/05/19/the-weird-and-wonderful-characters-of-clojure/
    -- parseParens covers the function marco
    -- parseTaggedLit covers the var macro (as identifiers can start with a quote ('))
    parseRegExp = do
      regExp <- parseString
      return $ Term regExp 

    parseTaggedLit = do
      tLit <- parseVar
      return $ Term tLit

    -- parseMeta = do
    --   start <- getPosition
    --   meta <- parseMetadata
    --   end <- getPosition
    --   return $ Term meta (mkRange start end)

parseComment = do
  char ';'
  comment <- manyTill anyChar (newline <|> eofS)
  -- single line comment, if we parse end here we have parsed newline as well
  return $ Comment (T.pack comment) 

eofS = do
  eof
  return '\n'

parens p = between (symbol lexer "(") (string ")") p
braces p = between (symbol lexer "{") (string "}") p
brackets p = between (symbol lexer "[") (string "]") p


parseSet = do
  contents <- braces parseSepExprList
  end <- getPosition
  return $ Collection Set contents 

parseVec = do
  contents <- brackets parseSepExprList
  return $ Collection Vec contents 

parseParens = do
  contents <- parens parseSepExprList
  return $ Collection Parens contents 

parseSepExprList = parseSepExprList1 <|> parseEmptyList

parseEmptyList = do
  return $ Nil 

parseSepExprList1 = do
  x <- parseExpr
  sep <- parseSep
  xs <- parseSepExprList
  return $ Cons x sep xs 

parseSep = choice
  [ Comma <$ lexeme lexer (char ',')
  , NewLine <$ lexeme lexer (many1 endOfLine)
  , Space <$ lexeme lexer (many1 (space <|> tab))
  , EmptySep <$ (lookAhead (anyChar) <|> eofS)
  ]
parseString = do
  qstring <- quotedString
  return $ TaggedString String (T.pack qstring) 
  where
    quotedString :: Parsec String () String
    quotedString = do
      char '"'
      x <- many (try (try (string "\\\\") <|> string "\\\"") <|> fmap pure (noneOf "\""))
      char '"'
      return $ concat x

parseVar = do
  vstring <- (identifier)
  return $ TaggedString Var (T.pack vstring) 

identifier = do
  c <- alphaNum <|> oneOf ":!#$%&*+./<=>?@\\^|-~_',"
  cs <- many (alphaNum <|> oneOf ":!?#$%&*+-/.<=>'?@^|~_'^\"\\" <|> satisfy isSymbol)
  return (c:cs)


parseFile :: FilePath -> IO (Either ParseError Expr)
parseFile fname = do 
  input <- readFile fname
  return (runParser parseTop () fname input)