Project name change: vergilius -> virgil
Getty Ritter
8 years ago
1 | {-# LANGUAGE OverloadedStrings #-} | |
2 | ||
3 | module Language.Vergilius.AST where | |
4 | ||
5 | import Data.ByteString.Lazy (ByteString) | |
6 | import Data.HashMap.Strict (HashMap, fromList) | |
7 | import qualified Data.HashMap.Strict as HM | |
8 | import Data.Scientific (Scientific) | |
9 | import Data.Text (Text) | |
10 | import Data.Vector (Vector) | |
11 | import qualified Data.Vector as V | |
12 | ||
13 | type Ident = ByteString | |
14 | ||
15 | -- | A top-level declaration. | |
16 | data Decl | |
17 | = Fragment ChValue | |
18 | | DFun FunDec | |
19 | | DTyp TypDec | |
20 | deriving (Eq, Show) | |
21 | ||
22 | type ChObject = HashMap Text ChValue | |
23 | type ChArray = Vector ChValue | |
24 | ||
25 | mkArray :: [ChValue] -> ChValue | |
26 | mkArray = ChArray . V.fromList | |
27 | ||
28 | mkObject :: [(Text, ChValue)] -> ChValue | |
29 | mkObject = ChObject . HM.fromList | |
30 | ||
31 | data ChValue | |
32 | = ChObject ChObject | |
33 | | ChArray ChArray | |
34 | | ChString Text | |
35 | | ChNumber Scientific | |
36 | | ChBool Bool | |
37 | | ChNull | |
38 | | ChQuote Expr | |
39 | deriving (Eq, Show) | |
40 | ||
41 | data Typ | |
42 | = TApp Typ Typ | |
43 | | TNamed Ident | |
44 | | TArrow | |
45 | | TUnit | |
46 | | TVar Ident | |
47 | | TFix Ident Typ | |
48 | | TSum [(Ident, Typ)] | |
49 | | TProd [(Ident, Typ)] | |
50 | | TTup [Typ] | |
51 | deriving (Eq, Show) | |
52 | ||
53 | data TypDec = TypDec | |
54 | { tdName :: Ident | |
55 | , tdVars :: [Ident] | |
56 | , tdBody :: Typ | |
57 | } deriving (Eq, Show) | |
58 | ||
59 | data FunDec = FunDec | |
60 | { fdName :: Ident | |
61 | , fdTyp :: Maybe Typ | |
62 | , fdCases :: [FnCase] | |
63 | } deriving (Eq, Show) | |
64 | ||
65 | data FnCase = FnCase | |
66 | { fcPat :: Pattern | |
67 | , fcBody :: Expr | |
68 | } deriving (Eq, Show) | |
69 | ||
70 | data Pattern | |
71 | = PConstr Ident Pattern | |
72 | | PVar Ident | |
73 | | PLit Literal | |
74 | | PTup [Pattern] | |
75 | | PUnderscore | |
76 | deriving (Eq, Show) | |
77 | ||
78 | type Env = HashMap Ident Value | |
79 | ||
80 | data Value | |
81 | = VConstr Ident Value | |
82 | | VRecord [(Ident, Value)] | |
83 | | VTup [Value] | |
84 | | VLam [FnCase] Env | |
85 | | VLit Literal | |
86 | deriving (Eq, Show) | |
87 | ||
88 | data Expr | |
89 | = ELit Literal | |
90 | | EConstr Ident | |
91 | | EApp Expr Expr | |
92 | | EVar Ident | |
93 | | ETup [Expr] | |
94 | | EList [Expr] | |
95 | | ERecord [(Ident, Expr)] | |
96 | | ELam [FnCase] | |
97 | | ETyped Expr Typ | |
98 | | EAccess Expr Ident | |
99 | deriving (Eq, Show) | |
100 | ||
101 | data Literal | |
102 | = LInt Integer | |
103 | | LDouble Double | |
104 | | LString Text | |
105 | | LBool Bool | |
106 | deriving (Eq, Show) | |
107 | ||
108 | {- | |
109 | with fact : Int -> Int | |
110 | 0 = 1 | |
111 | n = n * fact (n - 1) | |
112 | -} | |
113 | fact :: Decl | |
114 | fact = DFun $ FunDec | |
115 | { fdName = "fact" | |
116 | , fdTyp = Just (TApp (TApp TArrow (TNamed "Int")) | |
117 | (TNamed "Int")) | |
118 | , fdCases = [ FnCase { fcPat = PLit (LInt 0) | |
119 | , fcBody = ELit (LInt 1) | |
120 | } | |
121 | , FnCase { fcPat = PVar "n" | |
122 | , fcBody = EApp (EApp (EVar "mul") (EVar "n")) | |
123 | (EApp (EVar "fact") | |
124 | (EApp (EApp | |
125 | (EVar "sub") | |
126 | (EVar "n")) | |
127 | (ELit (LInt 1)))) | |
128 | } | |
129 | ] | |
130 | } | |
131 | ||
132 | {- | |
133 | foo: | |
134 | bar: '(fact 5) | |
135 | baz: '(fact 10) | |
136 | -} | |
137 | foo :: Decl | |
138 | foo = Fragment $ ChObject $ fromList | |
139 | [ ("foo", ChObject $ fromList | |
140 | [ ("bar", ChQuote (EApp (EVar "fact") (ELit (LInt 5)))) | |
141 | , ("baz", ChQuote (EApp (EVar "fact") (ELit (LInt 10)))) | |
142 | ] | |
143 | ) | |
144 | ] |
1 | {-# LANGUAGE ParallelListComp #-} | |
2 | {-# LANGUAGE OverloadedStrings #-} | |
3 | ||
4 | module Language.Vergilius.Eval where | |
5 | ||
6 | import qualified Data.Aeson as Ae | |
7 | import Data.HashMap.Strict (HashMap) | |
8 | import qualified Data.HashMap.Strict as HM | |
9 | ||
10 | import Language.Vergilius.AST | |
11 | ||
12 | evalCh :: ChValue -> Either String Ae.Value | |
13 | evalCh (ChObject obj) = Ae.Object `fmap` mapM evalCh obj | |
14 | evalCh (ChArray arr) = Ae.Array `fmap` mapM evalCh arr | |
15 | evalCh (ChString t) = pure (Ae.String t) | |
16 | evalCh (ChNumber n) = pure (Ae.Number n) | |
17 | evalCh (ChBool b) = pure (Ae.Bool b) | |
18 | evalCh ChNull = pure Ae.Null | |
19 | evalCh (ChQuote _) = Left "no evaluation yet" | |
20 | ||
21 | -- | Match a pattern against a value | |
22 | match :: Pattern -> Value -> Maybe [(Ident, Value)] | |
23 | match (PConstr i p) (VConstr i' v) | |
24 | | i == i' = match p v | |
25 | match (PTup ps) (VTup vs) | |
26 | | length ps == length vs = | |
27 | fmap concat (sequence [ match p v | p <- ps | v <- vs ]) | |
28 | match (PVar i) x = return [(i, x)] | |
29 | match (PLit l) (VLit v) | |
30 | | l == v = return [] | |
31 | match PUnderscore _ = return [] | |
32 | match _ _ = Nothing | |
33 | ||
34 | eval :: Env -> Expr -> Either String Value | |
35 | eval _ (ELit l) = return (VLit l) | |
36 | eval _ con@(EConstr _) = do | |
37 | let fnCase = FnCase { fcPat = PVar "x" | |
38 | , fcBody = EApp con (EVar "x") | |
39 | } | |
40 | return (VLam [fnCase] HM.empty) | |
41 | eval env (EApp (EConstr i) e) = do | |
42 | e' <- eval env e | |
43 | return (VConstr i e') | |
44 | eval env (EApp f x) = do | |
45 | VLam cs env' <- eval env f | |
46 | arg <- eval env x | |
47 | app env cs arg | |
48 | eval env (EVar v) = case HM.lookup v env of | |
49 | Just x -> Right x | |
50 | Nothing -> Left ("Unbound variable: " ++ show v) | |
51 | eval env (ETup es) = do | |
52 | vs <- mapM (eval env) es | |
53 | return (VTup vs) | |
54 | eval env (EList es) = do | |
55 | vs <- mapM (eval env) es | |
56 | return (foldr (\ x xs -> VConstr "Cons" (VTup [x, xs])) | |
57 | (VConstr "Nil" (VTup [])) | |
58 | vs) | |
59 | eval env (ERecord rs) = do | |
60 | vs <- mapM (eval env . snd) rs | |
61 | return (VRecord [ (i, v) | i <- map fst rs | v <- vs ]) | |
62 | eval env (ELam cs) = return (VLam cs env) | |
63 | eval env (ETyped e _) = eval env e | |
64 | eval env (EAccess e i) = do | |
65 | VRecord r <- eval env e | |
66 | case lookup i r of | |
67 | Just v -> return v | |
68 | Nothing -> Left ("Bad lookup: field " ++ show i ++ | |
69 | " in " ++ show r) | |
70 | ||
71 | extend :: Env -> [(Ident, Value)] -> Env | |
72 | extend = foldr (uncurry HM.insert) | |
73 | ||
74 | app :: Env -> [FnCase] -> Value -> Either String Value | |
75 | app _ [] _ = Left "Non-exhaustive cases" | |
76 | app env (c:cs) arg = case match (fcPat c) arg of | |
77 | Just bindings -> eval (extend env bindings) (fcBody c) | |
78 | Nothing -> app env cs arg |
1 | { | |
2 | {-# OPTIONS -w #-} | |
3 | {-# LANGUAGE OverloadedStrings #-} | |
4 | ||
5 | module Language.Vergilius.Lexer where | |
6 | ||
7 | import Control.Monad (liftM) | |
8 | import Data.Text (Text) | |
9 | import qualified Data.Text as T | |
10 | ||
11 | import Prelude hiding (lex) | |
12 | } | |
13 | ||
14 | %wrapper "monadUserState" | |
15 | ||
16 | $digit = 0-9 | |
17 | $alpha = [A-Za-z] | |
18 | ||
19 | tokens :- | |
20 | $white+ ; | |
21 | "#".* ; | |
22 | ||
23 | $digit+ { lex (TkInt . read . T.unpack) } | |
24 | \"[^\"]*\" { lex strLiteral } -- this is for syntax highlighting -> " | |
25 | ||
26 | true { lex' (TkKw KwTrue) } | |
27 | false { lex' (TkKw KwFalse) } | |
28 | null { lex' (TkKw KwNull) } | |
29 | ||
30 | \{ { lex' TkLCurl } | |
31 | \} { lex' TkRCurl } | |
32 | \[ { lex' TkLBrac } | |
33 | \] { lex' TkRBrac } | |
34 | ||
35 | { | |
36 | data Token = Token AlexPosn TkType deriving (Eq, Show) | |
37 | ||
38 | data KwType | |
39 | = KwTrue | |
40 | | KwFalse | |
41 | | KwNull | |
42 | deriving (Eq, Show) | |
43 | ||
44 | data TkType | |
45 | = TkInt Integer | |
46 | | TkStr Text | |
47 | | TkKw KwType | |
48 | | TkLCurl | |
49 | | TkRCurl | |
50 | | TkLBrac | |
51 | | TkRBrac | |
52 | | TkEOF | |
53 | deriving (Eq, Show) | |
54 | ||
55 | strLiteral :: Text -> TkType | |
56 | strLiteral = TkStr . T.drop 1 . T.dropEnd 1 | |
57 | ||
58 | data AlexUserState = AlexUserState | |
59 | { filePath :: FilePath | |
60 | } deriving (Eq, Show) | |
61 | ||
62 | alexInitUserState :: AlexUserState | |
63 | alexInitUserState = AlexUserState "<unknown>" | |
64 | ||
65 | getFilePath :: Alex FilePath | |
66 | getFilePath = liftM filePath alexGetUserState | |
67 | ||
68 | setFilePath :: FilePath -> Alex () | |
69 | setFilePath = alexSetUserState . AlexUserState | |
70 | ||
71 | alexMonadScan' :: Alex Token | |
72 | alexMonadScan' = do | |
73 | inp <- alexGetInput | |
74 | sc <- alexGetStartCode | |
75 | case alexScan inp sc of | |
76 | AlexEOF -> alexEOF | |
77 | AlexError (p, _, _, s) -> | |
78 | alexError' p ("lexical error at character '" ++ take 1 s ++ "'") | |
79 | AlexSkip inp' len -> do | |
80 | alexSetInput inp' | |
81 | alexMonadScan' | |
82 | AlexToken inp' len action -> do | |
83 | alexSetInput inp' | |
84 | action (ignorePendingBytes inp) len | |
85 | ||
86 | alexError' :: AlexPosn -> String -> Alex a | |
87 | alexError' (AlexPn _ l c) msg = do | |
88 | fp <- getFilePath | |
89 | alexError (fp ++ ":" ++ show l ++ ":" ++ show c ++ ": " ++ msg) | |
90 | ||
91 | alexEOF :: Alex Token | |
92 | alexEOF = do | |
93 | (p,_,_,_) <- alexGetInput | |
94 | return (Token p TkEOF) | |
95 | ||
96 | ||
97 | lex :: (Text -> TkType) -> AlexAction Token | |
98 | lex f = \(p,_,_,s) i -> return (Token p (f (T.pack (take i s)))) | |
99 | ||
100 | lex' :: TkType -> AlexAction Token | |
101 | lex' = lex . const | |
102 | ||
103 | runAlex' :: Alex a -> FilePath -> String -> Either String a | |
104 | runAlex' mote fp input = runAlex input (setFilePath fp >> mote) | |
105 | } |
1 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} | |
2 | ||
3 | module Language.Vergilius.Parse where | |
4 | ||
5 | import Data.Char (isAlpha, isDigit) | |
6 | ||
7 | data ParseState = ParseState | |
8 | { indentLevel :: Int | |
9 | } deriving (Eq, Show) | |
10 | ||
11 | type Parser a = ParsecT Text ParseState Identity a | |
12 | ||
13 | pInt :: Parser Literal | |
14 | pInt = (LInt . read) <$> takeWhile1 digit | |
15 | ||
16 | pLiteral :: Parser Expr | |
17 | pLiteral = ELit <$> pInt | |
18 | ||
19 | pIChar :: Parser [Char] | |
20 | pIChar = satisfy (getAny (mconcat (map (Any .) tests))) | |
21 | where tests = [ (== '_') | |
22 | , isAlpha | |
23 | , isDigit | |
24 | ] | |
25 | ||
26 | pConstr :: Parser Text | |
27 | pConstr = do | |
28 | c <- upper | |
29 | cs <- many pIChar | |
30 | return (T.cons c (T.pack cs)) | |
31 | ||
32 | pIdent :: Parser Text | |
33 | pIdent = do | |
34 | c <- lower | |
35 | cs <- many pIChar | |
36 | return (T.cons c (T.pack cs)) |
1 | { | |
2 | {-# LANGUAGE OverloadedStrings #-} | |
3 | ||
4 | module Language.Vergilius.Parser where | |
5 | ||
6 | import Language.Vergilius.AST | |
7 | import Language.Vergilius.Lexer | |
8 | ||
9 | } | |
10 | ||
11 | %name parse | |
12 | %tokentype { Token } | |
13 | %monad { Alex } | |
14 | %lexer { lexwrap } { Token _ TkEOF } | |
15 | %error { happyError } | |
16 | ||
17 | %token | |
18 | '{' { Token _ TkLCurl } | |
19 | '}' { Token _ TkRCurl } | |
20 | '[' { Token _ TkLBrac } | |
21 | ']' { Token _ TkRBrac } | |
22 | ||
23 | true { Token _ (TkKw KwTrue) } | |
24 | false { Token _ (TkKw KwFalse) } | |
25 | null { Token _ (TkKw KwNull) } | |
26 | ||
27 | int { Token _ (TkInt $$) } | |
28 | str { Token _ (TkStr $$) } | |
29 | ||
30 | %% | |
31 | ||
32 | tlexpr | |
33 | : '[' list { mkArray $2 } | |
34 | | '{' dict { mkObject $2 } | |
35 | ||
36 | expr | |
37 | : tlexpr { $1 } | |
38 | | int { ChNumber (fromIntegral $1) } | |
39 | | str { ChString $1 } | |
40 | | true { ChBool True } | |
41 | | false { ChBool False} | |
42 | | null { ChNull } | |
43 | ||
44 | list | |
45 | : ']' { [] } | |
46 | | expr list { $1 : $2 } | |
47 | ||
48 | dict | |
49 | : '}' { [] } | |
50 | | str expr dict { ($1, $2) : $3 } | |
51 | ||
52 | { | |
53 | ||
54 | -- info :: a -> Alex (Info a) | |
55 | -- info x = do | |
56 | -- (AlexPn _ ln cl,_,_,_) <- alexGetInput | |
57 | -- fp <- getFilePath | |
58 | -- return $ Info { node = x | |
59 | -- , srcLine = ln | |
60 | -- , srcCol = cl | |
61 | -- , srcFile = fp | |
62 | -- } | |
63 | ||
64 | lexwrap :: (Token -> Alex a) -> Alex a | |
65 | lexwrap = (alexMonadScan' >>=) | |
66 | ||
67 | happyError :: Token -> Alex a | |
68 | happyError (Token p t) = | |
69 | alexError' p ("parse error at token " ++ show t) | |
70 | ||
71 | parseFile :: FilePath -> String -> Either String ChValue | |
72 | parseFile = runAlex' parse | |
73 | } |
1 | module Language.Vergilius.Pretty where | |
2 | ||
3 | import Data.Monoid ((<>)) | |
4 | import Text.PrettyPrint.HughesPJ.Class | |
5 | import Language.Vergilius.AST | |
6 | ||
7 | instance Pretty ChValue where | |
8 | pPrint (ChObject obj) = braces $ text "..." | |
9 | pPrint |
1 | module Language.Vergilius.Types where | |
2 | ||
3 | import Language.Cherenkov.AST | |
4 | ||
5 | type TEnv = HashMap Ident Typ | |
6 | ||
7 | inferType :: TEnv -> Expr -> () | |
8 | inferType _ (ELit (LInt _)) = TNamed "Int" | |
9 | inferType _ (ELit (LDouble _)) = TNamed "Double" | |
10 | inferType _ (ELit (LString _)) = TNamed "String" | |
11 | inferType _ (ELit (LBool _)) = TNamed "Bool" | |
12 | inferType e (ETup ts) = VTup (map (inferType e ts)) | |
13 | inferType e (EList ls) = undefined | |
14 | inferType e (EVar v) = undefined |
1 | module Language.Vergilius | |
2 | ( -- * Vergil Parsers | |
3 | decode | |
4 | , eitherDecode | |
5 | , decodeStrict | |
6 | , eitherDecodeStrict | |
7 | -- * Re-Exported Aeson Types | |
8 | -- ** Core JSON Types | |
9 | , Aeson.Value(..) | |
10 | , Aeson.Array | |
11 | , Aeson.Object | |
12 | -- ** Type Conversion | |
13 | , Aeson.FromJSON(..) | |
14 | , Aeson.ToJSON(..) | |
15 | , Aeson.Result(..) | |
16 | , Aeson.fromJSON | |
17 | -- ** Inspecting Values | |
18 | , Aeson.withObject | |
19 | , Aeson.withText | |
20 | , Aeson.withArray | |
21 | , Aeson.withScientific | |
22 | , Aeson.withBool | |
23 | -- ** Accessors | |
24 | , (Aeson..:) | |
25 | , (Aeson..:?) | |
26 | , (Aeson..:!) | |
27 | , (Aeson..!=) | |
28 | ) where | |
29 | ||
30 | import Data.Aeson (FromJSON, Result(..), fromJSON) | |
31 | import qualified Data.Aeson as Aeson | |
32 | import qualified Data.ByteString as BSE | |
33 | import Data.ByteString.Lazy (ByteString) | |
34 | import qualified Data.ByteString.Lazy.Char8 as BS | |
35 | import Language.Vergilius.AST (ChValue) | |
36 | import Language.Vergilius.Eval | |
37 | import Language.Vergilius.Lexer | |
38 | import Language.Vergilius.Parser | |
39 | ||
40 | parseVirgil :: ByteString -> Either String ChValue | |
41 | parseVirgil = runAlex' parse "<input>" . BS.unpack | |
42 | ||
43 | -- | Deserialize and execute a Virgil program. If parsing fails, | |
44 | -- or if type-checking fails, or if the execution of the Virgil | |
45 | -- program otherwise fails at runtime, then this function will | |
46 | -- return 'Nothing'. | |
47 | -- | |
48 | -- Note that Virgil rules out recursive programs, so executing a | |
49 | -- Virgil program won't loop forever---but it may still take | |
50 | -- a fair amount longer than parsing the equivalent JSON! | |
51 | decode :: FromJSON a => ByteString -> Maybe a | |
52 | decode bs = case eitherDecode bs of | |
53 | Error err -> Nothing | |
54 | Success val -> pure val | |
55 | ||
56 | -- | Deserialize and execute a Virgil program. If parsing fails, | |
57 | -- or if type-checking fails, or if the execution of the Virgil | |
58 | -- program otherwise fails at runtime, then this function will | |
59 | -- return an approrpriately informative error message. | |
60 | eitherDecode :: FromJSON a => ByteString -> Result a | |
61 | eitherDecode bs = case parseVirgil bs of | |
62 | Left err -> Error err | |
63 | Right ch -> case evalCh ch of | |
64 | Left err -> Error err | |
65 | Right v -> fromJSON v | |
66 | ||
67 | decodeStrict :: FromJSON a => BSE.ByteString -> Maybe a | |
68 | decodeStrict = decode . BS.fromStrict | |
69 | ||
70 | eitherDecodeStrict :: FromJSON a => BSE.ByteString -> Result a | |
71 | eitherDecodeStrict = eitherDecode . BS.fromStrict |
1 | {-# LANGUAGE OverloadedStrings #-} | |
2 | ||
3 | module Language.Virgil.AST where | |
4 | ||
5 | import Data.ByteString.Lazy (ByteString) | |
6 | import Data.HashMap.Strict (HashMap, fromList) | |
7 | import qualified Data.HashMap.Strict as HM | |
8 | import Data.Scientific (Scientific) | |
9 | import Data.Text (Text) | |
10 | import Data.Vector (Vector) | |
11 | import qualified Data.Vector as V | |
12 | ||
13 | type Ident = ByteString | |
14 | ||
15 | -- | A top-level declaration. | |
16 | data Decl | |
17 | = Fragment ChValue | |
18 | | DFun FunDec | |
19 | | DTyp TypDec | |
20 | deriving (Eq, Show) | |
21 | ||
22 | type ChObject = HashMap Text ChValue | |
23 | type ChArray = Vector ChValue | |
24 | ||
25 | mkArray :: [ChValue] -> ChValue | |
26 | mkArray = ChArray . V.fromList | |
27 | ||
28 | mkObject :: [(Text, ChValue)] -> ChValue | |
29 | mkObject = ChObject . HM.fromList | |
30 | ||
31 | data ChValue | |
32 | = ChObject ChObject | |
33 | | ChArray ChArray | |
34 | | ChString Text | |
35 | | ChNumber Scientific | |
36 | | ChBool Bool | |
37 | | ChNull | |
38 | | ChQuote Expr | |
39 | deriving (Eq, Show) | |
40 | ||
41 | data Typ | |
42 | = TApp Typ Typ | |
43 | | TNamed Ident | |
44 | | TArrow | |
45 | | TUnit | |
46 | | TVar Ident | |
47 | | TFix Ident Typ | |
48 | | TSum [(Ident, Typ)] | |
49 | | TProd [(Ident, Typ)] | |
50 | | TTup [Typ] | |
51 | deriving (Eq, Show) | |
52 | ||
53 | data TypDec = TypDec | |
54 | { tdName :: Ident | |
55 | , tdVars :: [Ident] | |
56 | , tdBody :: Typ | |
57 | } deriving (Eq, Show) | |
58 | ||
59 | data FunDec = FunDec | |
60 | { fdName :: Ident | |
61 | , fdTyp :: Maybe Typ | |
62 | , fdCases :: [FnCase] | |
63 | } deriving (Eq, Show) | |
64 | ||
65 | data FnCase = FnCase | |
66 | { fcPat :: Pattern | |
67 | , fcBody :: Expr | |
68 | } deriving (Eq, Show) | |
69 | ||
70 | data Pattern | |
71 | = PConstr Ident Pattern | |
72 | | PVar Ident | |
73 | | PLit Literal | |
74 | | PTup [Pattern] | |
75 | | PUnderscore | |
76 | deriving (Eq, Show) | |
77 | ||
78 | type Env = HashMap Ident Value | |
79 | ||
80 | data Value | |
81 | = VConstr Ident Value | |
82 | | VRecord [(Ident, Value)] | |
83 | | VTup [Value] | |
84 | | VLam [FnCase] Env | |
85 | | VLit Literal | |
86 | deriving (Eq, Show) | |
87 | ||
88 | data Expr | |
89 | = ELit Literal | |
90 | | EConstr Ident | |
91 | | EApp Expr Expr | |
92 | | EVar Ident | |
93 | | ETup [Expr] | |
94 | | EList [Expr] | |
95 | | ERecord [(Ident, Expr)] | |
96 | | ELam [FnCase] | |
97 | | ETyped Expr Typ | |
98 | | EAccess Expr Ident | |
99 | deriving (Eq, Show) | |
100 | ||
101 | data Literal | |
102 | = LInt Integer | |
103 | | LDouble Double | |
104 | | LString Text | |
105 | | LBool Bool | |
106 | deriving (Eq, Show) | |
107 | ||
108 | {- | |
109 | with fact : Int -> Int | |
110 | 0 = 1 | |
111 | n = n * fact (n - 1) | |
112 | -} | |
113 | fact :: Decl | |
114 | fact = DFun $ FunDec | |
115 | { fdName = "fact" | |
116 | , fdTyp = Just (TApp (TApp TArrow (TNamed "Int")) | |
117 | (TNamed "Int")) | |
118 | , fdCases = [ FnCase { fcPat = PLit (LInt 0) | |
119 | , fcBody = ELit (LInt 1) | |
120 | } | |
121 | , FnCase { fcPat = PVar "n" | |
122 | , fcBody = EApp (EApp (EVar "mul") (EVar "n")) | |
123 | (EApp (EVar "fact") | |
124 | (EApp (EApp | |
125 | (EVar "sub") | |
126 | (EVar "n")) | |
127 | (ELit (LInt 1)))) | |
128 | } | |
129 | ] | |
130 | } | |
131 | ||
132 | {- | |
133 | foo: | |
134 | bar: '(fact 5) | |
135 | baz: '(fact 10) | |
136 | -} | |
137 | foo :: Decl | |
138 | foo = Fragment $ ChObject $ fromList | |
139 | [ ("foo", ChObject $ fromList | |
140 | [ ("bar", ChQuote (EApp (EVar "fact") (ELit (LInt 5)))) | |
141 | , ("baz", ChQuote (EApp (EVar "fact") (ELit (LInt 10)))) | |
142 | ] | |
143 | ) | |
144 | ] |
1 | {-# LANGUAGE ParallelListComp #-} | |
2 | {-# LANGUAGE OverloadedStrings #-} | |
3 | ||
4 | module Language.Virgil.Eval where | |
5 | ||
6 | import qualified Data.Aeson as Ae | |
7 | import Data.HashMap.Strict (HashMap) | |
8 | import qualified Data.HashMap.Strict as HM | |
9 | ||
10 | import Language.Virgil.AST | |
11 | ||
12 | evalCh :: ChValue -> Either String Ae.Value | |
13 | evalCh (ChObject obj) = Ae.Object `fmap` mapM evalCh obj | |
14 | evalCh (ChArray arr) = Ae.Array `fmap` mapM evalCh arr | |
15 | evalCh (ChString t) = pure (Ae.String t) | |
16 | evalCh (ChNumber n) = pure (Ae.Number n) | |
17 | evalCh (ChBool b) = pure (Ae.Bool b) | |
18 | evalCh ChNull = pure Ae.Null | |
19 | evalCh (ChQuote _) = Left "no evaluation yet" | |
20 | ||
21 | -- | Match a pattern against a value | |
22 | match :: Pattern -> Value -> Maybe [(Ident, Value)] | |
23 | match (PConstr i p) (VConstr i' v) | |
24 | | i == i' = match p v | |
25 | match (PTup ps) (VTup vs) | |
26 | | length ps == length vs = | |
27 | fmap concat (sequence [ match p v | p <- ps | v <- vs ]) | |
28 | match (PVar i) x = return [(i, x)] | |
29 | match (PLit l) (VLit v) | |
30 | | l == v = return [] | |
31 | match PUnderscore _ = return [] | |
32 | match _ _ = Nothing | |
33 | ||
34 | eval :: Env -> Expr -> Either String Value | |
35 | eval _ (ELit l) = return (VLit l) | |
36 | eval _ con@(EConstr _) = do | |
37 | let fnCase = FnCase { fcPat = PVar "x" | |
38 | , fcBody = EApp con (EVar "x") | |
39 | } | |
40 | return (VLam [fnCase] HM.empty) | |
41 | eval env (EApp (EConstr i) e) = do | |
42 | e' <- eval env e | |
43 | return (VConstr i e') | |
44 | eval env (EApp f x) = do | |
45 | VLam cs env' <- eval env f | |
46 | arg <- eval env x | |
47 | app env cs arg | |
48 | eval env (EVar v) = case HM.lookup v env of | |
49 | Just x -> Right x | |
50 | Nothing -> Left ("Unbound variable: " ++ show v) | |
51 | eval env (ETup es) = do | |
52 | vs <- mapM (eval env) es | |
53 | return (VTup vs) | |
54 | eval env (EList es) = do | |
55 | vs <- mapM (eval env) es | |
56 | return (foldr (\ x xs -> VConstr "Cons" (VTup [x, xs])) | |
57 | (VConstr "Nil" (VTup [])) | |
58 | vs) | |
59 | eval env (ERecord rs) = do | |
60 | vs <- mapM (eval env . snd) rs | |
61 | return (VRecord [ (i, v) | i <- map fst rs | v <- vs ]) | |
62 | eval env (ELam cs) = return (VLam cs env) | |
63 | eval env (ETyped e _) = eval env e | |
64 | eval env (EAccess e i) = do | |
65 | VRecord r <- eval env e | |
66 | case lookup i r of | |
67 | Just v -> return v | |
68 | Nothing -> Left ("Bad lookup: field " ++ show i ++ | |
69 | " in " ++ show r) | |
70 | ||
71 | extend :: Env -> [(Ident, Value)] -> Env | |
72 | extend = foldr (uncurry HM.insert) | |
73 | ||
74 | app :: Env -> [FnCase] -> Value -> Either String Value | |
75 | app _ [] _ = Left "Non-exhaustive cases" | |
76 | app env (c:cs) arg = case match (fcPat c) arg of | |
77 | Just bindings -> eval (extend env bindings) (fcBody c) | |
78 | Nothing -> app env cs arg |
1 | { | |
2 | {-# OPTIONS -w #-} | |
3 | {-# LANGUAGE OverloadedStrings #-} | |
4 | ||
5 | module Language.Virgil.Lexer where | |
6 | ||
7 | import Control.Monad (liftM) | |
8 | import Data.Text (Text) | |
9 | import qualified Data.Text as T | |
10 | ||
11 | import Prelude hiding (lex) | |
12 | } | |
13 | ||
14 | %wrapper "monadUserState" | |
15 | ||
16 | $digit = 0-9 | |
17 | $alpha = [A-Za-z] | |
18 | ||
19 | tokens :- | |
20 | $white+ ; | |
21 | "#".* ; | |
22 | ||
23 | $digit+ { lex (TkInt . read . T.unpack) } | |
24 | \"[^\"]*\" { lex strLiteral } -- this is for syntax highlighting -> " | |
25 | ||
26 | true { lex' (TkKw KwTrue) } | |
27 | false { lex' (TkKw KwFalse) } | |
28 | null { lex' (TkKw KwNull) } | |
29 | ||
30 | \{ { lex' TkLCurl } | |
31 | \} { lex' TkRCurl } | |
32 | \[ { lex' TkLBrac } | |
33 | \] { lex' TkRBrac } | |
34 | ||
35 | { | |
36 | data Token = Token AlexPosn TkType deriving (Eq, Show) | |
37 | ||
38 | data KwType | |
39 | = KwTrue | |
40 | | KwFalse | |
41 | | KwNull | |
42 | deriving (Eq, Show) | |
43 | ||
44 | data TkType | |
45 | = TkInt Integer | |
46 | | TkStr Text | |
47 | | TkKw KwType | |
48 | | TkLCurl | |
49 | | TkRCurl | |
50 | | TkLBrac | |
51 | | TkRBrac | |
52 | | TkEOF | |
53 | deriving (Eq, Show) | |
54 | ||
55 | strLiteral :: Text -> TkType | |
56 | strLiteral = TkStr . T.drop 1 . T.dropEnd 1 | |
57 | ||
58 | data AlexUserState = AlexUserState | |
59 | { filePath :: FilePath | |
60 | } deriving (Eq, Show) | |
61 | ||
62 | alexInitUserState :: AlexUserState | |
63 | alexInitUserState = AlexUserState "<unknown>" | |
64 | ||
65 | getFilePath :: Alex FilePath | |
66 | getFilePath = liftM filePath alexGetUserState | |
67 | ||
68 | setFilePath :: FilePath -> Alex () | |
69 | setFilePath = alexSetUserState . AlexUserState | |
70 | ||
71 | alexMonadScan' :: Alex Token | |
72 | alexMonadScan' = do | |
73 | inp <- alexGetInput | |
74 | sc <- alexGetStartCode | |
75 | case alexScan inp sc of | |
76 | AlexEOF -> alexEOF | |
77 | AlexError (p, _, _, s) -> | |
78 | alexError' p ("lexical error at character '" ++ take 1 s ++ "'") | |
79 | AlexSkip inp' len -> do | |
80 | alexSetInput inp' | |
81 | alexMonadScan' | |
82 | AlexToken inp' len action -> do | |
83 | alexSetInput inp' | |
84 | action (ignorePendingBytes inp) len | |
85 | ||
86 | alexError' :: AlexPosn -> String -> Alex a | |
87 | alexError' (AlexPn _ l c) msg = do | |
88 | fp <- getFilePath | |
89 | alexError (fp ++ ":" ++ show l ++ ":" ++ show c ++ ": " ++ msg) | |
90 | ||
91 | alexEOF :: Alex Token | |
92 | alexEOF = do | |
93 | (p,_,_,_) <- alexGetInput | |
94 | return (Token p TkEOF) | |
95 | ||
96 | ||
97 | lex :: (Text -> TkType) -> AlexAction Token | |
98 | lex f = \(p,_,_,s) i -> return (Token p (f (T.pack (take i s)))) | |
99 | ||
100 | lex' :: TkType -> AlexAction Token | |
101 | lex' = lex . const | |
102 | ||
103 | runAlex' :: Alex a -> FilePath -> String -> Either String a | |
104 | runAlex' mote fp input = runAlex input (setFilePath fp >> mote) | |
105 | } |
1 | { | |
2 | {-# LANGUAGE OverloadedStrings #-} | |
3 | ||
4 | module Language.Virgil.Parser where | |
5 | ||
6 | import Language.Virgil.AST | |
7 | import Language.Virgil.Lexer | |
8 | ||
9 | } | |
10 | ||
11 | %name parse | |
12 | %tokentype { Token } | |
13 | %monad { Alex } | |
14 | %lexer { lexwrap } { Token _ TkEOF } | |
15 | %error { happyError } | |
16 | ||
17 | %token | |
18 | '{' { Token _ TkLCurl } | |
19 | '}' { Token _ TkRCurl } | |
20 | '[' { Token _ TkLBrac } | |
21 | ']' { Token _ TkRBrac } | |
22 | ||
23 | true { Token _ (TkKw KwTrue) } | |
24 | false { Token _ (TkKw KwFalse) } | |
25 | null { Token _ (TkKw KwNull) } | |
26 | ||
27 | int { Token _ (TkInt $$) } | |
28 | str { Token _ (TkStr $$) } | |
29 | ||
30 | %% | |
31 | ||
32 | tlexpr | |
33 | : '[' list { mkArray $2 } | |
34 | | '{' dict { mkObject $2 } | |
35 | ||
36 | expr | |
37 | : tlexpr { $1 } | |
38 | | int { ChNumber (fromIntegral $1) } | |
39 | | str { ChString $1 } | |
40 | | true { ChBool True } | |
41 | | false { ChBool False} | |
42 | | null { ChNull } | |
43 | ||
44 | list | |
45 | : ']' { [] } | |
46 | | expr list { $1 : $2 } | |
47 | ||
48 | dict | |
49 | : '}' { [] } | |
50 | | str expr dict { ($1, $2) : $3 } | |
51 | ||
52 | { | |
53 | ||
54 | -- info :: a -> Alex (Info a) | |
55 | -- info x = do | |
56 | -- (AlexPn _ ln cl,_,_,_) <- alexGetInput | |
57 | -- fp <- getFilePath | |
58 | -- return $ Info { node = x | |
59 | -- , srcLine = ln | |
60 | -- , srcCol = cl | |
61 | -- , srcFile = fp | |
62 | -- } | |
63 | ||
64 | lexwrap :: (Token -> Alex a) -> Alex a | |
65 | lexwrap = (alexMonadScan' >>=) | |
66 | ||
67 | happyError :: Token -> Alex a | |
68 | happyError (Token p t) = | |
69 | alexError' p ("parse error at token " ++ show t) | |
70 | ||
71 | parseFile :: FilePath -> String -> Either String ChValue | |
72 | parseFile = runAlex' parse | |
73 | } |
1 | module Language.Virgil.Pretty where | |
2 | ||
3 | import Data.Monoid ((<>)) | |
4 | import Text.PrettyPrint.HughesPJ.Class | |
5 | import Language.Virgil.AST | |
6 | ||
7 | instance Pretty ChValue where | |
8 | pPrint (ChObject obj) = braces $ text "..." | |
9 | pPrint |
1 | module Language.Virgil.Types where | |
2 | ||
3 | import Language.Cherenkov.AST | |
4 | ||
5 | type TEnv = HashMap Ident Typ | |
6 | ||
7 | inferType :: TEnv -> Expr -> () | |
8 | inferType _ (ELit (LInt _)) = TNamed "Int" | |
9 | inferType _ (ELit (LDouble _)) = TNamed "Double" | |
10 | inferType _ (ELit (LString _)) = TNamed "String" | |
11 | inferType _ (ELit (LBool _)) = TNamed "Bool" | |
12 | inferType e (ETup ts) = VTup (map (inferType e ts)) | |
13 | inferType e (EList ls) = undefined | |
14 | inferType e (EVar v) = undefined |
1 | module Language.Virgil | |
2 | ( -- * Virgil Parsers | |
3 | decode | |
4 | , eitherDecode | |
5 | , decodeStrict | |
6 | , eitherDecodeStrict | |
7 | -- * Re-Exported Aeson Types | |
8 | -- ** Core JSON Types | |
9 | , Aeson.Value(..) | |
10 | , Aeson.Array | |
11 | , Aeson.Object | |
12 | -- ** Type Conversion | |
13 | , Aeson.FromJSON(..) | |
14 | , Aeson.ToJSON(..) | |
15 | , Aeson.Result(..) | |
16 | , Aeson.fromJSON | |
17 | -- ** Inspecting Values | |
18 | , Aeson.withObject | |
19 | , Aeson.withText | |
20 | , Aeson.withArray | |
21 | , Aeson.withScientific | |
22 | , Aeson.withBool | |
23 | -- ** Accessors | |
24 | , (Aeson..:) | |
25 | , (Aeson..:?) | |
26 | , (Aeson..:!) | |
27 | , (Aeson..!=) | |
28 | ) where | |
29 | ||
30 | import Data.Aeson (FromJSON, Result(..), fromJSON) | |
31 | import qualified Data.Aeson as Aeson | |
32 | import qualified Data.ByteString as BSE | |
33 | import Data.ByteString.Lazy (ByteString) | |
34 | import qualified Data.ByteString.Lazy.Char8 as BS | |
35 | import Language.Virgil.AST (ChValue) | |
36 | import Language.Virgil.Eval | |
37 | import Language.Virgil.Lexer | |
38 | import Language.Virgil.Parser | |
39 | ||
40 | parseVirgil :: ByteString -> Either String ChValue | |
41 | parseVirgil = runAlex' parse "<input>" . BS.unpack | |
42 | ||
43 | -- | Deserialize and execute a Virgil program. If parsing fails, | |
44 | -- or if type-checking fails, or if the execution of the Virgil | |
45 | -- program otherwise fails at runtime, then this function will | |
46 | -- return 'Nothing'. | |
47 | -- | |
48 | -- Note that Virgil rules out recursive programs, so executing a | |
49 | -- Virgil program won't loop forever---but it may still take | |
50 | -- a fair amount longer than parsing the equivalent JSON! | |
51 | decode :: FromJSON a => ByteString -> Maybe a | |
52 | decode bs = case eitherDecode bs of | |
53 | Error err -> Nothing | |
54 | Success val -> pure val | |
55 | ||
56 | -- | Deserialize and execute a Virgil program. If parsing fails, | |
57 | -- or if type-checking fails, or if the execution of the Virgil | |
58 | -- program otherwise fails at runtime, then this function will | |
59 | -- return an approrpriately informative error message. | |
60 | eitherDecode :: FromJSON a => ByteString -> Result a | |
61 | eitherDecode bs = case parseVirgil bs of | |
62 | Left err -> Error err | |
63 | Right ch -> case evalCh ch of | |
64 | Left err -> Error err | |
65 | Right v -> fromJSON v | |
66 | ||
67 | decodeStrict :: FromJSON a => BSE.ByteString -> Maybe a | |
68 | decodeStrict = decode . BS.fromStrict | |
69 | ||
70 | eitherDecodeStrict :: FromJSON a => BSE.ByteString -> Result a | |
71 | eitherDecodeStrict = eitherDecode . BS.fromStrict |
1 | name: vergilius | |
2 | version: 0.1.0.0 | |
3 | -- synopsis: | |
4 | -- description: | |
5 | license: BSD3 | |
6 | license-file: LICENSE | |
7 | author: Getty Ritter | |
8 | maintainer: gdritter@galois.com | |
9 | -- copyright: | |
10 | category: Language | |
11 | build-type: Simple | |
12 | -- extra-source-files: | |
13 | cabal-version: >=1.10 | |
14 | ||
15 | library | |
16 | exposed-modules: Language.Vergilius, | |
17 | Language.Vergilius.Lexer, | |
18 | Language.Vergilius.Parser, | |
19 | Language.Vergilius.Eval | |
20 | -- other-modules: | |
21 | -- other-extensions: | |
22 | build-depends: base >=4.7 && <4.9, | |
23 | aeson, | |
24 | array, | |
25 | bytestring, | |
26 | unordered-containers, | |
27 | scientific, | |
28 | text, | |
29 | vector, | |
30 | pretty | |
31 | build-tools: alex | |
32 | default-language: Haskell2010⏎ |
1 | name: virgil | |
2 | version: 0.1.0.0 | |
3 | synopsis: An executable configuration language. | |
4 | -- description: | |
5 | license: BSD3 | |
6 | license-file: LICENSE | |
7 | author: Getty Ritter | |
8 | maintainer: gettyritter@gmail.com | |
9 | copyright: 2016 Getty Ritter | |
10 | category: Language | |
11 | build-type: Simple | |
12 | cabal-version: >=1.10 | |
13 | ||
14 | library | |
15 | exposed-modules: Language.Virgil | |
16 | other-modules: Language.Virgil.Lexer, | |
17 | Language.Virgil.Parser, | |
18 | Language.Virgil.Eval, | |
19 | Language.Virgil.AST | |
20 | build-depends: base >=4.7 && <4.9, | |
21 | aeson, | |
22 | array, | |
23 | bytestring, | |
24 | unordered-containers, | |
25 | scientific, | |
26 | text, | |
27 | vector, | |
28 | pretty | |
29 | build-tools: alex | |
30 | default-language: Haskell2010⏎ |