200 lines
5.3 KiB
Haskell
200 lines
5.3 KiB
Haskell
{-# LANGUAGE BlockArguments #-}
|
|
{-# LANGUAGE ExistentialQuantification #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# LANGUAGE TypeOperators #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
module Main where
|
|
|
|
import qualified Control.Monad.Combinators.Expr as E
|
|
import qualified Data.Eq.Type as EQ
|
|
import qualified Text.Megaparsec as M
|
|
import qualified Text.Megaparsec.Char as MC
|
|
import qualified Text.Megaparsec.Char.Lexer as ML
|
|
|
|
import Control.Applicative ((<|>))
|
|
import Data.Char (isDigit)
|
|
import Data.Eq.Type ((:=))
|
|
import Data.Foldable (foldl')
|
|
import Data.Functor (($>), void)
|
|
import Data.Proxy (Proxy(..))
|
|
import Data.Text (Text, unpack)
|
|
import Data.Text.IO (hGetContents)
|
|
import Data.Void (Void)
|
|
import Numeric (readDec)
|
|
import System.Environment (getArgs)
|
|
import System.IO (IOMode(ReadMode), openFile)
|
|
|
|
-- ========================================
|
|
-- Symantics
|
|
-- ========================================
|
|
|
|
class Symantics repr where
|
|
eInt :: Integer -> repr Integer
|
|
eBool :: Bool -> repr Bool
|
|
eAdd :: repr Integer -> repr Integer -> repr Integer
|
|
eSub :: repr Integer -> repr Integer -> repr Integer
|
|
eAnd :: repr Bool -> repr Bool -> repr Bool
|
|
eOr :: repr Bool -> repr Bool -> repr Bool
|
|
|
|
newtype SQ a = SQ {runSQ :: forall repr. Symantics repr => repr a}
|
|
|
|
instance Symantics SQ where
|
|
eInt e = SQ (eInt e)
|
|
eBool e = SQ (eBool e)
|
|
eAdd (SQ lhs) (SQ rhs) = SQ (eAdd lhs rhs)
|
|
eSub (SQ lhs) (SQ rhs) = SQ (eSub lhs rhs)
|
|
eAnd (SQ lhs) (SQ rhs) = SQ (eAnd lhs rhs)
|
|
eOr (SQ lhs) (SQ rhs) = SQ (eOr lhs rhs)
|
|
|
|
newtype Expr a = Expr {runExpr :: a} deriving Show
|
|
|
|
instance Symantics Expr where
|
|
eInt = Expr
|
|
eBool = Expr
|
|
eAdd (Expr lhs) (Expr rhs) = Expr (lhs + rhs)
|
|
eSub (Expr lhs) (Expr rhs) = Expr (lhs - rhs)
|
|
eAnd (Expr lhs) (Expr rhs) = Expr (lhs && rhs)
|
|
eOr (Expr lhs) (Expr rhs) = Expr (lhs || rhs)
|
|
|
|
-- ========================================
|
|
-- Typeable
|
|
-- ========================================
|
|
|
|
class Typeable repr where
|
|
pInt :: repr Integer
|
|
pBool :: repr Bool
|
|
|
|
newtype TQ t = TQ {runTQ :: forall repr. Typeable repr => repr t}
|
|
|
|
instance Typeable TQ where
|
|
pInt = TQ pInt
|
|
pBool = TQ pBool
|
|
|
|
newtype AsInt a = AsInt (Maybe (a := Integer))
|
|
|
|
instance Typeable AsInt where
|
|
pInt = AsInt (Just EQ.refl)
|
|
pBool = AsInt Nothing
|
|
|
|
newtype AsBool a = AsBool (Maybe (a := Bool))
|
|
|
|
instance Typeable AsBool where
|
|
pInt = AsBool Nothing
|
|
pBool = AsBool (Just EQ.refl)
|
|
|
|
-- ========================================
|
|
-- Dynamic
|
|
-- ========================================
|
|
|
|
data Dynamic repr = forall t. Dynamic (TQ t) (repr t)
|
|
|
|
class IsDynamic a where
|
|
type' :: forall repr. Typeable repr => repr a
|
|
lift' :: forall repr. Symantics repr => a -> repr a
|
|
cast' :: forall repr t. TQ t -> Maybe (t := a)
|
|
|
|
instance IsDynamic Integer where
|
|
type' = pInt
|
|
lift' = eInt
|
|
cast' (TQ t) = case t of AsInt a -> a
|
|
|
|
instance IsDynamic Bool where
|
|
type' = pBool
|
|
lift' = eBool
|
|
cast' (TQ t) = case t of AsBool a -> a
|
|
|
|
toDyn :: forall repr a. IsDynamic a => Symantics repr => a -> Dynamic repr
|
|
toDyn = Dynamic type' . lift'
|
|
|
|
fromDyn :: forall repr a. IsDynamic a => Dynamic repr -> Maybe (repr a)
|
|
fromDyn (Dynamic t e) = case t of
|
|
(cast' -> r) -> do
|
|
r' <- r
|
|
pure $ EQ.coerce (EQ.lift r') e
|
|
|
|
-- ========================================
|
|
-- Parser code
|
|
-- ========================================
|
|
|
|
type Parser = M.Parsec Void Text
|
|
|
|
space :: Parser ()
|
|
space = ML.space MC.space1 M.empty M.empty
|
|
{-# INLINE space #-}
|
|
|
|
lexeme_ :: forall a. Parser a -> Parser a
|
|
lexeme_ = ML.lexeme $ MC.space1 <|> M.eof
|
|
{-# INLINE lexeme_ #-}
|
|
|
|
symbol :: Text -> Parser Text
|
|
symbol = ML.symbol space
|
|
{-# INLINE symbol #-}
|
|
|
|
parens :: forall a. Parser a -> Parser a
|
|
parens = M.between (symbol "(") (symbol ")")
|
|
{-# INLINE parens #-}
|
|
|
|
boolean :: Parser Bool
|
|
boolean = lexeme_ do
|
|
MC.string "true" $> True <|> MC.string "false" $> False
|
|
{-# INLINE boolean #-}
|
|
|
|
integer :: Parser Integer
|
|
integer = lexeme_ do
|
|
i <- M.some $ M.satisfy isDigit
|
|
case readDec i of
|
|
[(value, "")] -> pure value
|
|
_ -> fail "integer"
|
|
{-# INLINE integer #-}
|
|
|
|
mkBinary
|
|
:: forall repr a
|
|
. Symantics repr
|
|
=> IsDynamic a
|
|
=> (repr a -> repr a -> repr a)
|
|
-> Dynamic repr
|
|
-> Dynamic repr
|
|
-> Maybe (Dynamic repr)
|
|
mkBinary bin lhs rhs = do
|
|
lhs' <- fromDyn lhs
|
|
rhs' <- fromDyn rhs
|
|
pure . Dynamic type' $ bin lhs' rhs'
|
|
|
|
expr :: forall repr. Symantics repr => Parser (Dynamic repr)
|
|
expr = expr' >>= \case
|
|
Left (offset, msg) -> M.setOffset offset >> fail msg
|
|
Right a -> pure a
|
|
where
|
|
expr' = E.makeExprParser
|
|
(parens expr' <|> Right . toDyn <$> integer <|> Right . toDyn <$> boolean)
|
|
[ [binary' "+" eAdd, binary' "-" eSub]
|
|
, [binary' "&&" eAnd, binary' "||" eOr]
|
|
]
|
|
|
|
binary' name bin = E.InfixL do
|
|
void $ symbol name
|
|
offset <- M.getOffset
|
|
pure $ \lhs rhs -> do
|
|
lhs' <- lhs
|
|
rhs' <- rhs
|
|
case mkBinary bin lhs' rhs' of
|
|
Nothing -> Left (offset, "Invalid operands for `" <> unpack name <> "`")
|
|
Just a -> pure a
|
|
|
|
-- ========================================
|
|
-- Main
|
|
-- ========================================
|
|
|
|
main :: IO ()
|
|
main = do
|
|
[fileName] <- getArgs
|
|
handle <- openFile fileName ReadMode
|
|
contents <- hGetContents handle
|
|
case M.parse expr fileName contents of
|
|
Left e -> print $ M.errorBundlePretty e
|
|
Right a -> print (fromDyn a :: Maybe (Expr Integer))
|