diff --git a/README.md b/README.md index 013174b..728932c 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ Increments: - [X] Binary Operators on Ints - [X] Unary Operators on Ints - [X] Local Variables and Assignment -- [ ] Booleans and Comparisons -- [ ] WriteLn("Hello World") (finally) +- [X] Booleans - [ ] Branching (if statements) +- [ ] Comparisons +- [ ] WriteLn("Hello World") (finally) - [ ] Loops - [ ] Procedures - [ ] Functions diff --git a/src/HSPC/CodeGen.hs b/src/HSPC/CodeGen.hs index b89fd0d..532a4ba 100644 --- a/src/HSPC/CodeGen.hs +++ b/src/HSPC/CodeGen.hs @@ -13,6 +13,8 @@ type OffsetMap = Map.Map String Int32 dataTypeToSize :: HSPCDataType -> Int dataTypeToSize IntegerType = 64 +-- space efficency is clearly a top priority +dataTypeToSize BooleanType = 64 buildOffsetMap :: [(String, HSPCDataType)] -> (Int32, OffsetMap) buildOffsetMap vars = (totalSize, Map.fromList mappedVars) @@ -72,6 +74,9 @@ generateStatement _ ast = error $ "CodeGen for " ++ show ast ++ " not implemente generateExpression :: OffsetMap -> Expression -> [Word8] generateExpression _ (IntLiteral i) = [0x48, 0xb8] ++ int64ToLE i -- movabs rax i +generateExpression _ (BoolLiteral b) = + [0x48, 0xb8] + ++ (int64ToLE . fromIntegral . fromEnum) b -- movabs rax i generateExpression offsetMap (Identifier str) = case Map.lookup str offsetMap of Just offset -> [0x48, 0x8b, 0x85] ++ int32ToLE offset -- mov rax [rbp - n] @@ -99,4 +104,19 @@ generateExpression offsetMap (IntDivide op1 op2) = ++ [0x48, 0x99] -- cqo ++ [0x48, 0xf7, 0x3c, 0x24] -- idiv qword [rsp] ++ [0x48, 0x83, 0xc4, 0x08] -- add rsp 8 (release stack space) +generateExpression offsetMap (BoolOr op1 op2) = + generateExpression offsetMap op2 + ++ [0x50] -- push rax + ++ generateExpression offsetMap op1 + ++ [0x48, 0x0B, 0x04, 0x24] -- or rax, [rsp] + ++ [0x48, 0x83, 0xc4, 0x08] -- add rsp 8 (release stack space) +generateExpression offsetMap (BoolAnd op1 op2) = + generateExpression offsetMap op2 + ++ [0x50] -- push rax + ++ generateExpression offsetMap op1 + ++ [0x48, 0x23, 0x04, 0x24] -- and rax, [rsp] + ++ [0x48, 0x83, 0xc4, 0x08] -- add rsp 8 (release stack space) +generateExpression offsetMap (BoolNot op) = + generateExpression offsetMap op + ++ [0x48, 0x83, 0xf0, 0x01] -- xor rax, 0x1 generateExpression _ ast = error $ "CodeGen for " ++ show ast ++ " not implemented" diff --git a/src/HSPC/Parse.hs b/src/HSPC/Parse.hs index 03ddc86..af5f4a5 100644 --- a/src/HSPC/Parse.hs +++ b/src/HSPC/Parse.hs @@ -10,12 +10,15 @@ module HSPC.Parse where import Data.Int (Int64) +import Data.Map qualified as Map import HSPC.Tokenize (HSPCToken (..)) data Program = Program String [Block] deriving (Show, Eq) -data HSPCDataType = IntegerType +data HSPCDataType + = IntegerType + | BooleanType deriving (Show, Eq) data Block @@ -33,6 +36,10 @@ data Statement data Expression = Identifier String | IntLiteral Int64 + | BoolLiteral Bool + | BoolNot Expression + | BoolAnd Expression Expression + | BoolOr Expression Expression | Add Expression Expression | Subtract Expression Expression | Multiply Expression Expression @@ -49,14 +56,16 @@ data ParseError | ExpectedVariableDeclaration [HSPCToken] | ExpectedOperand String | UnmatchedBracket + | VariableNotDeclared String | InvalidDataType HSPCToken | UnexpectedToken HSPCToken | ExpectedToken HSPCToken - | NotImplemented [HSPCToken] + | NotImplemented String [HSPCToken] deriving (Show) toDataType :: HSPCToken -> Either ParseError HSPCDataType toDataType IntegerTypeTok = Right IntegerType +toDataType BooleanTypeTok = Right BooleanType toDataType toc = Left $ InvalidDataType toc parse :: [HSPCToken] -> Either ParseError Program @@ -71,10 +80,10 @@ parse _ = Left MustStartWithProgramStatement parseMainProgramBlock :: [HSPCToken] -> Either ParseError Block parseMainProgramBlock (VarKeyWordTok : tocs) = do (vars, rest) <- parseVariableDeclarations [] tocs - (res, _) <- parseStatements [] rest + (res, _) <- parseStatements (Map.fromList vars) [] rest return $ MainProgramBlock vars res parseMainProgramBlock (BeginKeyWordTok : tocs) = do - (res, _) <- parseStatements [] tocs + (res, _) <- parseStatements Map.empty [] tocs return $ MainProgramBlock [] res parseMainProgramBlock _ = Left InvalidStartOfBlock @@ -90,6 +99,7 @@ parseVariableDeclarations acc xs = do parseVariableDeclarations (expr : acc) rest parseVariableDeclaration :: [HSPCToken] -> Either ParseError ((String, HSPCDataType), [HSPCToken]) +-- TODO: Handle multiple identifiers seperated by commas parseVariableDeclaration (IdentifierTok name : ColonTok : dataTypeToc : xs) = do datatype <- toDataType dataTypeToc rest <- removeLeadingSemiColon xs @@ -119,35 +129,43 @@ splitToMatchingBracket toks = go toks [] 0 go (x : xs) acc i = go xs (x : acc) i go [] _ _ = Left UnmatchedBracket -parseStatements :: [Statement] -> [HSPCToken] -> Either ParseError ([Statement], [HSPCToken]) -parseStatements acc (EndKeyWordTok : xs) = Right (reverse acc, xs) -parseStatements _ [] = Left ExpectedEndStatement -parseStatements acc xs = do - (expr, rest) <- parseStatement xs - parseStatements (expr : acc) rest +parseStatements :: Map.Map String HSPCDataType -> [Statement] -> [HSPCToken] -> Either ParseError ([Statement], [HSPCToken]) +parseStatements _ acc (EndKeyWordTok : xs) = Right (reverse acc, xs) +parseStatements _ _ [] = Left ExpectedEndStatement +parseStatements vars acc xs = do + (expr, rest) <- parseStatement vars xs + parseStatements vars (expr : acc) rest -parseStatement :: [HSPCToken] -> Either ParseError (Statement, [HSPCToken]) -parseStatement (IdentifierTok name : AssignmentTok : xs) = do +parseStatement :: Map.Map String HSPCDataType -> [HSPCToken] -> Either ParseError (Statement, [HSPCToken]) +parseStatement varMap (IdentifierTok name : AssignmentTok : xs) = do let (rhs, rawRest) = break (== SemiColonTok) xs rest <- removeLeadingSemiColon rawRest - op <- parseIntOperand rhs + op <- case Map.lookup name varMap of + Just dt -> parseOperand dt rhs + Nothing -> Left $ VariableNotDeclared name return (Assignment name op, rest) parseStatement + _ (HaltBuiltInTok : OpenBracketTok : CloseBracketTok : SemiColonTok : xs) = Right (Halt (IntLiteral 0), xs) parseStatement + _ (HaltBuiltInTok : OpenBracketTok : xs) = do (internal, rawRest) <- splitToMatchingBracket xs rest <- removeLeadingSemiColon rawRest - op <- parseIntOperand internal + op <- parseOperand IntegerType internal return (Halt op, rest) -parseStatement tok = Left $ NotImplemented tok +parseStatement _ tok = Left $ NotImplemented "parseStatement" tok + +parseOperand :: HSPCDataType -> [HSPCToken] -> Either ParseError Expression +parseOperand IntegerType = parseIntOperand +parseOperand BooleanType = parseBoolOperand -- IntOperand = Term { ("+" | "-") Term } -- Term = Factor { ("*" | "div") Factor } -- Factor = ("+" | "-") Factor | Number | "(" IntOperand ")" -- Number = // an integer parseIntOperand :: [HSPCToken] -> Either ParseError Expression -parseIntOperand toks = case breakLastOuter (`elem` [PlusTok, MinusTok]) toks of +parseIntOperand tokens = case breakLastOuter (`elem` [PlusTok, MinusTok]) tokens of -- starts with +- ([], after) -> parseTerm after -- doesn't contain +- @@ -163,35 +181,75 @@ parseIntOperand toks = case breakLastOuter (`elem` [PlusTok, MinusTok]) toks of right <- parseTerm after return $ Subtract left right _ -> error "Should be unreachable since we break on PlusTok || MinusTok" - -parseTerm :: [HSPCToken] -> Either ParseError Expression -parseTerm toks = case breakLastOuter (`elem` [IntDivideTok, MultiplyTok]) toks of - (before, []) -> parseFactor before - (_, [IntDivideTok]) -> Left $ ExpectedOperand "Expected operand after 'div'" - (before, IntDivideTok : after) -> do - left <- parseIntOperand before - right <- parseFactor after - return $ IntDivide left right - (_, [MultiplyTok]) -> Left $ ExpectedOperand "Expected operand after *" - (before, MultiplyTok : after) -> do - left <- parseIntOperand before - right <- parseFactor after - return $ Multiply left right - _ -> error "Should be unreachable since we break on IntDivideTok || MultiplyTok" - -parseFactor :: [HSPCToken] -> Either ParseError Expression -parseFactor [LiteralIntTok n] = Right $ IntLiteral n -parseFactor [IdentifierTok name] = Right $ Identifier name -parseFactor (PlusTok : xs) = do - operand <- parseFactor xs - return $ UnaryPlus operand -parseFactor (MinusTok : xs) = do - operand <- parseFactor xs - return $ UnaryMinus operand -parseFactor (OpenBracketTok : xs) = do + where + parseTerm :: [HSPCToken] -> Either ParseError Expression + parseTerm toks = case breakLastOuter (`elem` [IntDivideTok, MultiplyTok]) toks of + (before, []) -> parseFactor before + (_, [IntDivideTok]) -> Left $ ExpectedOperand "Expected operand after 'div'" + (before, IntDivideTok : after) -> do + left <- parseIntOperand before + right <- parseFactor after + return $ IntDivide left right + (_, [MultiplyTok]) -> Left $ ExpectedOperand "Expected operand after *" + (before, MultiplyTok : after) -> do + left <- parseIntOperand before + right <- parseFactor after + return $ Multiply left right + _ -> error "Should be unreachable since we break on IntDivideTok || MultiplyTok" + + parseFactor :: [HSPCToken] -> Either ParseError Expression + parseFactor [LiteralIntTok n] = Right $ IntLiteral n + parseFactor [IdentifierTok name] = Right $ Identifier name + parseFactor (PlusTok : xs) = do + operand <- parseFactor xs + return $ UnaryPlus operand + parseFactor (MinusTok : xs) = do + operand <- parseFactor xs + return $ UnaryMinus operand + parseFactor (OpenBracketTok : xs) = do + (inside, remaining) <- splitToMatchingBracket xs + case remaining of + [] -> parseIntOperand inside + (tok : _) -> Left $ UnexpectedToken tok + parseFactor (tok : _) = Left $ UnexpectedToken tok + parseFactor [] = Left $ ExpectedOperand "Expected operand" + +-- BoolOperand = BoolTerm { or BoolTerm } +-- BoolTerm = BoolFactor { and BoolFactor } +-- BoolFactor = not BoolFactor | BoolPrimary +-- BoolPrimary = True | False | ( BoolOperand ) +parseBoolOperand :: [HSPCToken] -> Either ParseError Expression +parseBoolOperand toks = case breakLastOuter (== OrTok) toks of + (before, []) -> parseBoolTerm before + (_, [OrTok]) -> Left $ ExpectedOperand "Expected operand after 'div'" + (before, OrTok : after) -> do + left <- parseBoolTerm before + right <- parseBoolTerm after + return $ BoolOr left right + _ -> error "Should be unreachable since we break on OrTok" + +parseBoolTerm :: [HSPCToken] -> Either ParseError Expression +parseBoolTerm toks = case breakLastOuter (== AndTok) toks of + (before, []) -> parseBoolFactor before + (_, [AndTok]) -> Left $ ExpectedOperand "Expected operand after 'div'" + (before, AndTok : after) -> do + left <- parseBoolFactor before + right <- parseBoolFactor after + return $ BoolAnd left right + _ -> error "Should be unreachable since we break on AndTok" + +parseBoolFactor :: [HSPCToken] -> Either ParseError Expression +parseBoolFactor (NotTok : op) = do + opAsExpr <- parseBoolFactor op + return $ BoolNot opAsExpr +parseBoolFactor toks = parseBoolPrimary toks + +parseBoolPrimary :: [HSPCToken] -> Either ParseError Expression +parseBoolPrimary [LiteralBoolTok bool] = Right $ BoolLiteral bool +parseBoolPrimary (OpenBracketTok : xs) = do (inside, remaining) <- splitToMatchingBracket xs case remaining of - [] -> parseIntOperand inside + [] -> parseBoolOperand inside (tok : _) -> Left $ UnexpectedToken tok -parseFactor (tok : _) = Left $ UnexpectedToken tok -parseFactor [] = Left $ ExpectedOperand "Expected operand" +parseBoolPrimary (tok : _) = Left $ UnexpectedToken tok +parseBoolPrimary [] = Left $ ExpectedOperand "Expected boolean operand" diff --git a/src/HSPC/Tokenize.hs b/src/HSPC/Tokenize.hs index 7edf8bf..f612d91 100644 --- a/src/HSPC/Tokenize.hs +++ b/src/HSPC/Tokenize.hs @@ -6,8 +6,10 @@ import Data.List (isPrefixOf) data HSPCToken = LiteralIntTok Int64 + | LiteralBoolTok Bool | ProgramKeyWordTok | IntegerTypeTok + | BooleanTypeTok | VarKeyWordTok | BeginKeyWordTok | EndKeyWordTok @@ -18,6 +20,9 @@ data HSPCToken | ColonTok | SemiColonTok | IntDivideTok + | OrTok + | AndTok + | NotTok | PlusTok | MinusTok | MultiplyTok @@ -48,6 +53,12 @@ tokenizeIdentifierOrKeyWord "BEGIN" = BeginKeyWordTok tokenizeIdentifierOrKeyWord "HALT" = HaltBuiltInTok tokenizeIdentifierOrKeyWord "DIV" = IntDivideTok tokenizeIdentifierOrKeyWord "INTEGER" = IntegerTypeTok +tokenizeIdentifierOrKeyWord "BOOLEAN" = BooleanTypeTok +tokenizeIdentifierOrKeyWord "TRUE" = LiteralBoolTok True +tokenizeIdentifierOrKeyWord "FALSE" = LiteralBoolTok False +tokenizeIdentifierOrKeyWord "OR" = OrTok +tokenizeIdentifierOrKeyWord "AND" = AndTok +tokenizeIdentifierOrKeyWord "NOT" = NotTok tokenizeIdentifierOrKeyWord s = IdentifierTok s tokenizeOperator :: String -> [HSPCToken] diff --git a/test/Main.hs b/test/Main.hs index a9ee010..f085a4a 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -33,9 +33,13 @@ testSuite = TestSpec "Brackets with literal ints" "operators_brackets" 26, TestSpec "Unary minus with literal ints" "operators_unary_minus_int" 75, TestSpec "Unary plus with literal ints" "operators_unary_plus_int" 25, + TestSpec "Or between bools" "operators_bool_or" 0, + TestSpec "And between bools" "operators_bool_and" 0, + TestSpec "Negating bools" "operators_bool_not" 0, TestSpec "{} comments" "comments_curly_brace" 12, TestSpec "// comments" "comments_single_line" 12, - TestSpec "Single variable" "variables_single_var" 10 + TestSpec "Single variable" "variables_single_var" 10, + TestSpec "Boolean variable" "variables_bool" 0 ] main :: IO () diff --git a/test/sample_programs/operators_bool_and.golden b/test/sample_programs/operators_bool_and.golden new file mode 100644 index 0000000..e69de29 diff --git a/test/sample_programs/operators_bool_and.pas b/test/sample_programs/operators_bool_and.pas new file mode 100644 index 0000000..3d3e53b --- /dev/null +++ b/test/sample_programs/operators_bool_and.pas @@ -0,0 +1,8 @@ +program BoolAnd; + +var + b : boolean; +begin + b := true and false; + halt(); +end. diff --git a/test/sample_programs/operators_bool_not.golden b/test/sample_programs/operators_bool_not.golden new file mode 100644 index 0000000..e69de29 diff --git a/test/sample_programs/operators_bool_not.pas b/test/sample_programs/operators_bool_not.pas new file mode 100644 index 0000000..4e79aab --- /dev/null +++ b/test/sample_programs/operators_bool_not.pas @@ -0,0 +1,8 @@ +program BoolNot; + +var + b : boolean; +begin + b := not (true and false); + halt(); +end. diff --git a/test/sample_programs/operators_bool_or.golden b/test/sample_programs/operators_bool_or.golden new file mode 100644 index 0000000..e69de29 diff --git a/test/sample_programs/operators_bool_or.pas b/test/sample_programs/operators_bool_or.pas new file mode 100644 index 0000000..01734db --- /dev/null +++ b/test/sample_programs/operators_bool_or.pas @@ -0,0 +1,8 @@ +program BoolOR; + +var + b : boolean; +begin + b := true or false; + halt(); +end. diff --git a/test/sample_programs/variables_bool.golden b/test/sample_programs/variables_bool.golden new file mode 100644 index 0000000..e69de29 diff --git a/test/sample_programs/variables_bool.pas b/test/sample_programs/variables_bool.pas new file mode 100644 index 0000000..b2b7f9b --- /dev/null +++ b/test/sample_programs/variables_bool.pas @@ -0,0 +1,8 @@ +program BoolVariable; + +var + i : boolean; +begin + i := true; + halt(); +end.