Skip to content

Commit

Permalink
Merge pull request #49 from bananu7/operators-enum
Browse files Browse the repository at this point in the history
Operators enum.
Closes #5.
Closes #40.
Closes #44.
  • Loading branch information
bananu7 authored Sep 23, 2018
2 parents d4a5cc6 + e97abb0 commit f9901ca
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 184 deletions.
38 changes: 19 additions & 19 deletions Test/TestParser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ spec = do
it "should parse integers" $ parse "return 5" `shouldBe` Block [Return [Number 5.0]]
it "should parse floats" $ parse "return 4.2" `shouldBe` Block [Return [Number 4.2]]
it "should parse negative numbers" $ do
parse "return -3" `shouldBe` Block [Return [UnOp "-" (Number 3.0)]]
parse "return -2.9" `shouldBe` Block [Return [UnOp "-" (Number 2.9)]]
parse "return -3" `shouldBe` Block [Return [UnOp OpUnaryMinus (Number 3.0)]]
parse "return -2.9" `shouldBe` Block [Return [UnOp OpUnaryMinus (Number 2.9)]]
it "should parse strings" $ parse "return \"test\"" `shouldSatisfy` (\(Block [Return [StringLiteral _ s]]) -> s == "test")

describe "tables" $ do
Expand Down Expand Up @@ -51,16 +51,16 @@ spec = do

describe "should parse length operator" $ do
it "on string literal (#\"abc\"" $
parse "return #\"abc\"" `shouldBe` Block [Return [UnOp "#" (StringLiteral (pos 9) "abc")]]
parse "return #\"abc\"" `shouldBe` Block [Return [UnOp OpLength (StringLiteral (pos 9) "abc")]]
it "on variables" $
parse "return #x" `shouldBe` Block [Return [UnOp "#" (Var "x")]]
parse "return #x" `shouldBe` Block [Return [UnOp OpLength (Var "x")]]
it "on table literals" $
parse "return #{1,2,3}" `shouldBe` Block [Return [UnOp "#" (TableCons [(Nothing, Number 1.0), (Nothing, Number 2.0), (Nothing, Number 3.0)])]]
parse "return #{1,2,3}" `shouldBe` Block [Return [UnOp OpLength (TableCons [(Nothing, Number 1.0), (Nothing, Number 2.0), (Nothing, Number 3.0)])]]
it "on function calls" $ do
parse "return #f()" `shouldBe` Block [Return [UnOp "#" (Call (Var "f") [])]]
parse "return #f.g()" `shouldBe` Block [Return [UnOp "#" (Call (FieldRef (Var "f") (StringLiteral (pos 11) "g")) [])]]
parse "return #f()" `shouldBe` Block [Return [UnOp OpLength (Call (Var "f") [])]]
parse "return #f.g()" `shouldBe` Block [Return [UnOp OpLength (Call (FieldRef (Var "f") (StringLiteral (pos 11) "g")) [])]]
it "mixed with concat" $
parse "return #x..y" `shouldBe` Block [Return [BinOp ".." (UnOp "#" (Var "x")) (Var "y")]]
parse "return #x..y" `shouldBe` Block [Return [BinOp OpConcat (UnOp OpLength (Var "x")) (Var "y")]]

describe "should parse multiple assignments" $ do
it "equal arity of lhs and rhs" $
Expand All @@ -81,31 +81,31 @@ spec = do

it "nested table (t[i][u] = v)" $
parse "t[i][u] = v" `shouldBe` Block [Assignment [LFieldRef (FieldRef (Var "t") (Var "i")) (Var "u")] [Var "v"]]

{-}
describe "should parse simple comparisons" $ do
mapM_ (\op -> it op $ (parse $ "return 1 " ++ op ++ " 2")
mapM_ (\op -> it op $ (parse $ "return 1 " ++ show op ++ " 2")
`shouldBe`
(Block [Return [BinOp op (Number 1) (Number 2)]])
)
["==", "~=", ">", "<", ">=", "<="]
[OpEqual, OpNotEqual, OpGreater, OpLess, OpGE, OpLE]-}

describe "should parse concatenation operator (..)" $ do
it "simple usage" $ parse "return a .. b" `shouldBe` (Block [Return [BinOp ".." (Var "a") (Var "b")]])
it "simple usage" $ parse "return a .. b" `shouldBe` (Block [Return [BinOp OpConcat (Var "a") (Var "b")]])
it "mixed with other dots" $ parse "return a.x..b.y" `shouldBe`
(Block [Return [BinOp ".."
(Block [Return [BinOp OpConcat
(FieldRef (Var "a") (StringLiteral (pos 10) "x"))
(FieldRef (Var "b") (StringLiteral (pos 15) "y"))
]])
it "associativity" $ parse "return a .. b .. c" `shouldBe`
(Block [Return [BinOp ".." (Var "a") (BinOp ".." (Var "b") (Var "c"))]])
(Block [Return [BinOp OpConcat (Var "a") (BinOp OpConcat (Var "b") (Var "c"))]])

describe "should parse logical operators" $ do
it "not" $
parse "return not x" `shouldBe` (Block [Return [UnOp "not" (Var "x")]])
parse "return not x" `shouldBe` (Block [Return [UnOp OpNot (Var "x")]])
it "or" $
parse "return a or b" `shouldBe` (Block [Return [BinOp "or" (Var "a") (Var "b")]])
parse "return a or b" `shouldBe` (Block [Return [BinOp OpOr (Var "a") (Var "b")]])
it "and" $
parse "return a and b" `shouldBe` (Block [Return [BinOp "and" (Var "a") (Var "b")]])
parse "return a and b" `shouldBe` (Block [Return [BinOp OpAnd (Var "a") (Var "b")]])

describe "should parse return statements" $ do
it "boolean" $
Expand Down Expand Up @@ -174,7 +174,7 @@ spec = do
parse "while true do end" `shouldBe` Block [While (Bool True) (Block [])]
parse "while x < 1 do break end" `shouldBe`
Block [
While (BinOp "<" (Var "x") (Number 1.0)) (Block [
While (BinOp OpLess (Var "x") (Number 1.0)) (Block [
Break
])
]
Expand All @@ -183,7 +183,7 @@ spec = do
parse "for x = 1,2 do end" `shouldBe` Block [For ["x"] (ForNum (Number 1.0) (Number 2.0) Nothing) (Block [])]
parse "for x = 5,1,-1 do break end" `shouldBe`
Block [
For ["x"] (ForNum (Number 5.0) (Number 1.0) (Just $ UnOp "-" (Number 1.0))) (Block [
For ["x"] (ForNum (Number 5.0) (Number 1.0) (Just $ UnOp OpUnaryMinus (Number 1.0))) (Block [
Break
])
]
Expand Down
26 changes: 24 additions & 2 deletions src/Turnip/AST.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ data LValue = LVar Name
| LFieldRef Expr Expr
deriving (Show, Eq)

data UnaryOperator = OpLength
| OpNot
| OpUnaryMinus
deriving (Show, Eq)

data BinaryOperator = OpRaise
| OpMult
| OpDivide
| OpModulo
| OpPlus
| OpMinus
| OpConcat
| OpLE
| OpGE
| OpLess
| OpGreater
| OpEqual
| OpNotEqual
| OpAnd
| OpOr
deriving (Show, Eq)

data Expr = Number Double
| StringLiteral SourcePos String
| Bool Bool
Expand All @@ -18,8 +40,8 @@ data Expr = Number Double
| Call Expr [Expr]
| MemberCall Expr Name [Expr]
| TableCons [(Maybe Expr, Expr)]
| UnOp String Expr
| BinOp String Expr Expr
| UnOp UnaryOperator Expr
| BinOp BinaryOperator Expr Expr
| FieldRef Expr Expr
| Var Name
| Lambda [Name] HasEllipsis Block
Expand Down
153 changes: 148 additions & 5 deletions src/Turnip/Eval/Eval.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Control.Monad.Except
import Control.Monad.State
import qualified Data.Map as Map
import Debug.Trace
import Data.Maybe (isJust)

padWithNils :: Int -> [Value] -> [Value]
padWithNils n xs = xs ++ replicate (n - length xs) Nil
Expand Down Expand Up @@ -160,10 +161,14 @@ eval (AST.FieldRef t k) = do
_ -> return [Nil]
Nothing -> return [Nil]

-- this is essentially the same as regular call
-- TODO should it even be a difference in the AST?
eval (AST.BinOp name lhs rhs) = eval (AST.Call (AST.Var name) [lhs, rhs])
eval (AST.UnOp name expr) = eval (AST.Call (AST.Var name) [expr])
eval (AST.BinOp op lhs rhs) = do
a <- head <$> eval lhs
b <- head <$> eval rhs
binaryOperatorCall op a b

eval (AST.UnOp op expr) = do
a <- head <$> eval expr
unaryOperatorCall op a

-- Table constructor in form { k = v, ... }
eval (AST.TableCons entries) = do
Expand Down Expand Up @@ -202,6 +207,142 @@ evalExpressionList xs = do

--------------

type BinaryOperatorImpl = Value -> Value -> LuaM [Value]
type UnaryOperatorImpl = Value -> LuaM [Value]

binaryOperatorCall :: AST.BinaryOperator -> Value -> Value -> LuaM [Value]
binaryOperatorCall AST.OpRaise = \_ _ -> vmErrorStr "Sorry, ^ not implemented yet"
binaryOperatorCall AST.OpPlus = opPlus
binaryOperatorCall AST.OpMinus = opMinus
binaryOperatorCall AST.OpMult = opMult
binaryOperatorCall AST.OpDivide = opDiv
binaryOperatorCall AST.OpModulo = \_ _ -> vmErrorStr "Sorry, % not implemented yet"

binaryOperatorCall AST.OpConcat = opConcat

binaryOperatorCall AST.OpEqual = opEqual
binaryOperatorCall AST.OpLess = opLess
binaryOperatorCall AST.OpGreater = opGreater
binaryOperatorCall AST.OpLE = \_ _ -> vmErrorStr "Sorry, <= not implemented yet"
binaryOperatorCall AST.OpGE = \_ _ -> vmErrorStr "Sorry, >= not implemented yet"
binaryOperatorCall AST.OpNotEqual = \_ _ -> vmErrorStr "Sorry, ~= not implemented yet"

binaryOperatorCall AST.OpAnd = opAnd
binaryOperatorCall AST.OpOr = opOr

unaryOperatorCall :: AST.UnaryOperator -> Value -> LuaM [Value]
unaryOperatorCall AST.OpUnaryMinus = opUnaryMinus
unaryOperatorCall AST.OpLength = opLength
unaryOperatorCall AST.OpNot = opNot

{-
https://www.lua.org/pil/13.1.html
To choose a metamethod, Lua does the following:
(1) If the first value has a metatable with an __add field, Lua uses this value as the metamethod,
independently of the second value;
(2) otherwise, if the second value has a metatable with an __add field, Lua uses this value as the metamethod;
(3) otherwise, Lua raises an error.
__add, __mul, __sub (for subtraction), __div (for division),
__unm (for negation), and __pow
-}

binaryMetaOperator :: String -> Value -> Value -> LuaM [Value]
binaryMetaOperator fstr a b = do
maybeFn <- getMetaFunction fstr a
case maybeFn of
Just fra -> callRef fra [a,b]
_ -> do
maybeFnB <- getMetaFunction fstr b
case maybeFnB of
Just frb -> callRef frb [a,b]
_ -> throwErrorStr $ "No metaop '" ++ fstr ++ "' on those two values"

unaryMetaOperator :: String -> Value -> LuaM [Value]
unaryMetaOperator fstr a = do
maybeFn <- getMetaFunction fstr a
case maybeFn of
Just fr -> callRef fr [a]
_ -> throwErrorStr $ "No metaop '" ++ fstr ++ "' on this value"


opPlus :: BinaryOperatorImpl
opPlus (Number a) (Number b) = return $ [Number (a + b)]
opPlus a b = binaryMetaOperator "__add" a b

opMult :: BinaryOperatorImpl
opMult (Number a) (Number b) = return $ [Number (a * b)]
opMult a b = binaryMetaOperator "__mult" a b

opDiv :: BinaryOperatorImpl
opDiv (Number a) (Number b) = return $ [Number (a / b)]
opDiv a b = binaryMetaOperator "__div" a b

opUnaryMinus :: UnaryOperatorImpl
opUnaryMinus (Number a) = return $ [Number (-a)] --unary negate
opUnaryMinus a = unaryMetaOperator "__unm" a

opMinus :: BinaryOperatorImpl
opMinus (Number a) (Number b) = return $ [Number (a - b)]
opMinus a b = binaryMetaOperator "__sub" a b

opConcat :: BinaryOperatorImpl
opConcat (Str a) (Str b) = return [Str $ a ++ b]
opConcat a b = binaryMetaOperator "__concat" a b

opLength :: UnaryOperatorImpl
opLength (Str a) = return [Number . fromIntegral $ length a]
opLength (Table tr) = do
hasMetaLen <- isJust <$> getMetaFunction "__len" (Table tr)
if hasMetaLen
then unaryMetaOperator "__len" (Table tr)
else do
(TableData td _) <- getTableData tr
case Map.lookupMax td of
Just (Number x, _) -> return [Number x]
_ -> return [Number 0]

opLength Nil = throwErrorStr "Attempt to get length of a nil value" -- :)
opLength a = unaryMetaOperator "__len" a

-- Polymorphic comparison operators
opEqual :: BinaryOperatorImpl
opEqual Nil Nil = return [Boolean False]
opEqual a b
| a == b = return [Boolean True]
| otherwise = luaEQHelper a b
where
luaEQHelper :: Value -> Value -> LuaM [Value]
luaEQHelper a b = do
maybeEqA <- getMetaFunction "__eq" a
maybeEqB <- getMetaFunction "__eq" b

case (maybeEqA, maybeEqB) of
-- meta-equality is only used if both eq functions are the same
(Just eqA, Just eqB) | eqA == eqB -> callRef eqA [a,b]
_ -> return [Boolean False]

opGreater :: BinaryOperatorImpl
opGreater (Number a) (Number b) = return [Boolean $ a > b]
opGreater (Str a) (Str b) = return [Boolean $ a > b]
opGreater a b = binaryMetaOperator "__lt" b a -- order reversed

opLess :: BinaryOperatorImpl
opLess (Number a) (Number b) = return [Boolean $ a < b]
opLess (Str a) (Str b) = return [Boolean $ a < b]
opLess a b = binaryMetaOperator "__lt" a b

opNot :: UnaryOperatorImpl
opNot a = return [Boolean . not . coerceToBool $ [a]]

opOr :: BinaryOperatorImpl
opOr a b = return [Boolean $ (coerceToBool [a]) || (coerceToBool [b])]

opAnd :: BinaryOperatorImpl
opAnd a b = return [Boolean $ (coerceToBool [a]) && (coerceToBool [b])]

--------------

runUntil :: Monad m => [a] -> (a -> m Bubble) -> m Bubble
runUntil (h:t) f = do
r <- f h
Expand Down Expand Up @@ -427,4 +568,6 @@ assignLValue (AST.LFieldRef t k) v = do
_ -> regularSet
Nothing -> regularSet
where
regularSet = setTableField tr (k,v)
regularSet = setTableField tr (k,v)


Loading

0 comments on commit f9901ca

Please sign in to comment.