Created
February 16, 2019 07:44
-
-
Save abailly/d2822256d642ddde61ddb06c2c6390f7 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE FlexibleContexts #-} | |
{-# LANGUAGE FlexibleInstances #-} | |
{-# LANGUAGE GADTs #-} | |
{-# LANGUAGE LambdaCase #-} | |
{-# LANGUAGE MultiParamTypeClasses #-} | |
{-# LANGUAGE RankNTypes #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
{-# LANGUAGE TypeApplications #-} | |
{-| | |
## 2019-02-14 | |
Trying to write an interpreter for a small monadic language of commands | |
with variables | |
@ | |
x <- Foo "arg1" | |
Bar x | |
@ | |
Of course, this should be properly typed. | |
## | |
2019-02-15 | |
I think I need 2 passes in the parser: | |
* One pass to extract the sequence of expressions, maybe using Data.Dynamic to | |
introduce placeholders for application of variables. Or simply an abstract | |
representation of the function and its application? | |
* Another pass to construct the actual Haskell (monadic) expression from the | |
parsed terms | |
This probably means, at the end of the day, we would simply have an interpreter | |
for `Expr`essions and `Statement`s... | |
OK, so when declaring variables, we make sure the value end up in a `Map Text Dynamic`, | |
and when referenced, we resolve the value and throw an error if it cannot be resolved. | |
`fromDynamic` returns a `Maybe a` so we can safely checks the return type as long as | |
we know it explicitly. | |
## 2019-02-06 | |
Managed to get a first version working, seems to me there's a lot of boilerplate code | |
in here: | |
* Split "compiler" in 2 levels, one for `Statement`s and one for `Expr`s | |
* Used `Eff` based interpreter. Compilers run into `Eff m` monad providing suitable | |
capabilities: a `State Env` to load and store variables, an `Action` interpreter, | |
an `Error Text` to report errors in the compilation process | |
* The `Expr` compiler, `compileE` produces an `Atom` which then needs to be unpacked | |
for consumption by further expressions and statements | |
* The `Call` typeclass provides a way to resolve arguments "generically", in a way | |
that's dependent on the type of the `Action`'s constructor | |
* The `FromAtom` typeclass resolves `Atom`s into an actual value of some type. This | |
handles both litteral values and variable references | |
-} | |
module Rex.Lang where | |
import Control.Monad.Freer | |
import Control.Monad.Freer.Error | |
import Control.Monad.Freer.State | |
import Data.Dynamic | |
import qualified Data.Map as Map | |
import Text.Parsec hiding (State, string) | |
import Text.Parsec.Language (haskellDef) | |
import qualified Text.Parsec.Token as P | |
type String = [Char] | |
-- * Semantics | |
-- | Our basic vocabulary of `Action`s with a phantom type to | |
-- represent result of the action. | |
data Action a where | |
Foo :: Text -> Action Integer | |
Bar :: Integer -> Action Text | |
runAction :: Eff (Action ': effs) a -> Eff effs a | |
runAction = interpret eval | |
where | |
eval :: Action x -> Eff m x | |
eval (Foo t) = pure (fromIntegral $ length t) | |
eval (Bar i) = pure (pack $ show i) | |
type Env = Map.Map Text Dynamic | |
-- * Interpretation | |
runProg :: Prog -> IO (Either Text ((), Env)) | |
runProg = runM . runError . runState mempty . runAction . compileP | |
compileP :: forall m . (Member Action m, Member (State Env) m, Member (Error Text) m) | |
=> Prog -> Eff m () | |
compileP [] = pure () | |
compileP (Act e:rest) = compileE e >> compileP rest | |
compileP (Var t e:rest) = mkVar t e >> compileP rest | |
where | |
mkVar :: Text -> Expr -> Eff m () | |
mkVar name expr = compileE expr >>= | |
\case | |
Str v -> modify (Map.insert name (toDyn v)) | |
INT v -> modify (Map.insert name (toDyn v)) | |
a -> throwError (pack $ "expression returned a reference" <> show a) | |
compileE :: forall m . (Member Action m, Member (State Env) m, Member (Error Text) m) | |
=> Expr -> Eff m Atom | |
compileE (Ap (At (Id "Foo")) args) = mkCall Foo args | |
compileE (Ap (At (Id "Bar")) args) = mkCall Bar args | |
compileE (At a) = pure a | |
compileE e = throwError $ pack $ "cannot compile expression " <> show e | |
class Call a where | |
mkCall :: forall m . (Member (Error Text) m, Member (State Env) m, Member Action m) | |
=> a -> [ Expr ] -> Eff m Atom | |
instance (Call (Action b),FromAtom a) => Call (a -> Action b) where | |
mkCall f (At a:as) = fromAtom a >>= \ a' -> mkCall (f a') as | |
mkCall _f [] = throwError (pack "not enough arguments") | |
mkCall _f (e:_) = throwError (pack $ "cannot handle nested expression" <> show e) | |
instance Call (Action Integer) where | |
mkCall f [] = INT <$> send f | |
mkCall _f _ = throwError (pack "too many arguments") | |
instance Call (Action Text) where | |
mkCall f [] = Str <$> send f | |
mkCall _f _ = throwError (pack "too many arguments") | |
class FromAtom a where | |
fromAtom :: (Member (State Env) m, Member (Error Text) m) | |
=> Atom -> Eff m a | |
instance FromAtom Text where | |
fromAtom (Str s) = pure s | |
fromAtom (Id n) = get >>= \ env -> (maybe (throwError $ "undefined variable " <> n) pure $ Map.lookup n env >>= fromDynamic) | |
fromAtom a = throwError (pack $ "wrong atom type " <> show a <> ", expected Text") | |
instance FromAtom Integer where | |
fromAtom (INT i) = pure i | |
fromAtom (Id n) = get >>= \ env -> (maybe (throwError $ "undefined variable " <> n) pure $ Map.lookup n env >>= fromDynamic) | |
fromAtom a = throwError (pack $ "wrong atom type " <> show a <> ", expected Int") | |
-- * Syntax | |
-- |Our `Atom`ic values from which we can build more complex expressions. | |
data Atom where | |
INT :: Integer -> Atom | |
Str :: Text -> Atom | |
Id :: Text -> Atom | |
deriving (Eq, Show) | |
data Expr where | |
Ap :: Expr -> [ Expr ] -> Expr | |
At :: Atom -> Expr | |
deriving (Eq, Show) | |
data Statement where | |
Act :: Expr -> Statement | |
Var :: Text -> Expr -> Statement | |
deriving (Eq, Show) | |
type Prog = [ Statement ] | |
-- ** Parsing | |
parse :: Text -> Either ParseError Prog | |
parse = runParser parser () "" . unpack | |
parser :: Parsec String () Prog | |
parser = sepBy1 stmtParser eol | |
stmtParser :: Parsec String () Statement | |
stmtParser = try varParser <|> actParser | |
where | |
varParser = | |
Var | |
<$> variableDecl | |
<*> (leftArrow >> exprParser) | |
actParser = | |
Act <$> exprParser | |
exprParser :: Parsec String () Expr | |
exprParser = | |
Ap | |
<$> (At <$> atomParser) | |
<*> (fmap At <$> many1 atomParser) | |
atomParser = identifier <|> integer <|> string | |
eol = P.semi lexer | |
lexer = P.makeTokenParser haskellDef | |
variableDecl = pack <$> P.identifier lexer | |
leftArrow = P.reserved lexer $ "<-" | |
identifier = Id <$> variableDecl | |
integer = INT <$> P.integer lexer | |
string = Str . pack <$> P.stringLiteral lexer |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment