New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type check input #165
Type check input #165
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,11 +17,10 @@ import Language.Types | |
import GHC.Generics | ||
import Data.Aeson | ||
import Runtime.Values | ||
import Runtime.Eval | ||
import Parser.Parser | ||
import Runtime.Monad | ||
import Error.Error | ||
|
||
import Text.Parsec (errorPos, sourceLine, sourceColumn, ParseError) | ||
|
||
-- | Representation of a request to read a BoGL file | ||
data SpielRead = SpielRead { | ||
path :: String | ||
|
@@ -56,31 +55,18 @@ instance FromJSON SpielFile where | |
parseJSON (Object v) = SpielFile <$> v .: "fileName" <*> v .: "content" | ||
parseJSON _ = fail "Unable to parse File option" -- fallback | ||
|
||
instance FromJSON Val where | ||
parseJSON (Object v) = do | ||
t <- v .: "input" | ||
case parseLine t of | ||
Right x -> case runWithBuffer (emptyEnv (0,0)) ([], [], 1) x of | ||
Right (_, v') -> return v' | ||
Left _ -> fail "failed to parse..." -- FIXME | ||
Left _ -> fail "failed to parse..." | ||
parseJSON _ = fail "Unable to parse Val" | ||
|
||
|
||
-- | Representation of input to the repl, from the user | ||
data SpielCommand = SpielCommand { | ||
prelude :: String, | ||
file :: String, | ||
input :: String, | ||
buffer :: [Val], | ||
buffer :: [String], | ||
programName :: String | ||
} deriving (Eq, Show, Generic) | ||
|
||
|
||
instance ToJSON SpielCommand | ||
instance FromJSON SpielCommand | ||
|
||
|
||
-- | Error message | ||
type Message = String | ||
-- | Error line number | ||
|
@@ -125,6 +111,14 @@ data SpielResponse = | |
Log String | ||
deriving(Eq, Generic) | ||
|
||
-- | Smart constructor for a SpielParseError | ||
spielParseError :: String -> ParseError -> SpielResponse | ||
spielParseError fn er = SpielParseError l c fn (show er) | ||
where | ||
pos = errorPos er | ||
l = sourceLine pos | ||
c = sourceColumn pos | ||
|
||
instance ToJSON SpielResponse where | ||
|
||
instance Show SpielResponse where | ||
|
@@ -144,8 +138,8 @@ instance Show SpielResponse where | |
show (SpielTypeHole m _ _) = show m | ||
-- show a fallback error, such as reading a bad-file | ||
show (SpielError m) = show m | ||
show x = show x | ||
|
||
show (SpielTypeError e) = show e | ||
show _ = "unused SpielResponse" -- todo: clean these out | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did not want to write cases for unused responses, but I also did not want to remove them in this change set for fear of changing too much at once. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good call. This is a certain unspoken limit before the PR should just be split into another PR haha |
||
|
||
-- | List of spiel responses | ||
type SpielResponses = [SpielResponse] |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,49 +10,80 @@ Holds the routines for parsing and interpreting a BoGL Prelude and Game file. | |
|
||
module API.Run (_runCodeWithCommands) where | ||
|
||
import API.JSONData | ||
import API.JSONData (SpielResponse(..), SpielCommand(..), SpielResponses, spielParseError) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file still needs quite a bit of cleaning up, but I had to let it go eventually. We can do some more work to polish the API later and do it then. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be good to clean things up once we've taken care of whatever comes up at this upcoming meeting. In addition, it would probably be good to make several smaller changes in separate PRs. Everything that is changed there will go live, and the more we change the more likely we are to introduce a bug. Small changes will make that easier to track down when they occur. |
||
import Parser.Parser | ||
import Language.Syntax | ||
import Language.Types | ||
import Text.Parsec.Pos | ||
import Text.Parsec (errorPos) | ||
import Text.Parsec.Error | ||
|
||
import Typechecker.Typechecker | ||
import Runtime.Eval | ||
import Typechecker.Monad (Env(types)) | ||
import Runtime.Eval (runWithBuffer, bindings_) | ||
import Runtime.Values | ||
import Runtime.Monad | ||
import Runtime.Monad (Buffer, emptyEnv) | ||
|
||
import Control.Monad(liftM, ap) | ||
|
||
-- | Runs BoGL code from raw text with the given commands | ||
-- utilizes parsePreludeAndGameText to parse the code directly, | ||
-- without reading it from a file first | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function was unnecessarily in the IO monad! I pulled it out so that it is easier to test. |
||
_runCodeWithCommands :: SpielCommand -> IO SpielResponses | ||
_runCodeWithCommands :: SpielCommand -> SpielResponses | ||
_runCodeWithCommands sc@(SpielCommand _prelude gameFile _ _ filename) = | ||
(_handleParsed sc $ parsePreludeAndGameText _prelude gameFile filename) | ||
|
||
|
||
-- | Handles result of parsing a prelude and game | ||
_handleParsed :: SpielCommand -> IO (Either ParseError (Game SourcePos)) -> IO SpielResponses | ||
_handleParsed (SpielCommand _ gameFile inpt buf _) parsed = do | ||
pparsed <- parsed | ||
case pparsed of | ||
Right game -> do | ||
let checked = tc game | ||
if success checked | ||
then return $ [SpielTypes (rtypes checked), (serverRepl game gameFile inpt (buf, [], 1))] | ||
else return $ SpielTypes (rtypes checked) : map (SpielTypeError . snd) (errors checked) | ||
Left _err -> do | ||
let position = errorPos _err | ||
l = sourceLine position | ||
c = sourceColumn position | ||
in | ||
return $ [SpielParseError l c gameFile (show _err)] | ||
|
||
|
||
-- |Handles running a command in the repl from the server | ||
case parsePreludeAndGameText _prelude gameFile filename of | ||
Right game -> _handleParsed sc game | ||
Left progParseError -> [spielParseError gameFile progParseError] | ||
|
||
-- | Typechecks and evaluates a parsed prelude and game | ||
_handleParsed :: SpielCommand -> (Game SourcePos) -> SpielResponses | ||
_handleParsed (SpielCommand _ gameFile replExpr buf _) game = | ||
if success progTCRes then | ||
case handleInput inputEnv buf of | ||
H (Left er) -> [er] | ||
H (Right buf') -> [progTypes, (serverRepl game gameFile replExpr (buf', [], 1))] | ||
else | ||
progTypes : map (SpielTypeError . snd) (errors progTCRes) | ||
where | ||
progTCRes = tc game | ||
inputEnv = (e progTCRes) { types = [] } -- keep the input type, discard other bindings | ||
progTypes = SpielTypes (rtypes progTCRes) | ||
|
||
-- | Parses, typecheckes, and evaluates input strings so they can be used in evaluation | ||
-- as a 'Val' buffer | ||
-- | ||
-- Note: if anything other than the first input string results in failure, then something is | ||
-- wrong on the front end. These have all been verified in previous calls to _runCodeWithCommands | ||
handleInput :: Env -> [String] -> SpielHandler [Val] | ||
handleInput inputEnv xs = do | ||
exprs <- mapM parseInput xs | ||
exprs' <- mapM (tcInput inputEnv) exprs | ||
mapM evalInput exprs' | ||
|
||
-- | Parses input strings so that they can be typechecked and evaluated | ||
parseInput :: String -> SpielHandler (Expr SourcePos) | ||
parseInput s = case parseFromText literal "user input" s of | ||
(Left er) -> hFail $ spielParseError "user input" er | ||
(Right ex) -> return ex | ||
|
||
-- | Typechecks input expressions | ||
tcInput :: Env -> (Expr SourcePos) -> SpielHandler (Expr SourcePos) | ||
tcInput inputEnv x = case exprHasInputType inputEnv x of | ||
(Left er) -> hFail $ SpielTypeError er | ||
Right _ -> return x | ||
|
||
-- | Evaluates typechecker input expressions. | ||
-- If a literal fails to evaluate, then something is wrong with the evaluator, | ||
-- hence the internal error | ||
evalInput :: (Expr SourcePos) -> SpielHandler Val | ||
evalInput x = case run x of | ||
Left _ -> hFail $ SpielError "input evaluation internal error" | ||
Right (_, v') -> return v' | ||
where | ||
run = runWithBuffer (emptyEnv (0,0)) ([], [], 1) | ||
|
||
-- | Handles running a command in the repl from the server | ||
serverRepl :: (Game SourcePos) -> String -> String -> Buffer -> SpielResponse | ||
serverRepl (Game _ i@(BoardDef (szx,szy) _) b vs) fn inpt buf = do | ||
case parseLine inpt of | ||
serverRepl (Game _ i@(BoardDef (szx,szy) _) b vs) fn replExpr buf = do | ||
case parseLine replExpr of | ||
Right x -> do | ||
case tcexpr (environment i b vs) x of | ||
Right _ -> do -- Right t | ||
|
@@ -66,15 +97,31 @@ serverRepl (Game _ i@(BoardDef (szx,szy) _) b vs) fn inpt buf = do | |
-- boards and tape returned, returns the boards for displaying on the frontend | ||
Left (bs, _) -> (SpielPrompt bs) | ||
|
||
-- runtime error encountered | ||
-- TODO REMOVED Redundant clause? | ||
--Left err -> (SpielRuntimeError (show err)) | ||
|
||
-- typechecker encountered an error in the expression | ||
Left _err -> (SpielTypeError _err) | ||
-- bad parse | ||
Left _err -> | ||
let position = errorPos _err | ||
l = sourceLine position | ||
c = sourceColumn position in | ||
(SpielParseError l c fn (show _err)) | ||
Left progParseError -> spielParseError fn progParseError | ||
|
||
newtype SpielHandler a = H (Either SpielResponse a) | ||
|
||
instance Show a => Show (SpielHandler a) where | ||
show (H (Left s)) = show s | ||
show (H (Right x)) = show x | ||
|
||
-- Monad for handling an operation in which failure is represented as an appropriate SpielResponse | ||
instance Monad SpielHandler where | ||
return res = H $ Right res | ||
(H h) >>= k = case h of | ||
Left er -> hFail er | ||
Right res -> k res | ||
|
||
instance Functor SpielHandler where | ||
fmap = liftM | ||
|
||
instance Applicative SpielHandler where | ||
pure = return | ||
(<*>) = ap | ||
|
||
-- Smart constructor for a failure case in SpielHandler | ||
hFail :: SpielResponse -> SpielHandler a | ||
hFail = H . Left |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ data Error = Error { | |
|
||
-- Note: the error code functionality works, but only for type errors so they are not displayed | ||
instance Show Error where | ||
show (Error (TE e@(InputMismatch _ _ _)) _ _) = show e | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice |
||
show (Error (TE e) _ p) = "Type error in " ++ srcname ++ show e | ||
where | ||
srcname = case sourceName p of | ||
|
@@ -57,3 +58,4 @@ cterr terr = Error (TE terr) (assign terr) | |
(Dereff _ _) -> 8 | ||
(Uninitialized _) -> 10 | ||
(SigBadFeq _ _ _) -> 11 | ||
(InputMismatch _ _ _) -> 12 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -94,9 +94,9 @@ boardt = Plain boardxt | |
boardxt :: Xtype | ||
boardxt = bnestx Board | ||
|
||
-- | Nest a Btype as a Type | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused and it caused a lot of issues with shadowing |
||
p :: Btype -> Type | ||
p b = Plain $ X b S.empty | ||
-- | Xtype smart constructor for Input | ||
inputxt :: Xtype | ||
inputxt = bnestx Input | ||
|
||
instance Show Xtype where | ||
show (X b xs) | S.null xs = show b | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,8 +6,8 @@ License : BSD-3 | |
-} | ||
|
||
module Parser.Parser ( | ||
parseLine, parsePreludeFromText, parseGameFromText, parseGameFile, parsePreludeAndGameText, | ||
expr, isLeft, parseAll, valdef, ftype, xtype, boardeqn, equation, decl, parseGame, typesyn, Parser, lexer, reservedNames, enum) | ||
parseLine, parseFromText, parsePreludeFromText, parseGameFromText, parseGameFile, parsePreludeAndGameText, | ||
expr, isLeft, parseAll, valdef, ftype, xtype, boardeqn, equation, decl, parseGame, typesyn, Parser, lexer, reservedNames, enum, literal) | ||
where | ||
|
||
import Parser.Error | ||
|
@@ -143,9 +143,6 @@ op s f assoc = Infix (reservedOp s *> pure f) assoc | |
lexeme :: ParsecT String u Identity a -> ParsecT String u Identity a | ||
lexeme = P.lexeme lexer | ||
|
||
-- TODO REMOVED UNUSED | ||
--integer = P.integer lexer | ||
|
||
-- | Integer token recognizer | ||
int :: ParsecT String u Identity Int | ||
int = fromInteger <$> P.integer lexer | ||
|
@@ -196,12 +193,16 @@ gameIdentifier = capIdentifier | |
capIdentifier :: ParsecT String u Identity [Char] | ||
capIdentifier = lookAhead upper *> identifier | ||
|
||
-- | Comma separated values, 2 or more | ||
commaSep2 :: ParsecT String u Identity a -> ParsecT String u Identity [a] | ||
commaSep2 p = (:) <$> (lexeme p <* lexeme comma) <*> commaSep1 p | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awesome 👍 |
||
|
||
-- | Comma separated values, 1 or more | ||
commaSep1 :: ParsecT String u Identity a -> ParsecT String u Identity [a] | ||
commaSep1 = P.commaSep1 lexer | ||
|
||
-- | 0 or more comma separated values | ||
-- unused | ||
-- unused, but possibly useful | ||
--commaSep :: ParsecT String u Identity a -> ParsecT String u Identity [a] | ||
--commaSep = P.commaSep lexer | ||
|
||
|
@@ -217,6 +218,22 @@ reservedOp = P.reservedOp lexer | |
comma :: ParsecT String u Identity String | ||
comma = P.comma lexer | ||
|
||
-- | A parser for a literal expression (to be used for input). | ||
-- Consumes all preceding whitespace since it is a top-level parser. | ||
literal :: Parser (Expr SourcePos) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we decide that only literals should be allowed, we can use this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed |
||
literal = spaces *> -- note: intentionally does not use whiteSpace, which allows comments | ||
(I <$> int | ||
<|> | ||
B <$> (reserved "True" *> pure True) | ||
<|> | ||
B <$> (reserved "False" *> pure False) | ||
<|> | ||
S <$> capIdentifier | ||
<|> | ||
(try $ parens (literal <* notFollowedBy comma)) -- parenthesized literal | ||
<|> | ||
Tuple <$> parens (commaSep2 literal)) | ||
|
||
-- | Atomic expression, under an annotation | ||
atom :: Parser (Expr SourcePos) | ||
atom = | ||
|
@@ -540,9 +557,8 @@ parseGameFromText :: String -> String -> ([Maybe (ValDef SourcePos)], ParState) | |
parseGameFromText prog fileName pr = parseWithState (snd pr) (parseGame (catMaybes (fst pr))) fileName prog | ||
|
||
-- | Parse a prelude and game from text directly, without a file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was no reason for this to be in IO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know I added the IO stuff in when we were first writing these out. I believe that was because we were still reading & writing from files instead of parsing the responses directly. Seems it was never re-addressed when we phased that out. |
||
parsePreludeAndGameText :: String -> String -> String -> IO ParseResult | ||
parsePreludeAndGameText preludeContent gameFileContent fileName = do | ||
prel <- return (parsePreludeFromText preludeContent) | ||
case prel of | ||
Right r -> return (parseGameFromText gameFileContent fileName r) | ||
Left err -> return $ Left err | ||
parsePreludeAndGameText :: String -> String -> String -> ParseResult | ||
parsePreludeAndGameText preludeContent gameFileContent fileName = | ||
case parsePreludeFromText preludeContent of | ||
Right r -> (parseGameFromText gameFileContent fileName r) | ||
Left err -> Left err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grrrrrr, I lost more time than I would like to admit due to this line.