diff --git a/LobsterLang/LobsterLang.cabal b/LobsterLang/LobsterLang.cabal index 574c3b5..b6ca935 100644 --- a/LobsterLang/LobsterLang.cabal +++ b/LobsterLang/LobsterLang.cabal @@ -27,6 +27,7 @@ library exposed-modules: AST AstEval + AstOptimizer Compiler Parse Scope @@ -69,6 +70,7 @@ test-suite LobsterLang-test main-is: Spec.hs other-modules: AstEvalSpec + AstOptimizerSpec CompilerSpec VmSpec Paths_LobsterLang diff --git a/LobsterLang/src/AstEval.hs b/LobsterLang/src/AstEval.hs index 8f64209..6ca026f 100644 --- a/LobsterLang/src/AstEval.hs +++ b/LobsterLang/src/AstEval.hs @@ -32,6 +32,19 @@ sexprToAst (SExpr.Symbol "true") = Just (Boolean True) sexprToAst (SExpr.Symbol "false") = Just (Boolean False) sexprToAst (SExpr.Symbol s) = Just (AST.Symbol s Nothing) +noEvaluationError :: String -> String +noEvaluationError s = "No evaluation in one or more parameters of " ++ s + +invalidParamsBiOp :: String -> String +invalidParamsBiOp op = + "One or more parameters of binary operator '" ++ op ++ "' is invalid" + +tooMuchParams :: String -> String +tooMuchParams s = "Too much parameters for " ++ s + +notEnoughParams :: String -> String +notEnoughParams s = "Not enough parameters for " ++ s + -- | Evaluate a 'Ast'. -- Takes a stack representing variables and the Ast to evaluate. -- Returns a tuple containing either the resulting Ast @@ -50,8 +63,13 @@ evalAst stack (AST.Value i) = (Right (Just (AST.Value i)), stack) evalAst stack (AST.Symbol s asts) = case getVarInScope stack s of Nothing -> (Left ("Symbol '" ++ s ++ "' doesn't exist in the current or global scope"), stack) Just (FunctionValue params ast Nothing) -> evalAst stack (FunctionValue params ast asts) - Just value -> evalAst stack value -evalAst stack (AST.List l) = (Right (Just (AST.List l)), stack) + Just value -> case asts of + Nothing -> evalAst stack value + _ -> (Left ("Symbol '" ++ s ++ "' isn't a function"), stack) +evalAst stack (AST.List l) = case evalSubParams stack l of + (Left err) -> (Left err, stack) + (Right (Just l')) -> (Right (Just (AST.List l')), stack) + (Right Nothing) -> (Left "Cannot have Nothing in a list", stack) evalAst stack (AST.String str) = (Right (Just (AST.String str)), stack) evalAst stack (Boolean b) = (Right (Just (Boolean b)), stack) evalAst stack (Call "+" astList) = evalBiValOp (+) stack (Call "+" astList) @@ -71,14 +89,17 @@ evalAst stack (Call "&&" astList) = evalBiBoolOp (&&) stack (Call "&&" astList) evalAst stack (Call "||" astList) = evalBiBoolOp (||) stack (Call "||" astList) evalAst stack (Call "^^" astList) = evalBiBoolOp (\a b -> (a || b) && not (a && b)) stack (Call "||" astList) evalAst stack (Call "!" [AST.Boolean b]) = (Right (Just (AST.Boolean (not b))), stack) --- TODO: add ! support for evaluation of sub parameters -evalAst stack (Call "!" [_]) = (Left "Parameter of unary operator '!' isn't a boolean", stack) +evalAst stack (Call "!" [ast]) = case evalAst stack ast of + (Left err, _) -> (Left err, stack) + (Right (Just (Boolean b)), _) -> (Right (Just (AST.Boolean (not b))), stack) + (Right Nothing, _) -> (Left "No evaluation in parameter of unary operator '!'", stack) + (Right _, _) -> (Left "Parameter of unary operator '!' isn't a boolean", stack) evalAst stack (Call "!" _) = (Left "Invalid number of parameter for unary operator '!'", stack) evalAst stack (Call "@" [ast]) = case astToString stack ast of Left err -> (Left err, stack) Right ast' -> (Right (Just ast'), stack) -evalAst stack (Call "@" (_ : _)) = (Left "Too much parameters for string conversion", stack) -evalAst stack (Call "@" []) = (Left "Not enough parameters for string conversion", stack) +evalAst stack (Call "@" (_ : _)) = (Left (tooMuchParams "string conversion"), stack) +evalAst stack (Call "@" []) = (Left (notEnoughParams "string conversion"), stack) evalAst stack (Call "++" astList) = evalBiListOp (\l el -> l ++ [el]) stack (Call "++" astList) evalAst stack (Call "--" astList) = evalBiListOp (\l el -> filter (/= el) l) stack (Call "++" astList) evalAst stack (Call "!!" astList) = case getElemInAstList stack (Call "!!" astList) of @@ -90,12 +111,13 @@ evalAst stack (Call "$" [ast1, ast2]) = case evalAst stack ast1 of (Right _, stack') -> case evalAst stack' ast2 of (Left err', _) -> (Left err', stack) (Right ast, stack'') -> (Right ast, stack'') -evalAst stack (Call "$" (_ : _)) = (Left "Too much parameters for operator $ (needs 2)", stack) -evalAst stack (Call "$" []) = (Left "Not enough parameters for operator $ (needs 2)", stack) +evalAst stack (Call "$" (_ : _)) = (Left (tooMuchParams "operator $ (needs 2)"), stack) +evalAst stack (Call "$" []) = (Left (notEnoughParams "operator $ (needs 2)"), stack) evalAst stack (Call unknown _) = (Left ("Unknown operator: " ++ unknown), stack) evalAst stack (FunctionValue params ast Nothing) = (Right (Just (FunctionValue params ast Nothing)), stack) -evalAst stack (FunctionValue [] ast (Just [])) = Data.Bifunctor.second clearScope (evalAst (beginScope stack) ast) +evalAst stack (FunctionValue [] ast (Just [])) = + Data.Bifunctor.second clearScope (evalAst (beginScope stack) ast) evalAst stack (FunctionValue params ast (Just [])) = (Right (Just (FunctionValue params ast Nothing)), stack) evalAst stack (FunctionValue params ast (Just asts)) @@ -109,10 +131,10 @@ evalAst stack (FunctionValue params ast (Just asts)) stack ) | otherwise = case evalAst stack (head asts) of - (Left err, _) -> (Left err, stack) - (Right Nothing, _) -> (Left "No evaluation in one or more parameters of expression", stack) - (Right (Just ast'), _) -> - evalAst stack (FunctionValue (tail params) (Call "$" [Define (head params) ast', ast]) (Just (tail asts))) + (Left err, _) -> (Left err, stack) + (Right Nothing, _) -> (Left (noEvaluationError "expression"), stack) + (Right (Just ast'), _) -> + evalAst stack (FunctionValue (tail params) (Call "$" [Define (head params) ast', ast]) (Just (tail asts))) evalAst stack (Cond (AST.Boolean b) a1 (Just a2)) | b = evalAst stack a1 | otherwise = evalAst stack a2 @@ -135,25 +157,25 @@ evalAst stack (Cond ast a1 a2) = case fst (evalAst stack ast) of -- application of the function onto the values inside the given 'Ast' -- or a 'String' containing the error message in case of error evalBiValOp :: (Int -> Int -> Int) -> [ScopeMb] -> Ast -> (Either String (Maybe Ast), [ScopeMb]) -evalBiValOp _ stack (Call op [AST.Boolean _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [_, AST.Boolean _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [AST.String _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [_, AST.String _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [AST.List _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [_, AST.List _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiValOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) +evalBiValOp _ stack (Call op [AST.Boolean _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [_, AST.Boolean _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [AST.String _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [_, AST.String _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [AST.List _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [_, AST.List _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left (invalidParamsBiOp op), stack) +evalBiValOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left (invalidParamsBiOp op), stack) evalBiValOp f stack (Call _ [AST.Value a, AST.Value b]) = (Right (Just (AST.Value (f a b))), stack) evalBiValOp _ stack (Call op [ast1, ast2]) = case evalSubParams stack [ast1, ast2] of Left err -> (Left err, stack) Right asts -> maybe - (Left ("No evaluation in one or more parameters of binary operator '" ++ op ++ "'"), stack) + (Left (noEvaluationError "binary operator '" ++ op ++ "'"), stack) (evalAst stack . Call op) asts -evalBiValOp _ stack (Call op (_ : _ : _)) = (Left ("Too much parameter for binary operator '" ++ op ++ "'"), stack) -evalBiValOp _ stack (Call op _) = (Left ("Not enough parameter for binary operator '" ++ op ++ "'"), stack) +evalBiValOp _ stack (Call op (_ : _ : _)) = (Left (tooMuchParams "binary operator '" ++ op ++ "'"), stack) +evalBiValOp _ stack (Call op _) = (Left (notEnoughParams "binary operator '" ++ op ++ "'"), stack) evalBiValOp _ stack _ = (Left "Ast isn't a Call", stack) -- | Evaluate the 'Ast' for a given binary boolean operator @@ -164,25 +186,25 @@ evalBiValOp _ stack _ = (Left "Ast isn't a Call", stack) -- application of the function onto the booleans inside the given 'Ast' -- or a 'String' containing the error message in case of error evalBiBoolOp :: (Bool -> Bool -> Bool) -> [ScopeMb] -> Ast -> (Either String (Maybe Ast), [ScopeMb]) -evalBiBoolOp _ stack (Call op [AST.Value _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [_, AST.Value _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [AST.String _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [_, AST.String _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [AST.List _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [_, AST.List _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiBoolOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) +evalBiBoolOp _ stack (Call op [AST.Value _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [_, AST.Value _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [AST.String _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [_, AST.String _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [AST.List _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [_, AST.List _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left (invalidParamsBiOp op), stack) +evalBiBoolOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left (invalidParamsBiOp op), stack) evalBiBoolOp f stack (Call _ [AST.Boolean a, AST.Boolean b]) = (Right (Just (AST.Boolean (f a b))), stack) evalBiBoolOp _ stack (Call op [ast1, ast2]) = case evalSubParams stack [ast1, ast2] of Left err -> (Left err, stack) Right asts -> maybe - (Left ("No evaluation in one or more parameters of binary operator '" ++ op ++ "'"), stack) + (Left (noEvaluationError "binary operator '" ++ op ++ "'"), stack) (evalAst stack . Call op) asts -evalBiBoolOp _ stack (Call op (_ : _ : _)) = (Left ("Too much parameter for binary operator '" ++ op ++ "'"), stack) -evalBiBoolOp _ stack (Call op _) = (Left ("Not enough parameter for binary operator '" ++ op ++ "'"), stack) +evalBiBoolOp _ stack (Call op (_ : _ : _)) = (Left (tooMuchParams "binary operator '" ++ op ++ "'"), stack) +evalBiBoolOp _ stack (Call op _) = (Left (notEnoughParams "binary operator '" ++ op ++ "'"), stack) evalBiBoolOp _ stack _ = (Left "Ast isn't a Call", stack) -- | Evaluate the 'Ast' for a given binary comparison operator @@ -193,25 +215,25 @@ evalBiBoolOp _ stack _ = (Left "Ast isn't a Call", stack) -- application of the function onto the values inside the given 'Ast' -- or a 'String' containing the error message in case of error evalBiCompValOp :: (Int -> Int -> Bool) -> [ScopeMb] -> Ast -> (Either String (Maybe Ast), [ScopeMb]) -evalBiCompValOp _ stack (Call op [AST.Boolean _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [_, AST.Boolean _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [AST.String _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [_, AST.String _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [AST.List _, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [_, AST.List _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiCompValOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left ("One or more parameters of binary operator '" ++ op ++ "' is invalid"), stack) +evalBiCompValOp _ stack (Call op [AST.Boolean _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [_, AST.Boolean _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [AST.String _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [_, AST.String _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [AST.List _, _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [_, AST.List _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left (invalidParamsBiOp op), stack) +evalBiCompValOp _ stack (Call op [_, AST.FunctionValue _ _ Nothing]) = (Left (invalidParamsBiOp op), stack) evalBiCompValOp f stack (Call _ [AST.Value a, AST.Value b]) = (Right (Just (AST.Boolean (f a b))), stack) evalBiCompValOp _ stack (Call op [ast1, ast2]) = case evalSubParams stack [ast1, ast2] of Left err -> (Left err, stack) Right asts -> maybe - (Left ("No evaluation in one or more parameters of binary operator '" ++ op ++ "'"), stack) + (Left (noEvaluationError "binary operator '" ++ op ++ "'"), stack) (evalAst stack . Call op) asts -evalBiCompValOp _ stack (Call op (_ : _ : _)) = (Left ("Too much parameter for binary operator '" ++ op ++ "'"), stack) -evalBiCompValOp _ stack (Call op _) = (Left ("Not enough parameter for binary operator '" ++ op ++ "'"), stack) +evalBiCompValOp _ stack (Call op (_ : _ : _)) = (Left (tooMuchParams "binary operator '" ++ op ++ "'"), stack) +evalBiCompValOp _ stack (Call op _) = (Left (notEnoughParams "binary operator '" ++ op ++ "'"), stack) evalBiCompValOp _ stack _ = (Left "Ast isn't a Call", stack) -- | Evaluate the 'Ast' for a given binary list operator @@ -222,21 +244,36 @@ evalBiCompValOp _ stack _ = (Left "Ast isn't a Call", stack) -- application of the function onto the values inside the given 'Ast' -- or a 'String' containing the error message in case of error evalBiListOp :: ([Ast] -> Ast -> [Ast]) -> [ScopeMb] -> Ast -> (Either String (Maybe Ast), [ScopeMb]) -evalBiListOp _ stack (Call op [AST.Boolean _, _]) = (Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiListOp _ stack (Call op [AST.Value _, _]) = (Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiListOp _ stack (Call op [AST.String _, _]) = (Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), stack) -evalBiListOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = (Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), stack) +evalBiListOp _ stack (Call op [AST.Boolean _, _]) = + ( Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), + stack + ) +evalBiListOp _ stack (Call op [AST.Value _, _]) = + ( Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), + stack + ) +evalBiListOp _ stack (Call op [AST.String _, _]) = + ( Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), + stack + ) +evalBiListOp _ stack (Call op [AST.FunctionValue _ _ Nothing, _]) = + ( Left ("First parameter of binary operator '" ++ op ++ "' is invalid"), + stack + ) evalBiListOp f stack (Call _ [AST.List a, ast]) = (Right (Just (AST.List (f a ast))), stack) -evalBiListOp _ stack (Call op [ast1, ast2]) = case evalSubParams stack [ast1, ast2] of - Left err -> (Left err, stack) - Right asts -> - maybe - (Left ("No evaluation in one or more parameters of binary operator '" ++ op ++ "'"), stack) - (evalAst stack . Call op) - asts -evalBiListOp _ stack (Call op (_ : _ : _)) = (Left ("Too much parameter for binary operator '" ++ op ++ "'"), stack) -evalBiListOp _ stack (Call op _) = (Left ("Not enough parameter for binary operator '" ++ op ++ "'"), stack) +evalBiListOp _ stack (Call op [ast1, ast2]) = + case evalSubParams stack [ast1, ast2] of + Left err -> (Left err, stack) + Right asts -> + maybe + (Left (noEvaluationError "binary operator '" ++ op ++ "'"), stack) + (evalAst stack . Call op) + asts +evalBiListOp _ stack (Call op (_ : _ : _)) = + (Left (tooMuchParams "binary operator '" ++ op ++ "'"), stack) +evalBiListOp _ stack (Call op _) = + (Left (notEnoughParams "binary operator '" ++ op ++ "'"), stack) evalBiListOp _ stack _ = (Left "Ast isn't a Call", stack) -- | Evaluate the 'Ast' for '!!'. @@ -245,21 +282,21 @@ evalBiListOp _ stack _ = (Left "Ast isn't a Call", stack) -- or a 'String' containing the error message in case of error getElemInAstList :: [ScopeMb] -> Ast -> Either String Ast getElemInAstList _ (Call "!!" [AST.Boolean _, _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [_, AST.Boolean _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [AST.String _, _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [_, AST.String _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [_, AST.List _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [AST.Value _, _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [AST.FunctionValue _ _ Nothing, _]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [_, AST.FunctionValue _ _ Nothing]) = - Left "One or more parameters of binary operator '!!' is invalid" + Left (invalidParamsBiOp "!!") getElemInAstList _ (Call "!!" [AST.List a, AST.Value b]) | b < 0 = Left "Index out of range" | length a > b = Right (a !! b) @@ -268,7 +305,7 @@ getElemInAstList stack (Call "!!" [ast1, ast2]) = case evalSubParams stack [ast1, ast2] of Left err -> Left err Right asts -> case maybe - ( Left "No evaluation in one or more parameters of binary operator '!!'", + ( Left (noEvaluationError "binary operator '!!'"), stack ) (evalAst stack . Call "!!") @@ -277,14 +314,14 @@ getElemInAstList stack (Call "!!" [ast1, ast2]) = (Right ast, _) -> maybe ( Left - "No evaluation in one or more parameters of binary operator '!!'" + (noEvaluationError "binary operator '!!'") ) Right ast getElemInAstList _ (Call "!!" (_ : _ : _)) = - Left "Too much parameter for binary operator '!!'" + Left (tooMuchParams "binary operator '!!'") getElemInAstList _ (Call "!!" _) = - Left "Not enough parameter for binary operator '!!'" + Left (notEnoughParams "binary operator '!!'") getElemInAstList _ _ = Left "Ast isn't a '!!' Call" -- | Evaluate the 'Ast' for a given unary list operator @@ -294,22 +331,29 @@ getElemInAstList _ _ = Left "Ast isn't a '!!' Call" -- Return a tuple containing the new stack post evaluation, and the -- application of the function onto the values inside the given 'Ast' -- or a 'String' containing the error message in case of error -evalUnListOp :: ([Ast] -> Ast) -> [ScopeMb] -> Ast -> (Either String (Maybe Ast), [ScopeMb]) -evalUnListOp _ stack (Call op [AST.Boolean _]) = (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) -evalUnListOp _ stack (Call op [AST.String _]) = (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) -evalUnListOp _ stack (Call op [AST.Value _]) = (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) -evalUnListOp _ stack (Call op [AST.FunctionValue _ _ Nothing]) = (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) +evalUnListOp :: ([Ast] -> Ast) -> [ScopeMb] -> + Ast -> (Either String (Maybe Ast), [ScopeMb]) +evalUnListOp _ stack (Call op [AST.Boolean _]) = + (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) +evalUnListOp _ stack (Call op [AST.String _]) = + (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) +evalUnListOp _ stack (Call op [AST.Value _]) = + (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) +evalUnListOp _ stack (Call op [AST.FunctionValue _ _ Nothing]) = + (Left ("The parameter of unary operator '" ++ op ++ "' is invalid"), stack) evalUnListOp f stack (Call _ [AST.List a]) = (Right (Just (f a)), stack) evalUnListOp _ stack (Call op [ast]) = case evalSubParams stack [ast] of Left err -> (Left err, stack) Right asts -> maybe - (Left ("No evaluation in one or more parameters of binary operator '" ++ op ++ "'"), stack) + (Left (noEvaluationError "binary operator '" ++ op ++ "'"), stack) (evalAst stack . Call op) asts -evalUnListOp _ stack (Call op (_ : _ : _)) = (Left ("Too much parameter for unary operator '" ++ op ++ "'"), stack) -evalUnListOp _ stack (Call op _) = (Left ("Not enough parameter for unary operator '" ++ op ++ "'"), stack) +evalUnListOp _ stack (Call op (_ : _ : _)) = + (Left (tooMuchParams "unary operator '" ++ op ++ "'"), stack) +evalUnListOp _ stack (Call op _) = + (Left (notEnoughParams "unary operator '" ++ op ++ "'"), stack) evalUnListOp _ stack _ = (Left "Ast isn't a Call", stack) -- | Evaluate the list of 'Ast' @@ -328,7 +372,8 @@ astToString :: [ScopeMb] -> Ast -> Either String Ast astToString _ (AST.String str) = Right (AST.String str) astToString _ (AST.Value val) = Right (AST.String (show val)) astToString _ (AST.Boolean bool) = Right (AST.String (show bool)) -astToString _ (AST.FunctionValue _ _ Nothing) = Left "Cannot convert lambda to string" +astToString _ (AST.FunctionValue _ _ Nothing) = + Left "Cannot convert lambda to string" astToString stack ast = case evalAst stack ast of (Left err, _) -> Left err (Right ast', _) -> @@ -337,7 +382,8 @@ astToString stack ast = case evalAst stack ast of (astToString stack) ast' -defineVar :: ([ScopeMb] -> String -> Ast -> [ScopeMb]) -> [ScopeMb] -> String -> Ast -> Either String [ScopeMb] +defineVar :: ([ScopeMb] -> String -> Ast -> [ScopeMb]) + -> [ScopeMb] -> String -> Ast -> Either String [ScopeMb] defineVar f stack name ast = case evalAst stack ast of (Left err, _) -> Left err (Right (Just ast'), _) -> Right (f stack name ast') diff --git a/LobsterLang/src/AstOptimizer.hs b/LobsterLang/src/AstOptimizer.hs new file mode 100644 index 0000000..db69279 --- /dev/null +++ b/LobsterLang/src/AstOptimizer.hs @@ -0,0 +1,164 @@ +{- +-- EPITECH PROJECT, 2024 +-- GLaDOS +-- File description: +-- AstOptimizer +-} + +module AstOptimizer + ( optimizeAst, + AstError(..), + AstOptimised(..), + ) +where + +import AST +import AstEval +import Data.Maybe +import Scope (ScopeMb, getVarInScope) + +data AstError = Error String Ast deriving (Eq, Show) + +data AstOptimised + = Result Ast + | Warning String Ast + deriving (Eq, Show) + +optimizeAst :: [ScopeMb] -> [Ast] -> Bool -> [Either AstError AstOptimised] +optimizeAst stack ((Value v) : xs) inFunc = Right (Result (Value v)) : optimizeAst stack xs inFunc +optimizeAst stack ((Boolean b) : xs) inFunc = Right (Result (Boolean b)) : optimizeAst stack xs inFunc +optimizeAst stack ((String str) : xs) inFunc = Right (Result (String str)) : optimizeAst stack xs inFunc +optimizeAst stack ((List asts) : xs) inFunc = case sequence (optimizeAst stack asts inFunc) of + Left err -> Left err : optimizeAst stack xs inFunc + Right opAst -> Right (Result (List (map fromOptimised opAst))) : optimizeAst stack xs inFunc +optimizeAst stack ((Define n ast) : xs) inFunc = case optimizeAst stack [ast] inFunc of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result opAst)] -> case evalAst stack (Define n opAst) of + (Right _, stack') -> Right (Result (Define n opAst)) : optimizeAst stack' xs inFunc + (Left ('S':'y':'m':'b':'o':'l':' ':'\'':xs'), _) + | inFunc -> Right (Result (Define n opAst)) : optimizeAst stack xs inFunc + | otherwise -> Left (Error ('S':'y':'m':'b':'o':'l':' ':'\'':xs') (Define n opAst)) : optimizeAst stack xs inFunc + (Left err, _) -> Left (Error err (Define n opAst)) : optimizeAst stack xs inFunc + [Right (Warning mes opAst)] -> Right (Warning mes (Define n opAst)) : optimizeAst stack xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (Define n ast)) : optimizeAst stack xs inFunc +optimizeAst stack ((Symbol s Nothing) : xs) inFunc + | inFunc = Right (Result (Symbol s Nothing)) : optimizeAst stack xs inFunc + | otherwise = case getVarInScope stack s of + Nothing -> Left (Error ("Symbol '" ++ s ++ "' doesn't exist in the current or global scope") (Symbol s Nothing)) : optimizeAst stack xs inFunc + Just _ -> Right (Result (Symbol s Nothing)) : optimizeAst stack xs inFunc +optimizeAst stack ((Symbol s (Just asts)) : xs) inFunc + | foldr ((&&) . isUnoptimizable) True asts = case evalAst stack (Symbol s (Just asts)) of + (Left ('S':'y':'m':'b':'o':'l':' ':'\'':xs'), _) + | inFunc -> Right (Result (Symbol s (Just asts))) : optimizeAst stack xs inFunc + | otherwise -> Left (Error ('S':'y':'m':'b':'o':'l':' ':'\'':xs') (Symbol s (Just asts))) : optimizeAst stack xs inFunc + (Left err, _) -> Left (Error err (Symbol s (Just asts))) : optimizeAst stack xs inFunc + (Right (Just _), stack') -> Right (Result (Symbol s (Just asts))) : optimizeAst stack' xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (Symbol s (Just asts))) : optimizeAst stack xs inFunc + | otherwise = case sequence (optimizeAst stack asts inFunc) of + Left err -> Left err : optimizeAst stack xs inFunc + Right opAst -> optimizeAst stack (Symbol s (Just (map fromOptimised opAst)):xs) inFunc +optimizeAst stack ((Call op asts) : xs) inFunc + | foldr ((&&) . isUnoptimizable) True asts + && foldr ((&&) . isValue) True asts = case evalAst stack (Call op asts) of + (Left ('S':'y':'m':'b':'o':'l':' ':'\'':xs'), _) + | inFunc -> Right (Result (Call op asts)) : optimizeAst stack xs inFunc + | otherwise -> Left (Error ('S':'y':'m':'b':'o':'l':' ':'\'':xs') (Call op asts)) : optimizeAst stack xs inFunc + (Left err, _) -> Left (Error err (Call op asts)) : optimizeAst stack xs inFunc + (Right (Just ast), stack') -> Right (Result ast) : optimizeAst stack' xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (Call op asts)) : optimizeAst stack xs inFunc + | foldr ((&&) . isUnoptimizable) True asts = case evalAst stack (Call op asts) of + (Left ('S':'y':'m':'b':'o':'l':' ':'\'':xs'), _) + | inFunc -> Right (Result (Call op asts)) : optimizeAst stack xs inFunc + | otherwise -> Left (Error ('S':'y':'m':'b':'o':'l':' ':'\'':xs') (Call op asts)) : optimizeAst stack xs inFunc + (Left err, _) -> Left (Error err (Call op asts)) : optimizeAst stack xs inFunc + (Right (Just _), stack') -> Right (Result (Call op asts)) : optimizeAst stack' xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (Call op asts)) : optimizeAst stack xs inFunc + | otherwise = case sequence (optimizeAst stack asts inFunc) of + Left err -> Left err : optimizeAst stack xs inFunc + Right asts' -> optimizeAst stack (Call op (map fromOptimised asts') : xs) inFunc +optimizeAst stack ((Cond condAst trueAst mFalseAst) : xs) inFunc + | not (isUnoptimizable condAst) = case optimizeAst stack [condAst] inFunc of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result condAst')] -> optimizeAst stack (Cond condAst' trueAst mFalseAst : xs) inFunc + [Right (Warning _ condAst')] -> optimizeAst stack (Cond condAst' trueAst mFalseAst : xs) inFunc + _ -> Right (Warning "This situation shouldn't happen" (Cond condAst trueAst mFalseAst)) : optimizeAst stack xs inFunc + | not (isUnoptimizable trueAst) = case optimizeAst stack [trueAst] inFunc of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result trueAst')] -> optimizeAst stack (Cond condAst trueAst' mFalseAst : xs) inFunc + [Right (Warning _ trueAst')] -> optimizeAst stack (Cond condAst trueAst' mFalseAst : xs) inFunc + _ -> Right (Warning "This situation shouldn't happen" (Cond condAst trueAst mFalseAst)) : optimizeAst stack xs inFunc + | isJust mFalseAst && not (isUnoptimizable (fromJust mFalseAst)) = case optimizeAst stack [fromJust mFalseAst] inFunc of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result falseAst')] -> optimizeAst stack (Cond condAst trueAst (Just falseAst') : xs) inFunc + [Right (Warning _ falseAst')] -> optimizeAst stack (Cond condAst trueAst (Just falseAst') : xs) inFunc + _ -> Right (Warning "This situation shouldn't happen" (Cond condAst trueAst mFalseAst)) : optimizeAst stack xs inFunc + | otherwise = case condAst of + Boolean True -> Right (Warning "Condition is always true" trueAst) : optimizeAst stack xs inFunc + Boolean False -> + Right + ( Warning + "Condition is always false" + (fromMaybe (Cond condAst trueAst mFalseAst) mFalseAst) + ) + : optimizeAst stack xs inFunc + _ -> Right (Result (Cond condAst trueAst mFalseAst)) : optimizeAst stack xs inFunc +optimizeAst stack ((FunctionValue params ast Nothing) : xs) inFunc = case optimizeAst stack [ast] True of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result ast')] -> Right (Result (FunctionValue params ast' Nothing)) : optimizeAst stack xs inFunc + [Right (Warning mes ast')] -> Right (Warning mes (FunctionValue params ast' Nothing)) : optimizeAst stack xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (FunctionValue params ast Nothing)) : optimizeAst stack xs inFunc +optimizeAst stack ((FunctionValue params ast (Just asts)) : xs) inFunc + | not (isUnoptimizable ast) = case optimizeAst stack [ast] True of + [Left err] -> Left err : optimizeAst stack xs inFunc + [Right (Result ast')] -> optimizeAst stack (FunctionValue params ast' (Just asts) : xs) inFunc + [Right (Warning _ ast')] -> optimizeAst stack (FunctionValue params ast' (Just asts) : xs) inFunc + _ -> Right (Warning "This situation shouldn't happen" (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + | not (foldr ((&&) . isUnoptimizable) True asts) = case sequence (optimizeAst stack asts inFunc) of + Left err -> Left err : optimizeAst stack xs inFunc + Right asts' -> optimizeAst stack (FunctionValue params ast (Just (map fromOptimised asts')) : xs) inFunc + | length params > length asts = case evalAst stack (FunctionValue params ast (Just asts)) of + (Left err, _) -> Left (Error err (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + (Right (Just ast'), stack') -> Right (Result ast') : optimizeAst stack' xs inFunc + (Right Nothing, _) -> Right (Warning "This situation shouldn't happen" (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + | otherwise = case evalAst stack (FunctionValue params ast (Just asts)) of + (Left ('S':'y':'m':'b':'o':'l':' ':'\'':xs'), _) + | inFunc -> Right (Result (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + | otherwise -> Left (Error ('S':'y':'m':'b':'o':'l':' ':'\'':xs') (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + (Left err, _) -> Left (Error err (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc + (Right (Just _), stack') -> Right (Result (FunctionValue params ast (Just asts))) : optimizeAst stack' xs inFunc + _ -> Right (Warning "This situation shouldn't happen" (FunctionValue params ast (Just asts))) : optimizeAst stack xs inFunc +optimizeAst _ [] _ = [] + +isUnoptimizable :: Ast -> Bool +isUnoptimizable (Define _ ast) = isUnoptimizable ast +isUnoptimizable (Value _) = True +isUnoptimizable (Boolean _) = True +isUnoptimizable (String _) = True +isUnoptimizable (List asts) = foldr ((&&) . isUnoptimizable) True asts +isUnoptimizable (Call _ asts) = + foldr ((&&) . isUnoptimizable) True asts + && not (foldr ((&&) . isValue) True asts) +isUnoptimizable (Symbol _ Nothing) = True +isUnoptimizable (Symbol _ (Just asts)) = foldr ((&&) . isUnoptimizable) True asts +isUnoptimizable (FunctionValue _ ast Nothing) = isUnoptimizable ast +isUnoptimizable (FunctionValue params ast (Just asts)) = + isUnoptimizable ast + && foldr ((&&) . isUnoptimizable) True asts + && length params > length asts +isUnoptimizable (Cond (Boolean _) _ _) = False +isUnoptimizable (Cond condAst bodyAst Nothing) = + isUnoptimizable condAst && isUnoptimizable bodyAst +isUnoptimizable (Cond condAst bodyAst (Just elseAst)) = + isUnoptimizable condAst && isUnoptimizable bodyAst && isUnoptimizable elseAst + +isValue :: Ast -> Bool +isValue (Value _) = True +isValue (Boolean _) = True +isValue (String _) = True +isValue (List _) = True +isValue (FunctionValue _ _ Nothing) = True +isValue _ = False + +fromOptimised :: AstOptimised -> Ast +fromOptimised (Warning _ ast) = ast +fromOptimised (Result ast) = ast diff --git a/LobsterLang/test/AstEvalSpec.hs b/LobsterLang/test/AstEvalSpec.hs index 628da23..a28be89 100644 --- a/LobsterLang/test/AstEvalSpec.hs +++ b/LobsterLang/test/AstEvalSpec.hs @@ -63,9 +63,9 @@ spec = do it "Check invalid value binary operation (wrong type 2)" $ do evalBiValOp (+) [] (Call "+" [AST.Value 8, AST.Boolean False]) `shouldBe` (Left "One or more parameters of binary operator '+' is invalid", []) it "Check invalid value binary operation (not enough ast parameters)" $ do - evalBiValOp (+) [] (Call "+" [AST.Value 8]) `shouldBe` (Left "Not enough parameter for binary operator '+'", []) + evalBiValOp (+) [] (Call "+" [AST.Value 8]) `shouldBe` (Left "Not enough parameters for binary operator '+'", []) it "Check invalid value binary operation (too much ast parameters)" $ do - evalBiValOp (+) [] (Call "+" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameter for binary operator '+'", []) + evalBiValOp (+) [] (Call "+" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameters for binary operator '+'", []) describe "Value comparison evaluation tests" $ do -- Value comparison operators it "Check valid operation ==" $ do @@ -85,9 +85,9 @@ spec = do it "Check invalid value comparison binary operation (wrong type 2)" $ do evalBiCompValOp (==) [] (Call "==" [AST.Value 8, AST.Boolean False]) `shouldBe` (Left "One or more parameters of binary operator '==' is invalid", []) it "Check invalid value comparison binary operation (not enough ast parameters)" $ do - evalBiCompValOp (==) [] (Call "==" [AST.Value 8]) `shouldBe` (Left "Not enough parameter for binary operator '=='", []) + evalBiCompValOp (==) [] (Call "==" [AST.Value 8]) `shouldBe` (Left "Not enough parameters for binary operator '=='", []) it "Check invalid value comparison binary operation (too much ast parameters)" $ do - evalBiCompValOp (==) [] (Call "==" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameter for binary operator '=='", []) + evalBiCompValOp (==) [] (Call "==" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameters for binary operator '=='", []) describe "Boolean operators evaluation tests" $ do -- Boolean operators it "Check valid operation &&" $ do @@ -108,18 +108,24 @@ spec = do evalAst [] (Call "!" [AST.Boolean False]) `shouldBe` (Right (Just (AST.Boolean True)), []) it "Check valid operation ! (not) 2" $ do evalAst [] (Call "!" [AST.Boolean True]) `shouldBe` (Right (Just (AST.Boolean False)), []) + it "Check valid operation ! (not) with symbol" $ do + evalAst [Variable "a" (Boolean True) 0] (Call "!" [AST.Symbol "a" Nothing]) `shouldBe` (Right (Just (AST.Boolean False)), [Variable "a" (Boolean True) 0]) + it "Check valid operation ! (not) with eval" $ do + evalAst [] (Call "!" [Call "&&" [AST.Boolean True, AST.Boolean True]]) `shouldBe` (Right (Just (AST.Boolean False)), []) it "Check invalid operation ! (not)" $ do evalAst [] (Call "!" [AST.Boolean True, AST.Boolean False]) `shouldBe` (Left "Invalid number of parameter for unary operator '!'", []) it "Check invalid operation ! (not) 2" $ do evalAst [] (Call "!" [AST.Value 5]) `shouldBe` (Left "Parameter of unary operator '!' isn't a boolean", []) + it "Check invalid operation ! (not) with symbol" $ do + evalAst [Variable "a" (Value 8) 0] (Call "!" [Symbol "a" Nothing]) `shouldBe` (Left "Parameter of unary operator '!' isn't a boolean", [Variable "a" (Value 8) 0]) it "Check invalid value comparison binary operation (wrong type)" $ do evalBiBoolOp (&&) [] (Call "&&" [AST.Boolean True, AST.Value 8]) `shouldBe` (Left "One or more parameters of binary operator '&&' is invalid", []) it "Check invalid value comparison binary operation (wrong type 2)" $ do evalBiBoolOp (&&) [] (Call "&&" [AST.Value 8, AST.Boolean False]) `shouldBe` (Left "One or more parameters of binary operator '&&' is invalid", []) it "Check invalid value comparison binary operation (not enough ast parameters)" $ do - evalBiBoolOp (&&) [] (Call "&&" [AST.Value 8]) `shouldBe` (Left "Not enough parameter for binary operator '&&'", []) + evalBiBoolOp (&&) [] (Call "&&" [AST.Value 8]) `shouldBe` (Left "Not enough parameters for binary operator '&&'", []) it "Check invalid value comparison binary operation (too much ast parameters)" $ do - evalBiBoolOp (&&) [] (Call "&&" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameter for binary operator '&&'", []) + evalBiBoolOp (&&) [] (Call "&&" [AST.Value 8, AST.Value 9, AST.Value 3]) `shouldBe` (Left "Too much parameters for binary operator '&&'", []) describe "Define and function evaluation tests" $ do -- Check Define it "Check unknown variable" $ do @@ -134,6 +140,8 @@ spec = do evalAst [Variable "foo" (AST.Value 1) 0, ScopeBegin 0] (AST.Symbol "foo" Nothing) `shouldBe` (Right (Just (AST.Value 1)), [Variable "foo" (AST.Value 1) 0, ScopeBegin 0]) it "Check variable usage 2" $ do evalAst [Variable "bar" (Call "+" [AST.Value 1, AST.Value 5]) 0, ScopeBegin 0] (AST.Symbol "bar" Nothing) `shouldBe` (Right (Just (AST.Value 6)), [Variable "bar" (Call "+" [AST.Value 1, AST.Value 5]) 0, ScopeBegin 0]) + it "Check invalid variable usage" $ do + evalAst [Variable "bar" (Call "+" [AST.Value 1, AST.Value 5]) 0, ScopeBegin 0] (AST.Symbol "bar" (Just [Value 1])) `shouldBe` (Left "Symbol 'bar' isn't a function", [Variable "bar" (Call "+" [AST.Value 1, AST.Value 5]) 0, ScopeBegin 0]) it "Check invalid function" $ do evalAst [Variable "foo" (FunctionValue ["x"] (Call "+" [AST.Symbol "x" Nothing, AST.Boolean True]) Nothing) 0, ScopeBegin 0] (Symbol "foo" (Just [AST.Value 5])) `shouldBe` (Left "One or more parameters of binary operator '+' is invalid", [Variable "foo" (FunctionValue ["x"] (Call "+" [AST.Symbol "x" Nothing, AST.Boolean True]) Nothing) 0, ScopeBegin 0]) it "Check basic function definition" $ do diff --git a/LobsterLang/test/AstOptimizerSpec.hs b/LobsterLang/test/AstOptimizerSpec.hs new file mode 100644 index 0000000..2557ee2 --- /dev/null +++ b/LobsterLang/test/AstOptimizerSpec.hs @@ -0,0 +1,161 @@ +{- +-- EPITECH PROJECT, 2024 +-- GLaDOS +-- File description: +-- AstOptimizerSpec +-} + +module AstOptimizerSpec where + +import Test.Hspec +import AST +import AstOptimizer +import Scope + +spec :: Spec +spec = do + describe "Value Ast optimization tests" $ do + it "Basic Value" $ do + optimizeAst [] [Value 5] False `shouldBe` [Right (Result (Value 5))] + it "Basic Value (multiple)" $ do + optimizeAst [] [Value 5, Value 8] False `shouldBe` [Right (Result (Value 5)), Right (Result (Value 8))] + describe "Boolean Ast optimization tests" $ do + it "Basic Boolean" $ do + optimizeAst [] [Boolean True] False `shouldBe` [Right (Result (Boolean True))] + it "Basic Boolean (multiple)" $ do + optimizeAst [] [Boolean True, Boolean False] False `shouldBe` [Right (Result (Boolean True)), Right (Result (Boolean False))] + describe "String Ast optimization tests" $ do + it "Basic String" $ do + optimizeAst [] [String "blegh"] False `shouldBe` [Right (Result (String "blegh"))] + it "Basic String (multiple)" $ do + optimizeAst [] [String "blegh", String "aaaaa"] False `shouldBe` [Right (Result (String "blegh")), Right (Result (String "aaaaa"))] + describe "List Ast optimization tests" $ do + it "Empty list" $ do + optimizeAst [] [List []] False `shouldBe` [Right (Result (List []))] + it "Optimizable list" $ do + optimizeAst [] [List [Call "+" [Value 1, Value 2], Value 5]] False `shouldBe` [Right (Result (List [Value 3, Value 5]))] + it "Unoptimizable list" $ do + optimizeAst [] [List [Value 5, Boolean True, Value 9, String "vzb"]] False `shouldBe` [Right (Result (List [Value 5, Boolean True, Value 9, String "vzb"]))] + it "Unoptimizable list 2" $ do + optimizeAst [Variable "a" (Value 5) 0] [List [Value 5, Symbol "a" Nothing]] False `shouldBe` [Right (Result (List [Value 5, Symbol "a" Nothing]))] + it "Unoptimizable list error" $ do + optimizeAst [] [List [Value 5, Symbol "a" Nothing]] False `shouldBe` [Left (Error "Symbol 'a' doesn't exist in the current or global scope" (Symbol "a" Nothing))] + describe "Define Ast optimization tests" $ do + it "Unoptimizable Define" $ do + optimizeAst [] [Define "a" (Value 5)] False `shouldBe` [Right (Result (Define "a" (Value 5)))] + it "Optimizable Define" $ do + optimizeAst [] [Define "a" (Call "+" [Value 5, Value 5])] False `shouldBe` [Right (Result (Define "a" (Value 10)))] + it "Error Define" $ do + optimizeAst [] [Define "a" (Call "+" [Value 5])] False `shouldBe` [Left (Error "Not enough parameters for binary operator '+'" (Call "+" [Value 5]))] + it "Error Define 2" $ do + optimizeAst [] [Define "a" (Define "b" (Value 2))] False `shouldBe` [Left (Error "Cannot define with no value" (Define "a" (Define "b" (Value 2))))] + it "Error Define 3" $ do + optimizeAst [] [Define "a" (Symbol "b" Nothing)] False `shouldBe` [Left (Error "Symbol 'b' doesn't exist in the current or global scope" (Symbol "b" Nothing))] + it "Define from symbol in function" $ do + optimizeAst [] [Define "a" (Symbol "b" Nothing)] True `shouldBe` [Right (Result (Define "a" (Symbol "b" Nothing)))] + describe "Symbol Ast optimization tests" $ do + it "Simple symbol (in function)" $ do + optimizeAst [] [Symbol "a" Nothing] True `shouldBe` [Right (Result (Symbol "a" Nothing))] + it "Simple symbol (out of function)" $ do + optimizeAst [Variable "a" (Value 5) 0] [Symbol "a" Nothing] False `shouldBe` [Right (Result (Symbol "a" Nothing))] + it "Error doesn't exist symbol (out of function)" $ do + optimizeAst [] [Symbol "a" Nothing] False `shouldBe` [Left (Error "Symbol 'a' doesn't exist in the current or global scope" (Symbol "a" Nothing))] + it "Error function symbol (in function) with unoptimizable params (doesn't exist)" $ do + optimizeAst [] [Symbol "a" (Just [Value 5])] True `shouldBe` [Right (Result (Symbol "a" (Just [Value 5])))] + it "Error function symbol (out of function) with unoptimizable params" $ do + optimizeAst [] [Symbol "a" (Just [Value 5])] False `shouldBe` [Left (Error "Symbol 'a' doesn't exist in the current or global scope" (Symbol "a" (Just [Value 5])))] + it "Simple function symbol (out of function) with unoptimizable params" $ do + optimizeAst [Variable "a" (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 1]) Nothing) 0] [Symbol "a" (Just [Value 5])] False `shouldBe` [Right (Result (Symbol "a" (Just [Value 5])))] + it "Error function symbol (out of function) with unoptimizable params" $ do + optimizeAst [Variable "a" (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 1]) Nothing) 0] [Symbol "a" (Just [Value 5, Value 6])] False `shouldBe` [Left (Error "Expression takes 1 parameters, got 2" (Symbol "a" (Just [Value 5, Value 6])))] + it "Simple function symbol (out of function) with optimizable params" $ do + optimizeAst [Variable "a" (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 1]) Nothing) 0] [Symbol "a" (Just [Call "+" [Value 1, Value 5]])] False `shouldBe` [Right (Result (Symbol "a" (Just [Value 6])))] + it "Error function symbol (out of function) with optimizable params" $ do + optimizeAst [Variable "a" (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 1]) Nothing) 0] [Symbol "a" (Just [Call "+" [Value 1, Boolean True]])] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [Value 1, Boolean True]))] + describe "Operator Ast optimization tests" $ do + it "Optimize +" $ do + optimizeAst [] [Call "+" [Value 5, Value 8]] False `shouldBe` [Right (Result (Value 13))] + it "Optimize -" $ do + optimizeAst [] [Call "-" [Value 5, Value 8]] False `shouldBe` [Right (Result (Value (-3)))] + it "Optimize &&" $ do + optimizeAst [] [Call "&&" [Boolean True, Boolean False]] False `shouldBe` [Right (Result (Boolean False))] + it "Optimize !" $ do + optimizeAst [] [Call "!" [Boolean True]] False `shouldBe` [Right (Result (Boolean False))] + it "Optimize @" $ do + optimizeAst [] [Call "@" [Value 56]] False `shouldBe` [Right (Result (String "56"))] + it "Optimize nested operators" $ do + optimizeAst [] [Call "*" [Call "+" [Value 8, Value 2], Call "-" [Value 9, Call "%" [Value 126, Value 10]]]] False `shouldBe` [Right (Result (Value 30))] + it "Optimize + with symbol" $ do + optimizeAst [Variable "a" (Value 5) 0] [Call "+" [Symbol "a" Nothing, Value 8]] False `shouldBe` [Right (Result (Call "+" [Symbol "a" Nothing, Value 8]))] + it "Error not Value" $ do + optimizeAst [] [Call "+" [List [Value 8, Value 9], Value 8]] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [List [Value 8, Value 9], Value 8]))] + it "Error symbol doesn't exist" $ do + optimizeAst [] [Call "+" [Symbol "a" Nothing, Value 8]] False `shouldBe` [Left (Error "Symbol 'a' doesn't exist in the current or global scope" (Call "+" [Symbol "a" Nothing, Value 8]))] + describe "Cond Ast Optimizations" $ do + it "Optimize condition in Cond" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Call "||" [Boolean True, Boolean False], Symbol "a" Nothing]) (Value 1) Nothing] False `shouldBe` [Right (Result (Cond (Call "&&" [Boolean True, Symbol "a" Nothing]) (Value 1) Nothing))] + it "Optimize true ast in Cond" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Call "+" [Value 5, Value 8]) Nothing] False `shouldBe` [Right (Result (Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 13) Nothing))] + it "Optimize false ast in Cond" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 1) (Just (Call "+" [Value 5, Value 8]))] False `shouldBe` [Right (Result (Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 1) (Just (Value 13))))] + it "Optimize condition in Cond error" $ do + optimizeAst [] [Cond (Call "&&" [Symbol "a" Nothing, Call "||" [Boolean True, Boolean False]]) (Value 1) Nothing] False `shouldBe` [Left (Error "Symbol 'a' doesn't exist in the current or global scope" (Symbol "a" Nothing))] + it "Optimize true ast in Cond error" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Call "+" [String "bleg", Value 8]) Nothing] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [String "bleg", Value 8]))] + it "Optimize false ast in Cond error" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 1) (Just (Call "+" [Value 5, List [String "bleg"]]))] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [Value 5, List [String "bleg"]]))] + it "Optimize condition in Cond warning" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Cond (Boolean True) (Symbol "a" Nothing) Nothing) (Value 1) Nothing] False `shouldBe` [Right (Result (Cond (Symbol "a" Nothing) (Value 1) Nothing))] + it "Optimize true ast in Cond warning" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Cond (Boolean True) (Symbol "a" Nothing) Nothing) Nothing] False `shouldBe` [Right (Result (Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Symbol "a" Nothing) Nothing))] + it "Optimize false ast in Cond warning" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 1) (Just (Cond (Boolean True) (Symbol "a" Nothing) Nothing))] False `shouldBe` [Right (Result (Cond (Call "&&" [Symbol "a" Nothing, Boolean True]) (Value 1) (Just (Symbol "a" Nothing))))] + it "Optimize always true" $ do + optimizeAst [] [Cond (Boolean True) (Value 1) (Just (Value 8))] False `shouldBe` [Right (Warning "Condition is always true" (Value 1))] + it "Optimize always false" $ do + optimizeAst [] [Cond (Boolean False) (Value 1) (Just (Value 8))] False `shouldBe` [Right (Warning "Condition is always false" (Value 8))] + it "Optimize always false (no else)" $ do + optimizeAst [] [Cond (Boolean False) (Value 1) Nothing] False `shouldBe` [Right (Warning "Condition is always false" (Cond (Boolean False) (Value 1) Nothing))] + it "Unoptimizable Cond" $ do + optimizeAst [Variable "a" (Boolean True) 0] [Cond (Call "&&" [Boolean True, Symbol "a" Nothing]) (Value 1) Nothing] False `shouldBe` [Right (Result (Cond (Call "&&" [Boolean True, Symbol "a" Nothing]) (Value 1) Nothing))] + describe "FunctionValue Ast optimization tests" $ do + -- without params + it "Optimize inner ast in FunctionValue error" $ do + optimizeAst [] [FunctionValue ["x"] (Call "$" [Define "a" (Call "+" [Boolean True, Value 1]), Call "+" [Symbol "a" Nothing, Value 1]]) Nothing] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [Boolean True, Value 1]))] + it "Optimize inner ast in FunctionValue" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Call "+" [Value 1, Value 1]]) Nothing] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) Nothing))] + it "Optimize inner ast in FunctionValue warning" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Cond (Boolean True) (Value 2) Nothing]) Nothing] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) Nothing))] + -- with params (inner) + it "Optimize inner ast in FunctionValue with params" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Call "+" [Value 1, Value 1]]) (Just [Value 2])] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Value 2])))] + it "Optimize inner ast in FunctionValue with params warning" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Cond (Boolean True) (Value 2) Nothing]) (Just [Value 2])] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Value 2])))] + it "Optimize inner ast in FunctionValue with params error" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Call "+" [Boolean True, Value 1]]) (Just [Value 2])] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [Boolean True, Value 1]))] + -- with params (params) + it "Empty params" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [])] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) Nothing))] + it "Unoptimizable params error" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Call "+" [Value 1, Boolean True]])] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (Call "+" [Value 1, Boolean True]))] + it "Optimizable params" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Call "+" [Value 1, Value 1]])] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Value 2])))] + it "Unoptimizable params" $ do + optimizeAst [] [FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Value 2])] False `shouldBe` [Right (Result (FunctionValue ["x"] (Call "+" [Symbol "x" Nothing, Value 2]) (Just [Value 2])))] + -- currying + it "Currying" $ do + optimizeAst [] [FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Value 2])] False `shouldBe` [Right (Result (FunctionValue ["b"] (Call "$" [Define "a" (Value 2), Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]]) Nothing))] + -- Check valaidity of func + it "Unoptimizable func with params" $ do + optimizeAst [] [FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Value 2, Value 3])] False `shouldBe` [Right (Result (FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Value 2, Value 3])))] + it "Unoptimizable func with params (symbol don't exist in func)" $ do + optimizeAst [] [FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Symbol "c" Nothing, Value 3])] True `shouldBe` [Right (Result (FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Symbol "c" Nothing, Value 3])))] + it "Unoptimizable func with params (symbol don't exist out of func)" $ do + optimizeAst [] [FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Symbol "c" Nothing, Value 3])] False `shouldBe` [Left (Error "Symbol 'c' doesn't exist in the current or global scope" (FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Symbol "c" Nothing, Value 3])))] + it "Unoptimizable func with params error" $ do + optimizeAst [] [FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Value 2, Boolean True])] False `shouldBe` [Left (Error "One or more parameters of binary operator '+' is invalid" (FunctionValue ["a", "b"] (Call "+" [Symbol "a" Nothing, Symbol "b" Nothing]) (Just [Value 2, Boolean True])))] + describe "Advanced Ast optimization tests" $ do + it "Call then symbol" $ do + optimizeAst [Variable "a" (Value 5) 0] [Call "-" [Value 5, Value 8], Call "+" [Symbol "a" Nothing, Value 8]] False `shouldBe` [Right (Result (Value (-3))), Right (Result (Call "+" [Symbol "a" Nothing, Value 8]))] + it "Define then call" $ do + optimizeAst [] [Define "a" (Value 5), Call "-" [Symbol "a" Nothing, Value 8]] False `shouldBe` [Right (Result (Define "a" (Value 5))), Right (Result (Call "-" [Symbol "a" Nothing, Value 8]))]