commit dc6ca57a6b063481095062ff24abaae0fbf1ec34 Author: ellie Date: Sat Dec 26 11:14:10 2020 +0000 real initial commit diff --git a/src/Language/Epsilon/Eval.hs b/src/Language/Epsilon/Eval.hs new file mode 100644 index 0000000..f186455 --- /dev/null +++ b/src/Language/Epsilon/Eval.hs @@ -0,0 +1,42 @@ +module Language.Epsilon.Eval (eval) where + +import Control.Monad.Except (throwError) +import Data.Text (Text) + +import Language.Epsilon.Types +import qualified Language.Epsilon.Prims as Prims + +prims = ("eval", Prim PrimFn eval) : Prims.prims + +eval :: Value -> EvalM Value +eval (Snoc f x) = apply f x +eval (Symbol s) = maybe (throwError (Unbound s)) return $ lookup s prims +eval (Quote q) = return q +eval x = return x + +apply :: Value -> Value -> EvalM Value +apply (Snoc f x) y = do + f' <- apply f x + apply f' y +apply (Fn v f) x = do + x' <- eval x + eval $ subst v x' f +apply (Macro v f) x = eval (subst v x f) >>= eval +apply (Prim PrimFn f) x = eval x >>= f >>= eval +apply (Prim PrimMacro f) x = f x +apply Nil x = eval x +apply f x = throwError $ BadApply f x + +subst :: Ident -> Value -> Value -> Value +subst i e (Snoc a d) = Snoc (subst i e a) (subst i e d) +subst i e expr@(Fn v b) + | i == v = expr + | otherwise = Fn v $ subst i e b +subst i e expr@(Macro v b) + | i == v = expr + | otherwise = Macro v $ subst i e b +subst _ _ (Quote q) = Quote q +subst i e expr@(Symbol i') + | i == i' = e + | otherwise = expr +subst _ _ expr = expr diff --git a/src/Language/Epsilon/Parser.hs b/src/Language/Epsilon/Parser.hs new file mode 100644 index 0000000..b3922aa --- /dev/null +++ b/src/Language/Epsilon/Parser.hs @@ -0,0 +1,75 @@ +{-# LANGUAGE + BlockArguments, ApplicativeDo, OverloadedStrings, LambdaCase, + TypeApplications + #-} + +module Language.Epsilon.Parser (parse) where + +import Control.Arrow +import Control.Applicative (liftA2) +import Control.Monad +import Data.Char +import Data.Functor +import Data.Text (Text) +import Text.Parsec hiding (parse) +import qualified Text.Parsec as P +import Text.Parsec.Char +import qualified Data.Text as T + +import Language.Epsilon.Types + +type Parser = Parsec Text () + +parse :: FilePath -> Text -> Either ParseError Value +parse = P.parse (value <* eof) + +value :: Parser Value +value = parens do end <- option Nil $ try $ value <* dot + es <- many value + pure $ foldl Snoc end es + <|> Quote <$ quote <*> value + <|> do sign <- option "" $ pure <$> char '-' + ds <- many1 digit + let intPart = sign ++ ds + Float . read . (intPart ++) <$> liftA2 (:) (char '.') (many1 digit) + <|> pure (Int $ read intPart) + <* spaces + <|> Char <$ char '?' <*> strChar '\'' <* spaces + <|> Str . T.pack <$ char '"' <*> many1 (strChar '"') <* char '"' <* spaces + <|> Symbol . T.pack <$> many1 (satisfy symbolChar) <* spaces + where symbolChar c = not (isSpace c) && notElem @[] c "()\"'?" + +strChar :: Char -> Parser Char +strChar c = noneOf [c, '\\'] <|> char '\\' *> esc where + esc :: Parser Char + esc = anyChar >>= \case + c' | c' == c -> pure c + '\\' -> pure '\\' + '\n' -> spaces *> strChar c -- skip all following whitespace + 'n' -> pure '\n' + 'e' -> pure '\ESC' + 'b' -> pure '\DEL' + '0' -> pure '\NUL' + 't' -> pure '\t' + 'x' -> hexChar 2 + 'u' -> hexChar 4 + 'U' -> hexChar 8 + + hexChar :: Int -> Parser Char + hexChar n = chr . parseHex <$> replicateM n hexDigit where + parseHex :: String -> Int + parseHex = foldr (\d n -> parseHexDigit d + n*16) 0 + parseHexDigit :: Char -> Int + parseHexDigit = toLower >>> \case + '0' -> 0; '1' -> 1; '2' -> 2; '3' -> 3 + '4' -> 4; '5' -> 5; '6' -> 6; '7' -> 7 + '8' -> 8; '9' -> 9; 'a' -> 10; 'b' -> 11 + 'c' -> 12; 'd' -> 13; 'e' -> 14; 'f' -> 15 + _ -> 0 + +parens :: Parser a -> Parser a +parens p = char '(' *> spaces *> p <* char ')' <* spaces + +dot, quote :: Parser () +dot = void $ char '.' <* spaces +quote = void $ char '\'' <* spaces diff --git a/src/Language/Epsilon/Prims.hs b/src/Language/Epsilon/Prims.hs new file mode 100644 index 0000000..73df9d1 --- /dev/null +++ b/src/Language/Epsilon/Prims.hs @@ -0,0 +1,99 @@ +{-# LANGUAGE + BlockArguments, OverloadedStrings, LambdaCase, UnicodeSyntax, RankNTypes + #-} + +module Language.Epsilon.Prims (prims) where + +import Control.Monad.Except (throwError) +import Control.Monad.IO.Class (liftIO) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T + +import Language.Epsilon.Types +import Language.Epsilon.Parser + +prims :: Env +prims = + [ ("quote", prim (pure . Quote)) + , ("fn", prim $ withSym \arg -> pure $ Macro "body" $ Fn arg "body") + , ("macro", prim $ withSym \arg -> pure $ Macro "body" $ Macro arg "body") + , ("snoc", primFn \a -> pure $ primFn \d -> pure $ Quote $ Snoc a d) + , ("car", primFn $ withSnoc \a _ -> pure a) + , ("cdr", primFn $ withSnoc \_ d -> pure d) + , ("+", binop (+)) + , ("-", binop (-)) + , ("*", binop (*)) + , ("/", binopFrac (/)) + , ("%", binopInt mod) + , ("//", binopInt div) + , ("getln", primIO (Str <$> T.getLine)) + , ("getch", primIO (Char <$> getChar)) + , ("println", primFn $ withString \s -> pure $ primIO (Nil <$ T.putStrLn s)) + , ("printch", primFn $ withChar \c -> pure $ primIO (Nil <$ putChar c)) + , ("length", primFn $ withString (pure . Int . T.length)) + , ("read", primFn $ withString $ either (throwError . ReadError) (pure . Quote) . parse "") + ] + +binop :: (∀ a. Num a => a -> a -> a) -> Value +binop f = primFn \case + Int i -> pure $ primFn \case + Int j -> pure $ Int (i `f` j) + Float n -> pure $ Float (fromIntegral i `f` n) + v -> throwError $ TypeError v + Float m -> pure $ primFn \case + Int j -> pure $ Float (m `f` fromIntegral j) + Float n -> pure $ Float (m `f` n) + v -> throwError $ TypeError v + v -> throwError $ TypeError v + +binopFrac :: (∀ a. Fractional a => a -> a -> a) -> Value +binopFrac f = Fn "x" $ Fn "y" $ p `Snoc` "x" `Snoc` "y" where + p = prim \case + Int i -> pure $ prim \case + Int j -> pure $ Float (fromIntegral i `f` fromIntegral j) + Float n -> pure $ Float (fromIntegral i `f` n) + v -> throwError $ TypeError v + Float m -> pure $ prim \case + Int j -> pure $ Float (m `f` fromIntegral j) + Float n -> pure $ Float (m `f` n) + v -> throwError $ TypeError v + v -> throwError $ TypeError v + +binopInt :: (∀ a. Integral a => a -> a -> a) -> Value +binopInt f = Fn "x" $ Fn "y" $ p `Snoc` "x" `Snoc` "y" where + p = prim \case + Int i -> pure $ prim \case + Int j -> pure $ Int (i `f` j) + v -> throwError $ TypeError v + v -> throwError $ TypeError v + +prim :: (Value -> EvalM Value) -> Value +prim = Prim PrimMacro + +primFn :: (Value -> EvalM Value) -> Value +primFn = Prim PrimFn + +primIO :: IO Value -> Value +primIO io = primFn \k -> Snoc k <$> liftIO io + +withSym :: (Ident -> EvalM Value) -> Value -> EvalM Value +withSym k = \case + Symbol s -> k s + v -> throwError $ TypeError v + +withSnoc :: (Value -> Value -> EvalM Value) -> Value -> EvalM Value +withSnoc k = \case + Snoc a d -> k a d + e -> throwError $ TypeError e + +withString :: (Text -> EvalM Value) -> Value -> EvalM Value +withString k = \case + Str s -> k s + e -> throwError $ TypeError e + +withChar :: (Char -> EvalM Value) -> Value -> EvalM Value +withChar k = \case + Char c -> k c + e -> throwError $ TypeError e + diff --git a/src/Language/Epsilon/Types.hs b/src/Language/Epsilon/Types.hs new file mode 100644 index 0000000..115a39e --- /dev/null +++ b/src/Language/Epsilon/Types.hs @@ -0,0 +1,59 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving, + MultiParamTypeClasses #-} + +module Language.Epsilon.Types where + +import Text.Parsec +import Data.Text (Text, pack, unpack) +import Data.String (IsString, fromString) +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Except (MonadError) +import Control.Monad.Trans.Except (ExceptT) + +type Ident = Text + +type Env = [(Ident, Value)] + +newtype EvalM a = EvalM { runEvalM :: ExceptT Error IO a } + deriving (Functor, Applicative, Monad, MonadIO) +deriving instance MonadError Error EvalM + +data PrimType = PrimFn | PrimMacro -- should args/result be evaluated? + +data Value + = Snoc Value Value + | Nil + | Fn Ident Value + | Macro Ident Value + | Prim PrimType (Value -> EvalM Value) + | Quote Value + | Symbol Text + | Int Int + | Float Double + | Str Text + | Char Char + +instance Show Value where + show (Snoc a d) = "(" ++ show a ++ " . " ++ show d ++ ")" + show Nil = "()" + show (Fn i v) = "(fn " ++ unpack i ++ " " ++ show v ++ ")" + show (Macro i v) = "(macro " ++ unpack i ++ " " ++ show v ++ ")" + show (Prim _ _) = "" + show (Quote v) = "'" ++ show v + show (Symbol s) = unpack s + show (Int i) = show i + show (Float f) = show f + show (Str s) = show s + show (Char c) = show c + +instance IsString Value where + fromString = Symbol . pack + +infixl 5 `Snoc` + +data Error + = Unbound Ident + | BadApply Value Value + | TypeError Value + | ReadError ParseError + deriving Show diff --git a/src/Main.hs b/src/Main.hs new file mode 100644 index 0000000..0dca392 --- /dev/null +++ b/src/Main.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE LambdaCase #-} + +module Main where + +import Control.Monad.Trans.Except (runExceptT) +import Data.Text (Text) +import qualified Data.Text.IO as T +import System.Environment (getArgs) + +import Language.Epsilon.Parser (parse) +import Language.Epsilon.Types (runEvalM) +import Language.Epsilon.Eval (eval) + +main :: IO () +main = getArgs >>= \case + [] -> T.getContents >>= go "" + [x] -> T.readFile x >>= go x + _ -> putStrLn "not sure what to do with these cli args" + +go :: FilePath -> Text -> IO () +go fp s = + case parse fp s of + Left pe -> print pe + Right ast -> do + runExceptT (runEvalM $ eval ast) >>= \case + Left e -> print e + Right res -> print res + diff --git a/test.eps b/test.eps new file mode 100644 index 0000000..3b89c34 --- /dev/null +++ b/test.eps @@ -0,0 +1,2 @@ +(getln read) +