axel-0.0.11: src/Axel/Parse.hs
{-# LANGUAGE GADTs #-}
module Axel.Parse where
import Axel.Prelude
import Axel.Eff.Error (Error(ParseError))
import Axel.Haskell.Language (haskellOperatorSymbols, haskellSyntaxSymbols)
import Axel.Parse.AST
( Expression(LiteralChar, LiteralInt, LiteralString, SExpression,
Symbol)
, bottomUpFmapSplicing
, getAnn
)
import qualified Axel.Sourcemap as SM (Expression)
import Axel.Sourcemap
( Position(Position, _column, _line)
, SourceMetadata
, quoteSourceMetadata
, wrapCompoundExpressions
)
import Control.Applicative ((<|>))
import Control.Lens (op)
import Control.Monad (void)
import Data.List ((\\))
import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Data.Void (Void)
import qualified Polysemy as Sem
import qualified Polysemy.Error as Sem
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import qualified Text.Megaparsec.Char.Lexer as P (charLiteral)
type Parser = P.Parsec Void Text
-- Adapted from https://hackage.haskell.org/package/megaparsec-7.0.5/docs/Text-Megaparsec-Char-Lexer.html#v:charLiteral.
stringLiteral :: Parser Text
stringLiteral = T.pack <$> (P.char '"' *> P.manyTill P.charLiteral (P.char '"'))
ann :: (SourceMetadata -> a -> b) -> Parser a -> Parser b
ann f x = do
parsecPosition <- P.getSourcePos
let sourcePosition =
( P.sourceName parsecPosition
, Position
{ _line = P.unPos $ P.sourceLine parsecPosition
, _column = P.unPos $ P.sourceColumn parsecPosition
})
f (Just sourcePosition) <$> x
parseReadMacro :: Text -> Text -> Parser SM.Expression
parseReadMacro prefix wrapper = do
expr <- P.string prefix *> expression
ann SExpression (pure [Symbol Nothing (T.unpack wrapper), expr])
eol :: Parser ()
eol = P.try (void P.eol) <|> P.eof
ignored :: Parser ()
ignored = P.skipMany $ P.try comment <|> void P.spaceChar
literalChar :: Parser SM.Expression
literalChar = ann LiteralChar (P.string "#\\" *> P.anySingle)
literalInt :: Parser SM.Expression
literalInt = ann LiteralInt (read <$> P.some P.digitChar)
literalList :: Parser SM.Expression
literalList =
ann
SExpression
((Symbol Nothing "list" :) <$>
(P.char '[' *> P.many sExpressionItem <* P.char ']'))
literalString :: Parser SM.Expression
literalString = ann LiteralString (T.unpack <$> stringLiteral)
quasiquotedExpression :: Parser SM.Expression
quasiquotedExpression = parseReadMacro "`" "quasiquote"
quotedExpression :: Parser SM.Expression
quotedExpression = parseReadMacro "'" "quote"
sExpressionItem :: Parser SM.Expression
sExpressionItem = ignored *> expression <* ignored
sExpression :: Parser SM.Expression
sExpression =
ann SExpression (P.char '(' *> P.many sExpressionItem <* P.char ')')
infixSExpression :: Parser SM.Expression
infixSExpression =
ann
SExpression
((Symbol Nothing "applyInfix" :) <$>
(P.char '{' *> P.many sExpressionItem <* P.char '}'))
spliceUnquotedExpression :: Parser SM.Expression
spliceUnquotedExpression = parseReadMacro "~@" "unquoteSplicing"
symbol :: Parser SM.Expression
symbol =
ann
Symbol
(P.some
(P.try P.alphaNumChar <|> P.try (P.oneOf ['\'', '_']) <|>
P.try (P.oneOf (map fst haskellSyntaxSymbols \\ syntaxSymbols)) <|>
P.oneOf (map fst haskellOperatorSymbols)))
unquotedExpression :: Parser SM.Expression
unquotedExpression = parseReadMacro "~" "unquote"
comment :: Parser ()
comment =
P.try (P.string "--" *> eol) <|>
void (P.string "-- " *> P.manyTill (void P.anySingle) eol)
expression :: Parser SM.Expression
expression =
P.try literalChar <|> P.try literalInt <|> P.try literalList <|>
P.try literalString <|>
P.try quotedExpression <|>
P.try quasiquotedExpression <|>
P.try spliceUnquotedExpression <|>
P.try unquotedExpression <|>
P.try sExpression <|>
P.try infixSExpression <|>
symbol
-- Adapted from Appendix A of "Quasiquotation in Lisp" by Alan Bawden.
expandQuasiquote :: SM.Expression -> SM.Expression
expandQuasiquote (SExpression _ [Symbol _ "unquote", expr]) = expr
expandQuasiquote (SExpression _ [Symbol _ "unquoteSplicing", _]) =
error
"Illegal splicing unquote at the top level of a quasiquote! (`~@foo is not allowed, but `(~@foo) is.)"
expandQuasiquote (SExpression ann' xs) =
SExpression
ann'
[ Symbol ann' "AST.SExpression"
, quoteSourceMetadata ann'
, SExpression
ann'
[ Symbol ann' "concat"
, SExpression ann' (Symbol ann' "list" : map expandQuasiquoteInList xs)
]
]
expandQuasiquote expr =
let ann' = getAnn expr
in SExpression ann' [Symbol ann' "quote", expr]
expandQuasiquoteInList :: SM.Expression -> SM.Expression
expandQuasiquoteInList (SExpression _ [Symbol _ "unquoteSplicing", expr]) =
let ann' = getAnn expr
in SExpression ann' [Symbol ann' "AST.toExpressionList", expr]
expandQuasiquoteInList expr =
let ann' = getAnn expr
in SExpression ann' [Symbol ann' "list", expandQuasiquote expr]
parseMultiple ::
(Sem.Member (Sem.Error Error) effs)
=> Maybe FilePath
-> Text
-> Sem.Sem effs [SM.Expression]
parseMultiple maybeFilePath input =
either throwErr (pure . expandQuasiquotes) $
P.parse program (T.unpack $ op FilePath filePath) input
where
filePath = fromMaybe (FilePath "") maybeFilePath
program = P.some (ignored *> expression <* ignored) <* P.eof
throwErr = Sem.throw . ParseError filePath . T.pack . P.errorBundlePretty
expandQuasiquotes =
map $
bottomUpFmapSplicing
(\case
SExpression _ (Symbol _ "quasiquote":xs') -> map expandQuasiquote xs'
x -> [x])
parseSource ::
(Sem.Member (Sem.Error Error) effs)
=> Maybe FilePath
-> Text
-> Sem.Sem effs SM.Expression
parseSource filePath input =
wrapCompoundExpressions <$> parseMultiple filePath input
syntaxSymbols :: String
syntaxSymbols = "()[]{}"