{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
module Language.Ninja.Lexer.Types
(
Parser, Ann
, Lexeme (..)
, LName (..)
, LFile (..)
, LBind (..)
, LBuild (..), makeLBuild
, LexemeConstraint
, LNameConstraint
, LFileConstraint
, LBindConstraint
, LBuildConstraint
, PositionParsing (..)
, spanned
) where
import Control.Arrow (second)
import Control.Monad (when)
import qualified Control.Lens as Lens
import Data.ByteString (ByteString)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Flow ((.>), (|>))
import qualified Text.Megaparsec as M
import Data.Aeson ((.:), (.=))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import Control.DeepSeq (NFData)
import Data.Hashable (Hashable)
import GHC.Generics (Generic)
import Test.SmallCheck.Series ((<~>))
import qualified Test.SmallCheck.Series as SC
import GHC.Exts (Constraint)
import qualified Language.Ninja.AST as AST
import qualified Language.Ninja.Misc as Misc
type Parser m a = M.ParsecT M.Dec Text m a
type Ann = Misc.Spans
class (Monad m) => PositionParsing m where
getPosition :: m Misc.Position
instance PositionParsing (M.ParsecT M.Dec Text m) where
getPosition = convert <$> M.getPosition
where
convert :: M.SourcePos -> Misc.Position
convert (M.SourcePos fp line column)
= let path = Lens.view (Lens.from Misc.pathString) fp
in Misc.makePosition (Just path) (toLine line, toColumn column)
toLine :: M.Pos -> Misc.Line
toColumn :: M.Pos -> Misc.Column
toLine = M.unPos .> fromIntegral
toColumn = M.unPos .> fromIntegral
spanned :: (PositionParsing m) => m a -> m (Misc.Spans, a)
spanned p = do
start <- getPosition
result <- p
end <- getPosition
let getPosFile :: Misc.Position -> Maybe Misc.Path
getPosFile = Lens.view Misc.positionFile
let (sfile, efile) = (getPosFile start, getPosFile end)
when (sfile /= efile) $ fail "spanned: somehow went over multiple files!"
let file = sfile
let offS = Lens.view Misc.positionOffset start
let offE = Lens.view Misc.positionOffset end
pure (Misc.makeSpans [Misc.makeSpan file offS offE], result)
data Lexeme ann
=
LexDefine !ann !(LBind ann)
|
LexBind !ann !(LBind ann)
|
LexInclude !ann !(LFile ann)
|
LexSubninja !ann !(LFile ann)
|
LexBuild !ann !(LBuild ann)
|
LexRule !ann !(LName ann)
|
LexPool !ann !(LName ann)
|
LexDefault !ann ![AST.Expr ann]
deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
instance Misc.Annotated Lexeme where
annotation' f = Lens.lens (helper .> fst) (helper .> snd)
where
helper (LexDefine ann v) = (ann, \x -> LexDefine x (f <$> v))
helper (LexBind ann v) = (ann, \x -> LexBind x (f <$> v))
helper (LexInclude ann v) = (ann, \x -> LexInclude x (f <$> v))
helper (LexSubninja ann v) = (ann, \x -> LexSubninja x (f <$> v))
helper (LexBuild ann v) = (ann, \x -> LexBuild x (f <$> v))
helper (LexRule ann v) = (ann, \x -> LexRule x (f <$> v))
helper (LexPool ann v) = (ann, \x -> LexPool x (f <$> v))
helper (LexDefault ann v) = (ann, \x -> LexDefault x (map (fmap f) v))
instance forall ann. (Aeson.ToJSON ann) => Aeson.ToJSON (Lexeme ann) where
toJSON = (\case (LexDefine ann value) -> obj ann "define" value
(LexBind ann value) -> obj ann "bind" value
(LexInclude ann value) -> obj ann "include" value
(LexSubninja ann value) -> obj ann "subninja" value
(LexBuild ann value) -> obj ann "build" value
(LexRule ann value) -> obj ann "rule" value
(LexPool ann value) -> obj ann "pool" value
(LexDefault ann value) -> obj ann "default" value)
where
obj :: forall x. (Aeson.ToJSON x) => ann -> Text -> x -> Aeson.Value
obj ann tag value = [ "ann" .= ann, "tag" .= tag, "value" .= value
] |> Aeson.object
instance (Aeson.FromJSON ann) => Aeson.FromJSON (Lexeme ann) where
parseJSON = (Aeson.withObject "Lexeme" $ \o -> do
ann <- o .: "ann"
tag <- o .: "tag"
case (tag :: Text) of
"define" -> LexDefine ann <$> (o .: "value")
"bind" -> LexBind ann <$> (o .: "value")
"include" -> LexInclude ann <$> (o .: "value")
"subninja" -> LexSubninja ann <$> (o .: "value")
"build" -> LexBuild ann <$> (o .: "value")
"rule" -> LexRule ann <$> (o .: "value")
"pool" -> LexPool ann <$> (o .: "value")
"default" -> LexDefault ann <$> (o .: "value")
owise -> invalidTagError (Text.unpack owise))
where
invalidTagError :: String -> Aeson.Parser a
invalidTagError x = [ "Invalid tag: ", x, "; expected one of: "
, show validTags
] |> mconcat |> fail
validTags :: [Text]
validTags = [ "define", "bind", "include", "subninja"
, "build", "rule", "pool", "default" ]
instance (Hashable ann) => Hashable (Lexeme ann)
instance (NFData ann) => NFData (Lexeme ann)
instance ( Monad m, LexemeConstraint (SC.Serial m) ann
) => SC.Serial m (Lexeme ann)
instance ( Monad m, LexemeConstraint (SC.CoSerial m) ann
) => SC.CoSerial m (Lexeme ann)
type LexemeConstraint (c :: * -> Constraint) (ann :: *)
= ( LBindConstraint c ann
, LFileConstraint c ann
, LBuildConstraint c ann
, LNameConstraint c ann
, c [AST.Expr ann]
, c ann
)
data LName ann
= MkLName
{ _lnameAnn :: !ann
, _lnameBS :: !ByteString
}
deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
instance Misc.Annotated LName where
annotation' _ = Lens.lens _lnameAnn
$ \(MkLName {..}) x -> MkLName { _lnameAnn = x, .. }
instance (Aeson.ToJSON ann) => Aeson.ToJSON (LName ann) where
toJSON (MkLName {..})
= [ "ann" .= _lnameAnn
, "name" .= Text.decodeUtf8 _lnameBS
] |> Aeson.object
instance (Aeson.FromJSON ann) => Aeson.FromJSON (LName ann) where
parseJSON = (Aeson.withObject "LName" $ \o -> do
_lnameAnn <- (o .: "ann") >>= pure
_lnameBS <- (o .: "name") >>= Text.encodeUtf8 .> pure
pure (MkLName {..}))
instance (Hashable ann) => Hashable (LName ann)
instance (NFData ann) => NFData (LName ann)
instance ( Monad m, LNameConstraint (SC.Serial m) ann
) => SC.Serial m (LName ann) where
series = SC.series |> fmap (second Text.encodeUtf8 .> uncurry MkLName)
instance ( Monad m, LNameConstraint (SC.CoSerial m) ann
) => SC.CoSerial m (LName ann) where
coseries = SC.coseries .> fmap (\f -> _lnameBS .> Text.decodeUtf8 .> f)
type LNameConstraint (c :: * -> Constraint) (ann :: *) = (c Text, c ann)
newtype LFile ann
= MkLFile
{ _lfileExpr :: AST.Expr ann
}
deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
instance (Aeson.ToJSON ann) => Aeson.ToJSON (LFile ann) where
toJSON (MkLFile {..})
= [ "file" .= _lfileExpr
] |> Aeson.object
instance (Aeson.FromJSON ann) => Aeson.FromJSON (LFile ann) where
parseJSON = (Aeson.withObject "LFile" $ \o -> do
_lfileExpr <- (o .: "file") >>= pure
pure (MkLFile {..}))
instance (Hashable ann) => Hashable (LFile ann)
instance (NFData ann) => NFData (LFile ann)
instance ( Monad m, LFileConstraint (SC.Serial m) ann
) => SC.Serial m (LFile ann)
instance ( Monad m, LFileConstraint (SC.CoSerial m) ann
) => SC.CoSerial m (LFile ann)
type LFileConstraint (c :: * -> Constraint) (ann :: *) = (c Text, c ann)
data LBind ann
= MkLBind
{ _lbindAnn :: !ann
, _lbindName :: !(LName ann)
, _lbindValue :: !(AST.Expr ann)
}
deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
instance (Aeson.ToJSON ann) => Aeson.ToJSON (LBind ann) where
toJSON (MkLBind {..})
= [ "ann" .= _lbindAnn
, "name" .= _lbindName
, "value" .= _lbindValue
] |> Aeson.object
instance (Aeson.FromJSON ann) => Aeson.FromJSON (LBind ann) where
parseJSON = (Aeson.withObject "LBind" $ \o -> do
_lbindAnn <- (o .: "ann") >>= pure
_lbindName <- (o .: "name") >>= pure
_lbindValue <- (o .: "value") >>= pure
pure (MkLBind {..}))
instance (Hashable ann) => Hashable (LBind ann)
instance (NFData ann) => NFData (LBind ann)
instance ( Monad m, LBindConstraint (SC.Serial m) ann
) => SC.Serial m (LBind ann)
instance ( Monad m, LBindConstraint (SC.CoSerial m) ann
) => SC.CoSerial m (LBind ann)
type LBindConstraint (c :: * -> Constraint) (ann :: *) = (c Text, c ann)
data LBuild ann
= MkLBuild
{ _lbuildAnn :: !ann
, _lbuildOuts :: ![AST.Expr ann]
, _lbuildRule :: !(LName ann)
, _lbuildDeps :: ![AST.Expr ann]
}
deriving (Eq, Show, Generic, Functor, Foldable, Traversable)
makeLBuild :: forall ann.
ann
-> [AST.Expr ann]
-> LName ann
-> [AST.Expr ann]
-> LBuild ann
makeLBuild ann outs rule deps
= let filterExprs :: [AST.Expr ann] -> [AST.Expr ann]
filterExprs = filter (\case (AST.Lit _ "") -> False
(AST.Exprs _ []) -> False
_ -> True)
in MkLBuild ann (filterExprs outs) rule (filterExprs deps)
instance Misc.Annotated LBuild where
annotation' f = Lens.lens _lbuildAnn
$ \(MkLBuild {..}) x ->
MkLBuild { _lbuildAnn = x
, _lbuildOuts = map (fmap f) _lbuildOuts
, _lbuildRule = f <$> _lbuildRule
, _lbuildDeps = map (fmap f) _lbuildOuts
, .. }
instance (Aeson.ToJSON ann) => Aeson.ToJSON (LBuild ann) where
toJSON (MkLBuild {..})
= [ "ann" .= _lbuildAnn
, "outs" .= _lbuildOuts
, "rule" .= _lbuildRule
, "deps" .= _lbuildDeps
] |> Aeson.object
instance (Aeson.FromJSON ann) => Aeson.FromJSON (LBuild ann) where
parseJSON = (Aeson.withObject "LBuild" $ \o -> do
_lbuildAnn <- (o .: "ann") >>= pure
_lbuildOuts <- (o .: "outs") >>= pure
_lbuildRule <- (o .: "rule") >>= pure
_lbuildDeps <- (o .: "deps") >>= pure
pure (MkLBuild {..}))
instance (Hashable ann) => Hashable (LBuild ann)
instance (NFData ann) => NFData (LBuild ann)
instance ( Monad m, LBuildConstraint (SC.Serial m) ann
) => SC.Serial m (LBuild ann) where
series = makeLBuild <$> SC.series <~> SC.series <~> SC.series <~> SC.series
instance ( Monad m, LBuildConstraint (SC.CoSerial m) ann
) => SC.CoSerial m (LBuild ann)
type LBuildConstraint (c :: * -> Constraint) (ann :: *) = (c Text, c ann)