From 807aea112ead53f2928ade36ae1c5d0920fabbc4 Mon Sep 17 00:00:00 2001 From: Matthias Raba Date: Fri, 14 Jun 2024 07:57:38 +0200 Subject: [PATCH] added Test classes, fixed assignment dup missing --- .gitignore | 1 - Test/JavaSources/Main.java | 34 +++++++ Test/JavaSources/TestConstructor.java | 9 ++ Test/JavaSources/TestEmpty.java | 4 + Test/JavaSources/TestFields.java | 5 + Test/JavaSources/TestMalicious.java | 11 ++ Test/JavaSources/TestMultipleClasses.java | 9 ++ Test/JavaSources/TestRecursion.java | 27 +++++ Test/TestByteCodeGenerator.hs | 118 ---------------------- Test/TestSuite.hs | 8 +- project.cabal | 9 +- src/ByteCode/Generator.hs | 6 +- src/Main.hs | 17 ++-- 13 files changed, 121 insertions(+), 137 deletions(-) create mode 100644 Test/JavaSources/Main.java create mode 100644 Test/JavaSources/TestConstructor.java create mode 100644 Test/JavaSources/TestEmpty.java create mode 100644 Test/JavaSources/TestFields.java create mode 100644 Test/JavaSources/TestMalicious.java create mode 100644 Test/JavaSources/TestMultipleClasses.java create mode 100644 Test/JavaSources/TestRecursion.java delete mode 100644 Test/TestByteCodeGenerator.hs diff --git a/.gitignore b/.gitignore index d20ba06..5dcf875 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,6 @@ cabal-dev *.chs.h *.dyn_o *.dyn_hi -*.java *.class *.local~* src/Parser/JavaParser.hs diff --git a/Test/JavaSources/Main.java b/Test/JavaSources/Main.java new file mode 100644 index 0000000..41b5d2f --- /dev/null +++ b/Test/JavaSources/Main.java @@ -0,0 +1,34 @@ +// compile (in project root) using: +// javac -g:none -sourcepath Test/JavaSources/ Test/JavaSources/Main.java +// compile all test files using: +// ls Test/JavaSources/*.java | grep -v ".*Main.java" | xargs -I {} cabal run compiler {} +// afterwards, run using +// java -ea -cp Test/JavaSources/ Main + +public class Main { + public static void main(String[] args) + { + TestEmpty empty = new TestEmpty(); + TestFields fields = new TestFields(); + TestConstructor constructor = new TestConstructor(42); + TestMultipleClasses multipleClasses = new TestMultipleClasses(); + TestRecursion recursion = new TestRecursion(10); + TestMalicious malicious = new TestMalicious(); + + // constructing a basic class works + assert empty != null; + // initializers (and default initializers to 0/null) work + assert fields.a == 0 && fields.b == 42; + // constructor parameters override initializers + assert constructor.a == 42; + // multiple classes within one file work. Referencing another classes fields/methods works. + assert multipleClasses.a.a == 42; + // self-referencing classes work. + assert recursion.child.child.child.child.child.value == 5; + // self-referencing methods work. + assert recursion.fibonacci(15) == 610; + // intentionally dodgy expressions work + assert malicious.assignNegativeIncrement(42) == -42; + assert malicious.tripleAddition(1, 2, 3) == 6; + } +} diff --git a/Test/JavaSources/TestConstructor.java b/Test/JavaSources/TestConstructor.java new file mode 100644 index 0000000..f676e1f --- /dev/null +++ b/Test/JavaSources/TestConstructor.java @@ -0,0 +1,9 @@ +public class TestConstructor +{ + public int a = -1; + + public TestConstructor(int initial_value) + { + a = initial_value; + } +} diff --git a/Test/JavaSources/TestEmpty.java b/Test/JavaSources/TestEmpty.java new file mode 100644 index 0000000..184895d --- /dev/null +++ b/Test/JavaSources/TestEmpty.java @@ -0,0 +1,4 @@ +public class TestEmpty +{ + +} diff --git a/Test/JavaSources/TestFields.java b/Test/JavaSources/TestFields.java new file mode 100644 index 0000000..2baaf80 --- /dev/null +++ b/Test/JavaSources/TestFields.java @@ -0,0 +1,5 @@ +public class TestFields +{ + public int a; + public int b = 42; +} diff --git a/Test/JavaSources/TestMalicious.java b/Test/JavaSources/TestMalicious.java new file mode 100644 index 0000000..f5f39e0 --- /dev/null +++ b/Test/JavaSources/TestMalicious.java @@ -0,0 +1,11 @@ +public class TestMalicious { + public int assignNegativeIncrement(int n) + { + return n=-++n+1; + } + + public int tripleAddition(int a, int b, int c) + { + return a+++b+++c++; + } +} diff --git a/Test/JavaSources/TestMultipleClasses.java b/Test/JavaSources/TestMultipleClasses.java new file mode 100644 index 0000000..c5e418e --- /dev/null +++ b/Test/JavaSources/TestMultipleClasses.java @@ -0,0 +1,9 @@ +public class TestMultipleClasses +{ + public AnotherTestClass a = new AnotherTestClass(); +} + +class AnotherTestClass +{ + public int a = 42; +} diff --git a/Test/JavaSources/TestRecursion.java b/Test/JavaSources/TestRecursion.java new file mode 100644 index 0000000..baa6a31 --- /dev/null +++ b/Test/JavaSources/TestRecursion.java @@ -0,0 +1,27 @@ +public class TestRecursion { + + public int value = 0; + public TestRecursion child = null; + + public TestRecursion(int n) + { + value = n; + + if(n > 0) + { + child = new TestRecursion(n - 1); + } + } + + public int fibonacci(int n) + { + if(n < 2) + { + return n; + } + else + { + return fibonacci(n - 1) + this.fibonacci(n - 2); + } + } +} diff --git a/Test/TestByteCodeGenerator.hs b/Test/TestByteCodeGenerator.hs deleted file mode 100644 index 2c6f4d2..0000000 --- a/Test/TestByteCodeGenerator.hs +++ /dev/null @@ -1,118 +0,0 @@ -module TestByteCodeGenerator where - -import Test.HUnit -import ByteCode.ClassFile.Generator -import ByteCode.ClassFile -import ByteCode.Constants -import Ast - -nakedClass = Class "Testklasse" [] [] -expectedClass = ClassFile { - constantPool = [ - ClassInfo 4, - MethodRefInfo 1 3, - NameAndTypeInfo 5 6, - Utf8Info "java/lang/Object", - Utf8Info "", - Utf8Info "()V", - Utf8Info "Code", - ClassInfo 9, - Utf8Info "Testklasse" - ], - accessFlags = accessPublic, - thisClass = 8, - superClass = 1, - fields = [], - methods = [], - attributes = [] - } - -classWithFields = Class "Testklasse" [] [VariableDeclaration "int" "testvariable" Nothing] -expectedClassWithFields = ClassFile { - constantPool = [ - ClassInfo 4, - MethodRefInfo 1 3, - NameAndTypeInfo 5 6, - Utf8Info "java/lang/Object", - Utf8Info "", - Utf8Info "()V", - Utf8Info "Code", - ClassInfo 9, - Utf8Info "Testklasse", - FieldRefInfo 8 11, - NameAndTypeInfo 12 13, - Utf8Info "testvariable", - Utf8Info "I" - ], - accessFlags = accessPublic, - thisClass = 8, - superClass = 1, - fields = [ - MemberInfo { - memberAccessFlags = accessPublic, - memberNameIndex = 12, - memberDescriptorIndex = 13, - memberAttributes = [] - } - ], - methods = [], - attributes = [] - } - -method = MethodDeclaration "int" "add_two_numbers" [ - ParameterDeclaration "int" "a", - ParameterDeclaration "int" "b" ] - (Block [Return (Just (BinaryOperation Addition (Reference "a") (Reference "b")))]) - - -classWithMethod = Class "Testklasse" [method] [] -expectedClassWithMethod = ClassFile { - constantPool = [ - ClassInfo 4, - MethodRefInfo 1 3, - NameAndTypeInfo 5 6, - Utf8Info "java/lang/Object", - Utf8Info "", - Utf8Info "()V", - Utf8Info "Code", - ClassInfo 9, - Utf8Info "Testklasse", - FieldRefInfo 8 11, - NameAndTypeInfo 12 13, - Utf8Info "add_two_numbers", - Utf8Info "(II)I" - ], - accessFlags = accessPublic, - thisClass = 8, - superClass = 1, - fields = [], - methods = [ - MemberInfo { - memberAccessFlags = accessPublic, - memberNameIndex = 12, - memberDescriptorIndex = 13, - memberAttributes = [ - CodeAttribute { - attributeMaxStack = 420, - attributeMaxLocals = 420, - attributeCode = [Opiadd] - } - ] - } - ], - attributes = [] - } - -testBasicConstantPool = TestCase $ assertEqual "basic constant pool" expectedClass $ classBuilder nakedClass emptyClassFile -testFields = TestCase $ assertEqual "fields in constant pool" expectedClassWithFields $ classBuilder classWithFields emptyClassFile -testMethodDescriptor = TestCase $ assertEqual "method descriptor" "(II)I" (methodDescriptor method) -testMethodAssembly = TestCase $ assertEqual "method assembly" expectedClassWithMethod (classBuilder classWithMethod emptyClassFile) -testFindMethodIndex = TestCase $ assertEqual "find method" (Just 0) (findMethodIndex expectedClassWithMethod "add_two_numbers") - -tests = TestList [ - TestLabel "Basic constant pool" testBasicConstantPool, - TestLabel "Fields constant pool" testFields, - TestLabel "Method descriptor building" testMethodDescriptor, - TestLabel "Method assembly" testMethodAssembly, - TestLabel "Find method by name" testFindMethodIndex - ] \ No newline at end of file diff --git a/Test/TestSuite.hs b/Test/TestSuite.hs index bf2c67e..cf8c1e7 100644 --- a/Test/TestSuite.hs +++ b/Test/TestSuite.hs @@ -2,13 +2,11 @@ module Main where import Test.HUnit import TestLexer -import TestByteCodeGenerator import TestParser - tests = TestList [ - TestLabel "TestLexer" TestLexer.tests, - TestLabel "TestParser" TestParser.tests, - TestLabel "TestByteCodeGenerator" TestByteCodeGenerator.tests] + TestLabel "TestLexer" TestLexer.tests, + TestLabel "TestParser" TestParser.tests + ] main = do runTestTTAndExit Main.tests \ No newline at end of file diff --git a/project.cabal b/project.cabal index 2ebf1b2..9b12543 100644 --- a/project.cabal +++ b/project.cabal @@ -10,7 +10,8 @@ executable compiler array, HUnit, utf8-string, - bytestring + bytestring, + filepath default-language: Haskell2010 hs-source-dirs: src build-tool-depends: alex:alex, happy:happy @@ -32,15 +33,15 @@ test-suite tests array, HUnit, utf8-string, - bytestring + bytestring, + filepath build-tool-depends: alex:alex, happy:happy other-modules: Parser.Lexer, Parser.JavaParser, Ast, TestLexer, TestParser, - TestByteCodeGenerator, ByteCode.ByteUtil, ByteCode.ClassFile, - ByteCode.ClassFile.Generator, + ByteCode.Generator, ByteCode.Constants diff --git a/src/ByteCode/Generator.hs b/src/ByteCode/Generator.hs index c0e5a92..e448fb3 100644 --- a/src/ByteCode/Generator.hs +++ b/src/ByteCode/Generator.hs @@ -2,9 +2,11 @@ module ByteCode.Generator( datatypeDescriptor, memberInfoName, memberInfoDescriptor, + methodDescriptor, returnOperation, binaryOperation, comparisonOperation, + findMethodIndex, ClassFileBuilder, Assembler, classBuilder @@ -443,7 +445,7 @@ assembleStatementExpression (constants_a, ops_a, _) = assembleExpression (constants, ops, lvars) expr isPrimitive = elem dtype ["char", "boolean", "int"] in case localIndex of - Just index -> (constants_a, ops_a ++ if isPrimitive then [Opistore (fromIntegral index)] else [Opastore (fromIntegral index)], lvars) + Just index -> (constants_a, ops_a ++ if isPrimitive then [Opdup, Opistore (fromIntegral index)] else [Opastore (fromIntegral index)], lvars) Nothing -> error ("No such local variable found in local variable pool: " ++ name) (TypedExpression dtype (FieldVariable name)) -> let owner = resolveNameChainOwner (TypedExpression dtype receiver) @@ -628,4 +630,4 @@ assembleMethod (constants, ops, lvars) (MethodDeclaration returntype name _ (Typ in (constants_a, ops_a ++ [Opreturn], lvars_a) otherwise -> foldl assembleStatement (constants, ops, lvars) statements -assembleMethod _ (MethodDeclaration _ _ _ stmt) = error ("Block expected for method body, got: " ++ show stmt) +assembleMethod _ (MethodDeclaration _ _ _ stmt) = error ("Typed block expected for method body, got: " ++ show stmt) diff --git a/src/Main.hs b/src/Main.hs index 200a9ed..27bf217 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -7,22 +7,25 @@ import Parser.JavaParser import ByteCode.Generator import ByteCode.ClassFile import Data.ByteString (pack, writeFile) +import System.Environment +import System.FilePath.Posix (takeDirectory) main = do - -- read source code from disk - file <- readFile "Testklasse.java" + args <- getArgs + let filename = if (length args) > 0 + then args!!0 + else error "Missing filename, I need to know what to compile" + let outputDirectory = takeDirectory filename + print ("Compiling " ++ filename) + file <- readFile filename - -- parse source code let untypedAST = parse $ alexScanTokens file - -- typecheck AST let typedAST = (typeCheckCompilationUnit untypedAST) - -- assemble classes let assembledClasses = map (\(typedClass) -> classBuilder typedClass emptyClassFile) typedAST - -- write class files to disk mapM_ (\(classFile) -> let fileContent = pack (serialize classFile) - fileName = (className classFile) ++ ".class" + fileName = outputDirectory ++ "/" ++ (className classFile) ++ ".class" in Data.ByteString.writeFile fileName fileContent ) assembledClasses