Merge pull request 'bytecode' (#7) from bytecode into master
Reviewed-on: #7
This commit is contained in:
commit
94ed7b5056
@ -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;
|
||||
|
12
Test/JavaSources/TestLoop.java
Normal file
12
Test/JavaSources/TestLoop.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
]
|
@ -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.
|
||||
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.
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user