zuramaru-0.1.0.0: src/Maru/QQ.hs
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE LambdaCase #-}
-- |
-- Present compile time lisp (zuramaru) literals as quasi-quotes.
--
-- The compile time inline lisp is contained :sunny:
--
-- You can use 'Maru.QQ.ShortName' instead of these functions,
-- if you don't worry name conflictions.
--
-- These requires to
-- `
-- import Maru.Type.SExpr (SExpr(..), MaruSymbol(..))
-- `
-- and add `text` to your package.yaml or {project}.cabal (below is a package.yaml example)
-- `
-- dependencies:
-- - text
-- `
--
-- and optionally requirements are existent, please see below.
--
-- If you want to use as patterns
-- (e.g. `let ![parse|123|] = AtomInt 123`)
-- `
-- {-# LANGUAGE OverloadedStrings #-}
-- `
--
-- If you want to use as types
-- (e.g. `f :: [parse|(1 2)|]`)
-- `
-- {-# LANGUAGE DataKinds #-}
-- `
module Maru.QQ
( parse
, parsePreprocess
, zurae
) where
import Control.Arrow ((>>>))
import Data.Text (Text)
import Language.Haskell.TH
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Maru.Type (SExpr(..), MaruSymbol(..), CallowSExpr(..))
import qualified Data.Text as T
import qualified Maru.Eval as Maru
import qualified Maru.Parser as Maru
import qualified Maru.Preprocessor as Maru
-- $setup
-- >>> :set -XQuasiQuotes
-- >>> :set -XDataKinds
-- >>> :set -XOverloadedStrings
-- >>> import Data.Singletons (sing, fromSing)
-- >>> import Maru.Type.Parser (ParseErrorResult)
-- >>> import Maru.Type.SExpr (SourceCode)
-- >>> import Maru.Type.TypeLevel (HighSExpr(..), Sing(..))
-- >>> import System.IO.Silently (silence)
--TODO: Implement tests to check the compiler errors (See 'Maru.QQ.parse', 'Maru.QQ.zurae' documentation for conditions of the compile error)
-- (What can I implement it ?)
textE :: Text -> Exp
textE t =
let x = T.unpack t
in VarE (mkName "Data.Text.pack") `AppE` LitE (StringL x)
intL :: Int -> Lit
intL = IntegerL . fromIntegral
boolP :: Bool -> Pat
boolP x = ConP (mkName $ show x) []
-- | A value `x :: Int` as a type `x :: 'Nat'`
intT :: Int -> Type
intT = LitT . NumTyLit . fromIntegral
-- | A value `x :: Bool` as a type `x :: 'Bool`
boolT :: Bool -> Type
boolT = PromotedT . mkName . show
-- | A value 'x :: Text' as a type `x :: 'Symbol'`
textSym :: Text -> Type
textSym = LitT . StrTyLit . T.unpack
toExp :: SExpr -> Exp
toExp (Cons x y) = ConE (mkName "Cons") `AppE` ParensE (toExp x) `AppE` ParensE (toExp y)
toExp (Quote x) = ConE (mkName "Quote") `AppE` ParensE (toExp x)
toExp Nil = ConE (mkName "Nil")
toExp (AtomInt x) = ConE (mkName "AtomInt") `AppE` LitE (intL x)
toExp (AtomBool x) = ConE (mkName "AtomBool") `AppE` ConE (mkName $ show x)
toExp (AtomSymbol (MaruSymbol x)) = ConE (mkName "AtomSymbol") `AppE`
ParensE (ConE (mkName "MaruSymbol") `AppE` textE x)
toPat :: SExpr -> Pat
toPat (Cons x y) = ConP (mkName "Cons") [toPat x, toPat y]
toPat (Quote x) = ConP (mkName "Quote") [toPat x]
toPat Nil = ConP (mkName "Nil") []
toPat (AtomInt x) = ConP (mkName "AtomInt") [LitP $ intL x]
toPat (AtomBool x) = ConP (mkName "AtomBool") [boolP x]
-- This requires OverloadedStrings
toPat (AtomSymbol (MaruSymbol x)) = ConP (mkName "AtomSymbol") [ConP (mkName "MaruSymbol") [LitP . StringL $ T.unpack x]]
toType :: SExpr -> Type
toType (Cons x y) = PromotedT (mkName "HCons") `AppT` ParensT (toType x) `AppT` ParensT (toType y)
toType (Quote x) = PromotedT (mkName "HQuote") `AppT` ParensT (toType x)
toType Nil = PromotedT (mkName "HNil")
--NOTE: Should specify a branch when `x < 0` ? or NumTyLit satisfies ?
toType (AtomInt x) = PromotedT (mkName "HAtomInt") `AppT` intT x
toType (AtomBool x) = PromotedT (mkName "HAtomBool") `AppT` boolT x
toType (AtomSymbol (MaruSymbol x)) = PromotedT (mkName "HAtomSymbol") `AppT` textSym x
-- |
-- Expand a code to 'SExpr'
-- if it can be parsed by 'Maru.Parser.parse'
-- (or to be compiled error).
--
-- Occure the compile error if it cannot be parsed.
--
-- >>> :{
-- >>> let p :: SourceCode -> Either ParseErrorResult SExpr
-- >>> p x = growUp <$> Maru.parse x -- with the empty preprocessor (growUp)
-- >>> :}
--
-- As expressions
--
-- >>> Right [parse|123|] == p "123"
-- True
-- >>> Right [parse|abc|] == p "abc"
-- True
-- >>> Right [parse|(123 456)|] == p "(123 456)"
-- True
--
-- As patterns
--
-- >>> case AtomInt 123 of; [parse|123|] -> "good"
-- "good"
-- >>> case AtomInt 000 of; [parse|123|] -> "bad"; AtomInt _ -> "good"
-- "good"
-- >>> case AtomSymbol "x" of; [parse|x|] -> "good"
-- "good"
-- >>> case Quote (AtomInt 10) of; [parse|'10|] -> "good"
-- "good"
-- >>> case Cons (AtomSymbol "x") (Cons (AtomInt 10) Nil) of; [parse|(x 10)|] -> "good"
-- "good"
--
-- As types (compile time calculations)
--
-- >>> fromSing (sing :: Sing [parse|10|])
-- AtomInt 10
-- >>> fromSing (sing :: Sing [parse|konoko|])
-- AtomSymbol "konoko"
-- >>> fromSing (sing :: Sing [parse|(1 2 3)|])
-- Cons (AtomInt 1) (Cons (AtomInt 2) (Cons (AtomInt 3) Nil))
parse :: QuasiQuoter
parse = QuasiQuoter
{ quoteExp = power toExp
, quotePat = power toPat
, quoteType = power toType
, quoteDec = error "Maru.QQ.parse: the expansion to `[Dec]` (`quoteDec`) isn't support supported"
}
where
power :: (SExpr -> a) -> String -> Q a
power f = T.pack >>> Maru.parse >>> \case
Left e -> fail $ Maru.parseErrorPretty e
Right a -> return . f $ growUp a
-- |
-- Similar to 'parse' but 'Maru.preprocessor.preprocess' is appended
--
-- >>> :{
-- >>> let pp :: SourceCode -> Either ParseErrorResult SExpr
-- >>> pp code = Maru.preprocess <$> Maru.parse code
-- >>> :}
--
-- >>> Right [parsePreprocess|sugar|] == pp "sugar"
-- True
-- >>> Right [parsePreprocess|10|] == pp "10"
-- True
-- >>> Right [parsePreprocess|(1 2 3)|] == pp "(1 2 3)"
-- True
-- >>> Right [parsePreprocess|'10|] == pp "'10"
-- True
parsePreprocess :: QuasiQuoter
parsePreprocess = QuasiQuoter
{ quoteExp = power toExp
, quotePat = power toPat
, quoteType = power toType
, quoteDec = error "Maru.QQ.parsePreprocess: the expansion to `[Dec]` (`quoteDec`) isn't support supported"
}
where
power :: (SExpr -> a) -> String -> Q a
power f = T.pack >>> Maru.parse >>> \case
Left e -> fail $ Maru.parseErrorPretty e
Right a -> return . f $ Maru.preprocess a
-- |
-- Similar to 'parsePreprocess' but it is ran in the compile time,
-- Occure the side effects,
-- and Return the evaluated result.
--
-- Occure the compile error if the code throws exceptions.
--
-- この関数名zuraeの発音はずら〜(ずらあ)です。
-- (the e suffix means an 'e'valuation)
--
-- NOTICE:
-- Please be careful to the halting problem.
-- The halting safety is abandon
-- because this ('Maru.Eval.eval') is turing-complete in the compile time :D
--
-- >>> :{
-- >>> let force :: SourceCode -> IO SExpr
-- >>> force code = do
-- >>> let (Right sexpr) = Maru.preprocess <$> Maru.parse code
-- >>> Right (result, _, _) <- silence $ Maru.eval Maru.initialEnv sexpr
-- >>> return result
-- >>> :}
--
-- >>> (==) <$> return [zurae|10|] <*> force "10"
-- True
-- >>> (==) <$> return [zurae|'sugar|] <*> force "'sugar"
-- True
-- >>> (==) <$> return [zurae|'(1 2 3)|] <*> force "'(1 2 3)"
-- True
-- >>> (==) <$> return [zurae|(print 10)|] <*> force "(print 10)"
-- 10True
zurae :: QuasiQuoter
zurae = QuasiQuoter
{ quoteExp = power toExp
, quotePat = power toPat
, quoteType = power toType
, quoteDec = error "Maru.QQ.zurae: the expansion to `[Dec]` (`quoteDec`) isn't support supported"
}
where
power :: (SExpr -> a) -> String -> Q a
power f = T.pack >>> Maru.parse >>> fmap Maru.preprocess >>> \case
Left e -> fail $ Maru.parseErrorPretty e
Right sexpr -> do
result <- runIO $ Maru.eval Maru.initialEnv sexpr
case result of
Left e -> fail $ "Maru.QQ.zurae: an error is occured in the compile time: " ++ show e
Right (sexpr, _, _) -> return $ f sexpr