diff --git a/Test/JavaSources/Main.java b/Test/JavaSources/Main.java index b912e91..e3b7b76 100644 --- a/Test/JavaSources/Main.java +++ b/Test/JavaSources/Main.java @@ -15,6 +15,7 @@ public class Main { TestMultipleClasses multipleClasses = new TestMultipleClasses(); TestRecursion recursion = new TestRecursion(10); TestMalicious malicious = new TestMalicious(); + TestLoop loop = new TestLoop(); // constructing a basic class works assert empty != null; @@ -32,6 +33,7 @@ public class Main { assert recursion.child.child.child.child.child.value == 5; // self-referencing methods work. assert recursion.fibonacci(15) == 610; + assert loop.factorial(5) == 120; // intentionally dodgy expressions work assert malicious.assignNegativeIncrement(42) == -42; assert malicious.tripleAddition(1, 2, 3) == 6; diff --git a/Test/JavaSources/TestLoop.java b/Test/JavaSources/TestLoop.java new file mode 100644 index 0000000..40491fb --- /dev/null +++ b/Test/JavaSources/TestLoop.java @@ -0,0 +1,12 @@ +public class TestLoop { + public int factorial(int n) + { + int tally = 1; + for(int i = 1; i <= n; i++) + { + tally *= i; + } + + return tally; + } +} diff --git a/Test/TestParser.hs b/Test/TestParser.hs index 3d05001..2767c15 100644 --- a/Test/TestParser.hs +++ b/Test/TestParser.hs @@ -238,6 +238,40 @@ testStatementPreIncrement = TestCase $ assertEqual "expect increment" [StatementExpressionStatement $ PostIncrement $ Reference "a"] $ parseStatement [IDENTIFIER "a",INCREMENT,SEMICOLON] +testForLoop = TestCase $ + assertEqual "expect for loop" [Block [ + LocalVariableDeclaration (VariableDeclaration "int" "i" (Just (IntegerLiteral 0))), + While (BinaryOperation CompareLessThan (Reference "i") (IntegerLiteral 3)) (Block [Block [], StatementExpressionStatement (PostIncrement (Reference "i"))]) + ]] $ + parseStatement [FOR,LBRACE,INT,IDENTIFIER "i",ASSIGN,INTEGERLITERAL 0,SEMICOLON,IDENTIFIER "i",LESS,INTEGERLITERAL 3,SEMICOLON,IDENTIFIER "i",INCREMENT,RBRACE,LBRACKET,RBRACKET] +testForLoopExpressionlistInInit = TestCase $ + assertEqual "expect expressionlist in init part of for loop" [Block [ + StatementExpressionStatement (PostIncrement (Reference "i")), + While (BinaryOperation CompareLessThan (Reference "i") (IntegerLiteral 3)) (Block [Block [], StatementExpressionStatement (PostIncrement (Reference "i"))]) + ]] $ + parseStatement [FOR,LBRACE,IDENTIFIER "i",INCREMENT,SEMICOLON,IDENTIFIER "i",LESS,INTEGERLITERAL 3,SEMICOLON,IDENTIFIER "i",INCREMENT,RBRACE,LBRACKET,RBRACKET] +testForLoopMultipleUpdateExpressions = TestCase $ + assertEqual "expect for loop with multiple update statements" [Block [ + LocalVariableDeclaration (VariableDeclaration "int" "i" (Just (IntegerLiteral 0))), + While (BinaryOperation CompareLessThan (Reference "i") (IntegerLiteral 3)) (Block [Block [], StatementExpressionStatement (PostIncrement (Reference "i")), StatementExpressionStatement (PostIncrement (Reference "k"))]) + ]] $ + parseStatement [FOR,LBRACE,INT,IDENTIFIER "i",ASSIGN,INTEGERLITERAL 0,SEMICOLON,IDENTIFIER "i",LESS,INTEGERLITERAL 3,SEMICOLON,IDENTIFIER "i",INCREMENT,COMMA,IDENTIFIER "k",INCREMENT,RBRACE,LBRACKET,RBRACKET] +testForLoopEmptyFirstPart = TestCase $ + assertEqual "expect for loop with empty init part" [Block [ + While (BinaryOperation CompareLessThan (Reference "i") (IntegerLiteral 3)) (Block [Block [], StatementExpressionStatement (PostIncrement (Reference "i"))]) + ]] $ + parseStatement [FOR,LBRACE,SEMICOLON,IDENTIFIER "i",LESS,INTEGERLITERAL 3,SEMICOLON,IDENTIFIER "i",INCREMENT,RBRACE,LBRACKET,RBRACKET] +testForLoopEmtpySecondPart = TestCase $ + assertEqual "expect for loop with empty expresion part" [Block [ + While (BooleanLiteral True) (Block [Block [], StatementExpressionStatement (PostIncrement (Reference "i"))]) + ]] $ + parseStatement [FOR,LBRACE,SEMICOLON,SEMICOLON,IDENTIFIER "i",INCREMENT,RBRACE,LBRACKET,RBRACKET] +testForLoopEmtpy = TestCase $ + assertEqual "expect empty for loop" [Block [While (BooleanLiteral True) (Block [Block []])]] $ + parseStatement [FOR,LBRACE,SEMICOLON,SEMICOLON,RBRACE,LBRACKET,RBRACKET] + + + tests = TestList [ testSingleEmptyClass, @@ -308,5 +342,11 @@ tests = TestList [ testStatementMethodCallNoParams, testStatementConstructorCall, testStatementConstructorCallWithArgs, - testStatementPreIncrement + testStatementPreIncrement, + testForLoop, + testForLoopExpressionlistInInit, + testForLoopMultipleUpdateExpressions, + testForLoopEmptyFirstPart, + testForLoopEmtpySecondPart, + testForLoopEmtpy ] \ No newline at end of file diff --git a/doc/bytecode.md b/doc/bytecode.md index 1002122..5624b72 100644 --- a/doc/bytecode.md +++ b/doc/bytecode.md @@ -4,7 +4,7 @@ Die Bytecodegenerierung ist letztendlich eine zweistufige Transformation: `Getypter AST -> [ClassFile] -> [[Word8]]` -Vom AST, der bereits den Typcheck durchlaufen hat, wird zunächst eine Abbildung in die einzelnen ClassFiles vorgenommen. Diese ClassFiles werden anschließend in deren Byte-Repräsentation serialisiert. Dieser Teil der Aufgabenstellung wurde gemeinsam von Christian Brier und Matthias Raba umgesetzt. +Vom AST, der bereits den Typcheck durchlaufen hat, wird zunächst eine Abbildung in die einzelnen ClassFiles vorgenommen. Diese ClassFiles werden anschließend in deren Byte-Repräsentation serialisiert. ## Codegenerierung @@ -76,4 +76,8 @@ instance Serializable Operation where serialize (Opgetfield index) = 0xB4 : unpackWord16 index ``` -Die Struktur ClassFile ruft für deren Kinder rekursiv diese `serialize` Funktion auf und konkateniert die Ergebnisse. Am Ende bleibt eine flache Word8-Liste übrig, die Serialisierung ist damit abgeschlossen. Da der Typecheck sicherstellt, dass alle referenzierten Methoden/Felder gültig sind, kann die Übersetzung der einzelnen Klassen voneinander unabhängig geschehen. \ No newline at end of file +Die Struktur ClassFile ruft für deren Kinder rekursiv diese `serialize` Funktion auf und konkateniert die Ergebnisse. Am Ende bleibt eine flache Word8-Liste übrig, die Serialisierung ist damit abgeschlossen. Da der Typecheck sicherstellt, dass alle referenzierten Methoden/Felder gültig sind, kann die Übersetzung der einzelnen Klassen voneinander unabhängig geschehen. + +## Aufgabenteilung + +Die Bytecodegenerierung wurde von Matthias Raba und Christian Brier im Stile des Pair Programmings zu zweit erarbeitet. Durch bisher gute Erfahrungen in vorherigen Projekten, sowie dem Interesse, alle Teile der Bytecodegenerierung zu sehen, wurde diese Programmierungsform als die Beste ausgewählt. Insgesamt lief die Implementierungsphase wie geplant und ohne weitere Komplikationen ab. \ No newline at end of file diff --git a/src/Ast.hs b/src/Ast.hs index fdf088b..794b030 100644 --- a/src/Ast.hs +++ b/src/Ast.hs @@ -6,8 +6,8 @@ type Identifier = String data ParameterDeclaration = ParameterDeclaration DataType Identifier deriving (Show, Eq) data VariableDeclaration = VariableDeclaration DataType Identifier (Maybe Expression) deriving (Show, Eq) -data Class = Class DataType [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq) data MethodDeclaration = MethodDeclaration DataType Identifier [ParameterDeclaration] Statement deriving (Show, Eq) +data Class = Class DataType [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq) data Statement = If Expression Statement (Maybe Statement) @@ -23,11 +23,11 @@ data StatementExpression = Assignment Expression Expression | ConstructorCall DataType [Expression] | MethodCall Expression Identifier [Expression] - | TypedStatementExpression DataType StatementExpression | PostIncrement Expression | PostDecrement Expression | PreIncrement Expression | PreDecrement Expression + | TypedStatementExpression DataType StatementExpression deriving (Show, Eq) data BinaryOperator diff --git a/src/ByteCode/Assembler.hs b/src/ByteCode/Assembler.hs index 101d7aa..555569b 100644 --- a/src/ByteCode/Assembler.hs +++ b/src/ByteCode/Assembler.hs @@ -221,26 +221,26 @@ assembleStatement (constants, ops, lvars) (TypedStatement _ (Block statements)) assembleStatement (constants, ops, lvars) (TypedStatement dtype (If expr if_stmt else_stmt)) = let (constants_cmp, ops_cmp, _) = assembleExpression (constants, [], lvars) expr - (constants_ifa, ops_ifa, _) = assembleStatement (constants_cmp, [], lvars) if_stmt + (constants_ifa, ops_ifa, lvars_ifa) = assembleStatement (constants_cmp, [], lvars) if_stmt (constants_elsea, ops_elsea, _) = case else_stmt of - Nothing -> (constants_ifa, [], lvars) - Just stmt -> assembleStatement (constants_ifa, [], lvars) stmt + Nothing -> (constants_ifa, [], lvars_ifa) + Just stmt -> assembleStatement (constants_ifa, [], lvars_ifa) stmt -- +6 because we insert 2 gotos, one for if, one for else if_length = sum (map opcodeEncodingLength ops_ifa) -- +3 because we need to account for the goto in the if statement. else_length = sum (map opcodeEncodingLength ops_elsea) in case dtype of - "void" -> (constants_ifa, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq (if_length + 6)] ++ ops_ifa ++ [Opgoto (else_length + 3)] ++ ops_elsea, lvars) - _ -> (constants_ifa, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq (if_length + 3)] ++ ops_ifa ++ ops_elsea, lvars) + "void" -> (constants_ifa, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq (if_length + 6)] ++ ops_ifa ++ [Opgoto (else_length + 3)] ++ ops_elsea, lvars_ifa) + _ -> (constants_ifa, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq (if_length + 3)] ++ ops_ifa ++ ops_elsea, lvars_ifa) assembleStatement (constants, ops, lvars) (TypedStatement _ (While expr stmt)) = let (constants_cmp, ops_cmp, _) = assembleExpression (constants, [], lvars) expr - (constants_stmta, ops_stmta, _) = assembleStatement (constants_cmp, [], lvars) stmt + (constants_stmta, ops_stmta, lvars_stmta) = assembleStatement (constants_cmp, [], lvars) stmt -- +3 because we insert 2 gotos, one for the comparison, one for the goto back to the comparison stmt_length = sum (map opcodeEncodingLength ops_stmta) + 6 entire_length = stmt_length + sum (map opcodeEncodingLength ops_cmp) in - (constants_stmta, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq stmt_length] ++ ops_stmta ++ [Opgoto (-entire_length)], lvars) + (constants_stmta, ops ++ ops_cmp ++ [Opsipush 0, Opif_icmpeq stmt_length] ++ ops_stmta ++ [Opgoto (-entire_length)], lvars_stmta) assembleStatement (constants, ops, lvars) (TypedStatement _ (LocalVariableDeclaration (VariableDeclaration dtype name expr))) = let isPrimitive = elem dtype ["char", "boolean", "int"] diff --git a/src/ByteCode/ClassFile.hs b/src/ByteCode/ClassFile.hs index 7a20e97..3546ded 100644 --- a/src/ByteCode/ClassFile.hs +++ b/src/ByteCode/ClassFile.hs @@ -96,40 +96,7 @@ className classFile = let opcodeEncodingLength :: Operation -> Word16 -opcodeEncodingLength Opiadd = 1 -opcodeEncodingLength Opisub = 1 -opcodeEncodingLength Opimul = 1 -opcodeEncodingLength Opidiv = 1 -opcodeEncodingLength Opirem = 1 -opcodeEncodingLength Opiand = 1 -opcodeEncodingLength Opior = 1 -opcodeEncodingLength Opixor = 1 -opcodeEncodingLength Opineg = 1 -opcodeEncodingLength Opdup = 1 -opcodeEncodingLength (Opnew _) = 3 -opcodeEncodingLength (Opif_icmplt _) = 3 -opcodeEncodingLength (Opif_icmple _) = 3 -opcodeEncodingLength (Opif_icmpgt _) = 3 -opcodeEncodingLength (Opif_icmpge _) = 3 -opcodeEncodingLength (Opif_icmpeq _) = 3 -opcodeEncodingLength (Opif_icmpne _) = 3 -opcodeEncodingLength Opaconst_null = 1 -opcodeEncodingLength Opreturn = 1 -opcodeEncodingLength Opireturn = 1 -opcodeEncodingLength Opareturn = 1 -opcodeEncodingLength Opdup_x1 = 1 -opcodeEncodingLength Oppop = 1 -opcodeEncodingLength (Opinvokespecial _) = 3 -opcodeEncodingLength (Opinvokevirtual _) = 3 -opcodeEncodingLength (Opgoto _) = 3 -opcodeEncodingLength (Opsipush _) = 3 -opcodeEncodingLength (Opldc_w _) = 3 -opcodeEncodingLength (Opaload _) = 4 -opcodeEncodingLength (Opiload _) = 4 -opcodeEncodingLength (Opastore _) = 4 -opcodeEncodingLength (Opistore _) = 4 -opcodeEncodingLength (Opputfield _) = 3 -opcodeEncodingLength (Opgetfield _) = 3 +opcodeEncodingLength op = fromIntegral . length . serialize $ op class Serializable a where serialize :: a -> [Word8] diff --git a/src/ByteCode/Util.hs b/src/ByteCode/Util.hs index f177342..7f48b17 100644 --- a/src/ByteCode/Util.hs +++ b/src/ByteCode/Util.hs @@ -279,14 +279,14 @@ operationStackCost constants (Opiload _) = 1 operationStackCost constants (Opastore _) = -1 operationStackCost constants (Opistore _) = -1 operationStackCost constants (Opputfield _) = -2 -operationStackCost constants (Opgetfield _) = -1 +operationStackCost constants (Opgetfield _) = 0 -simulateStackOperation :: [ConstantInfo] -> Operation -> (Int, Int) -> (Int, Int) -simulateStackOperation constants op (cd, md) = let +simulateStackOperation :: (Int, Int) -> [ConstantInfo] -> Operation -> (Int, Int) +simulateStackOperation (cd, md) constants op = let depth = cd + operationStackCost constants op in if depth < 0 then error ("Consuming value off of empty stack: " ++ show op) else (depth, max depth md) maxStackDepth :: [ConstantInfo] -> [Operation] -> Int -maxStackDepth constants ops = snd $ foldr (simulateStackOperation constants) (0, 0) (reverse ops) +maxStackDepth constants ops = snd $ foldl (`simulateStackOperation` constants) (0, 0) ops diff --git a/src/Parser/JavaParser.y b/src/Parser/JavaParser.y index d7dc862..3e011d1 100644 --- a/src/Parser/JavaParser.y +++ b/src/Parser/JavaParser.y @@ -75,6 +75,7 @@ import Parser.Lexer OREQUAL { OREQUAL } COLON { COLON } LESS { LESS } + FOR { FOR } %% compilationunit : typedeclarations { $1 } @@ -204,6 +205,7 @@ statement : statementwithouttrailingsubstatement{ $1 } -- statement retu | ifthenstatement { [$1] } | ifthenelsestatement { [$1] } | whilestatement { [$1] } + | forstatement { [$1] } expression : assignmentexpression { $1 } @@ -224,6 +226,21 @@ ifthenelsestatement : IF LBRACE expression RBRACE statementnoshortif ELSE state whilestatement : WHILE LBRACE expression RBRACE statement { While $3 (Block $5) } +forstatement : FOR LBRACE forinit optionalexpression forupdate statement { Block ($3 ++ [While ($4) (Block ($6 ++ $5))]) } + +forinit : statementexpressionlist SEMICOLON { $1 } + | localvariabledeclaration SEMICOLON { $1 } + | SEMICOLON { [] } + +optionalexpression : expression SEMICOLON { $1 } + | SEMICOLON { BooleanLiteral True } + +forupdate : statementexpressionlist RBRACE { $1 } + | RBRACE { [] } + +statementexpressionlist : statementexpression { [StatementExpressionStatement $1] } + | statementexpressionlist COMMA statementexpression { $1 ++ [StatementExpressionStatement $3] } + assignmentexpression : conditionalexpression { $1 } | assignment { StatementExpressionExpression $1 }