Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2141bd44cb | |||
| 3ac33af1b5 | |||
| 9046e3621b | |||
| 7c0f49ccca | |||
| 6e16a502aa | |||
| b45502a550 | |||
| 83ff583c1c | |||
| f19d6f6d39 | |||
| cbd7df780a | |||
| 9d405fac61 | |||
| dde92cc64d | |||
| c8261b361a | |||
| d0d37dd05e | |||
| e8ab0ed082 | |||
| 544774d8b9 | |||
| f2cc603690 | |||
| a0f5d736f9 | |||
| 8416f1b9c8 | |||
| 245e70169e | |||
| 8497b949a4 | |||
| 251d8daa0f | |||
| 1ad70c0b00 | |||
| 7f6e8f5cb5 | |||
| 88e9b545eb | |||
| 11fda5eaa7 | |||
| 1a153200fa | |||
| 815388b842 | |||
| 22e238317c | |||
| 5d48938221 | |||
| 44c1032e15 | |||
| 842149be5e | |||
| 48b6270fd0 | |||
| b668fc94ff | |||
| a829814cd6 | |||
| 66e0efba81 | |||
| 31248ecc97 | |||
| ac60aeba3e | |||
| b833d6ebe0 | |||
| 3f0e00ac63 | |||
| 4370ec3c1f | |||
| 162bed1b12 | |||
| e91fac7060 | |||
| 0eb8c64fc6 | |||
| f96627fcc9 | |||
| b5ddd0b5c9 | |||
| 531f5d7f7f | |||
| 5a16f08ccc | |||
| e5eb3a9792 | |||
| 8e3b978ba4 | |||
| 1b2ae2ae22 | |||
| d9b7ba40bc | |||
| 03e0571f4e | |||
| 4978de5220 | |||
| b80b71936a | |||
| 22de50b77e | |||
| e84550025c | |||
| 8470ab2d94 | |||
| 1ad8aface9 | |||
| daa741a090 | |||
| e81ceb454f | |||
| 87db9f6e23 | |||
| a447fad905 | |||
| b964bdda1b | |||
| 2f908d1df1 | |||
| 40e84e45f6 | |||
| 56d43bac38 | |||
| 9230042b25 | |||
| 76c4cb677c |
@@ -31,3 +31,9 @@ cabal.project.local~
|
||||
hie.yaml
|
||||
src/Grammar/Scanner.hs
|
||||
src/Grammar/Parser.hs
|
||||
src/Grammar/Parser.info
|
||||
src/Main
|
||||
*.class
|
||||
out/
|
||||
mine.txt
|
||||
ref.txt
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
# Compilerbau
|
||||
|
||||
# – Prüfungsleistung
|
||||
|
||||
## Spezifikation
|
||||
|
||||
Deklarationen: • Σ: Eingabe–Alphabet
|
||||
|
||||
- JC: Menge aller syntaktisch korrekten Java–Klassen mit folgenden Einschränkungen:
|
||||
- keine generischen Klassen
|
||||
- keine abstrakten Klassen
|
||||
- keine Vererbung
|
||||
- keine Interfaces
|
||||
- keine Threads
|
||||
- keine Exceptions
|
||||
- keine Arrays
|
||||
- als Basistypen sind nur int, boolean und char zugelassen
|
||||
- keine Packages
|
||||
- keine Imports
|
||||
- keine Lambda–Expressions
|
||||
- BC: Menge aller Bytecode–Files
|
||||
|
||||
Eingabe: p∈Σ∗
|
||||
|
||||
Vorbedingung: ∅
|
||||
|
||||
Ausgabe: bc∈BC∗∪{error}
|
||||
|
||||
Nachbedingungen: • falls p∈(JC)∗, so ist bc∈(BC)∗und p wird nach bc übersetzt wie es durch die Sprache Java definiert ist.
|
||||
|
||||
- falls p !∈(JC)∗, so ist bc=error.
|
||||
|
||||
## Vorgehen
|
||||
|
||||
Arbeitsteam:Der Java–Compiler wird in einem Team von 5-7 Personen erstellt. Das
|
||||
Team wird nochmals unterteilt:
|
||||
|
||||
|
||||
- Gemeinsame Aufgabe
|
||||
GIT-Repository: Einrichten eines GIT-Repositories auf den DHBW GITEA-Server
|
||||
Abstrakte Syntax: Aufbau der abstrakten Syntax aus dem Parsetree
|
||||
Dokumentation: Erstellen der Dokumentation
|
||||
- Scannen/Parsen/Grammatik (1–2 Personen)
|
||||
Scannen: alex–File oder Scanner von Hand programmieren
|
||||
Grammatik (nur bei Bearbeitung durch 2 Personen):Erstellen einer Mini-Java-Grammatik an Hand der Spezifikation
|
||||
Parsen:Erstellen des happy–Files oder des Kombinator–Parsers und Aufbau des abstrakten Syntaxbaums
|
||||
- Semantische Analyse:Typisierung der abstrakten Syntax(1 Person)
|
||||
Typisierung: Typisierung der abstrakten Syntax
|
||||
- Codeerzeugung (2-3 Personen):
|
||||
- Aufbau eines abstrakten ClassFiles (1 Person)
|
||||
- Konstantenpool (1 Person)
|
||||
- Nur bei Bearbeitung durch 3 Personen:Umwandlung des ClassFiles in Bytecode (1 Person)
|
||||
- Tester (1 Person)
|
||||
- Testsuite von Java–Files, die alle implementierten Features abdecken.
|
||||
- Händische Übersetzung aller Java-Files der Testsuite in die abstrakte Syntax (als Test–Eingaben für den Typ-Checker)
|
||||
- Händische Übersetzung aller Testfälle der abstrakten Syntax in getypte abstrakte Syntax (als Test–Eingaben für den Code-Generierer).
|
||||
- Händische Übersetzung aller Testfälle der getypten abstrakten Syntax in abstrakten Bytecode.
|
||||
- Automatische Tests, die die jeweiligen Testsuite mit den implementierten Funktionen des Teams vergleichen
|
||||
|
||||
## Prüfungsleistung
|
||||
|
||||
Die Arbeitsleistung wird bewertet an Hand
|
||||
|
||||
- des Gesamtergebnis des Teams
|
||||
- der Arbeitsleistung jeder/s Studierenden
|
||||
|
||||
an Hand folgender Kriterien:
|
||||
|
||||
- Projektergebnis
|
||||
- wöchentlicher Projektfortschritt
|
||||
- Mitarbeit im Team
|
||||
|
||||
Das Projektergebnis muss folgendes beinhalten:
|
||||
|
||||
- (Kurz)dokumentation aus der hervorgeht welche Leistung der jeweiligen Studierende erbrachten hat.
|
||||
- Im Teilprojekt muss folgendes vorliegen:
|
||||
- Eine Testsuite von Java–Programmen, für die der Compilerteil funktioniert.
|
||||
- Präsentation des Programms an Hand der erstellen Testsuites.
|
||||
- Durchgehendes Beispiel, fur das der gesamte Compiler funktioniert.
|
||||
- Abgabetermin: Letzte Semesterwoche
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
public class Runner { public static void main(String[] args) { FieldsTest f = new FieldsTest(); System.out.println(f.b); } }
|
||||
@@ -0,0 +1,228 @@
|
||||
module Codegen.ClassFile where
|
||||
|
||||
import Codegen.ConstPool
|
||||
( generateConstPool,
|
||||
lookupClassIndex,
|
||||
lookupFieldRefIndex,
|
||||
lookupUtf8Index,
|
||||
)
|
||||
import Codegen.Types
|
||||
import Data.Bits (shiftR, (.&.))
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Grammar.AST (Type)
|
||||
import Grammar.TAST
|
||||
( TypedClass (..),
|
||||
TypedExpr (..),
|
||||
TypedFieldDecl (..),
|
||||
TypedMethodDecl (..),
|
||||
)
|
||||
|
||||
data ClassFile = ClassFile
|
||||
{ magic :: Magic,
|
||||
minorVersion :: MinorVersion,
|
||||
majorVersion :: MajorVersion,
|
||||
constantPool :: CP_Infos,
|
||||
accessFlags :: AccessFlags,
|
||||
thisClass :: ThisClass,
|
||||
superClass :: SuperClass,
|
||||
interfaces :: Interfaces,
|
||||
fields :: Field_Infos,
|
||||
methods :: Method_Infos,
|
||||
attributes :: Attribute_Infos
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- Counts are derivable from list lengths, so no need to store them separately
|
||||
|
||||
type Index = Int
|
||||
|
||||
type Interfaces = [Word16] -- constant pool indices
|
||||
|
||||
type Field_Infos = [Field_Info]
|
||||
|
||||
type Method_Infos = [Method_Info]
|
||||
|
||||
type Attribute_Infos = [Attribute_Info]
|
||||
|
||||
newtype Magic = Magic Word32 deriving (Show, Eq) -- always 0xCAFEBABE
|
||||
|
||||
newtype MinorVersion = MinorVersion Int deriving (Show, Eq)
|
||||
|
||||
newtype MajorVersion = MajorVersion Int deriving (Show, Eq) -- 52 = Java 8
|
||||
|
||||
newtype AccessFlags = AccessFlags [AccessFlag] deriving (Show, Eq)
|
||||
|
||||
newtype ThisClass = ThisClass Index deriving (Show, Eq)
|
||||
|
||||
newtype SuperClass = SuperClass Index deriving (Show, Eq)
|
||||
|
||||
data AccessFlag
|
||||
= ACC_PUBLIC
|
||||
| ACC_PRIVATE
|
||||
| ACC_PROTECTED
|
||||
| ACC_STATIC
|
||||
| ACC_FINAL
|
||||
| ACC_SUPER -- always set on classes
|
||||
| ACC_INTERFACE
|
||||
| ACC_ABSTRACT
|
||||
| ACC_SYNTHETIC
|
||||
| ACC_ENUM
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Field_Info = Field_Info
|
||||
{ fieldAccessFlags :: AccessFlags,
|
||||
fieldNameIndex :: Index,
|
||||
fieldDescIndex :: Index,
|
||||
fieldAttributes :: Attribute_Infos
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Method_Info = Method_Info
|
||||
{ methodAccessFlags :: AccessFlags,
|
||||
methodNameIndex :: Index,
|
||||
methodDescIndex :: Index,
|
||||
methodAttributes :: Attribute_Infos
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
data Attribute_Info
|
||||
= Code_Attribute
|
||||
{ codeNameIndex :: Index,
|
||||
maxStack :: Int,
|
||||
maxLocals :: Int,
|
||||
codeBody :: [Word8], -- raw serialized bytecode
|
||||
exceptionTable :: [ExceptionEntry],
|
||||
codeAttributes :: Attribute_Infos
|
||||
}
|
||||
| Generic_Attribute
|
||||
{ attrNameIndex :: Index,
|
||||
attrData :: [Word8]
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
data ExceptionEntry = ExceptionEntry
|
||||
{ startPc :: Int,
|
||||
endPc :: Int,
|
||||
handlerPc :: Int,
|
||||
catchType :: Index -- 0 means catches all (finally)
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
generateClassFile :: TypedClass -> ClassFile
|
||||
generateClassFile cls =
|
||||
let constPool = makeConstpool cls
|
||||
in ClassFile
|
||||
{ magic = Magic 0xCAFEBABE,
|
||||
minorVersion = MinorVersion 0,
|
||||
majorVersion = MajorVersion 48,
|
||||
constantPool = constPool,
|
||||
accessFlags = makeAccessFlags,
|
||||
thisClass = makeThisClass constPool cls,
|
||||
superClass = makeSuperClass constPool,
|
||||
interfaces = makeInterface,
|
||||
fields = makeFields constPool cls,
|
||||
methods = makeMethods constPool cls,
|
||||
attributes = makeAttributes constPool
|
||||
}
|
||||
|
||||
makeConstpool :: TypedClass -> CP_Infos
|
||||
makeConstpool = generateConstPool
|
||||
|
||||
makeAccessFlags :: AccessFlags
|
||||
makeAccessFlags = AccessFlags [ACC_PUBLIC, ACC_SUPER]
|
||||
|
||||
makeThisClass :: CP_Infos -> TypedClass -> ThisClass
|
||||
makeThisClass constPool (Class name _ _) =
|
||||
ThisClass (lookupClassIndex constPool name)
|
||||
|
||||
makeSuperClass :: CP_Infos -> SuperClass
|
||||
makeSuperClass constPool =
|
||||
SuperClass (lookupClassIndex constPool "java/lang/Object")
|
||||
|
||||
makeInterface :: Interfaces
|
||||
makeInterface = []
|
||||
|
||||
makeFields :: CP_Infos -> TypedClass -> Field_Infos
|
||||
makeFields constPool (Class _ fields _) = map (makeField constPool) fields
|
||||
|
||||
makeField :: CP_Infos -> TypedFieldDecl -> Field_Info
|
||||
makeField constPool (Field fieldType name _) =
|
||||
Field_Info
|
||||
{ fieldAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
fieldNameIndex = lookupUtf8Index constPool name,
|
||||
fieldDescIndex = lookupUtf8Index constPool (typeDescriptor fieldType),
|
||||
fieldAttributes = []
|
||||
}
|
||||
|
||||
makeAttributes :: CP_Infos -> Attribute_Infos
|
||||
makeAttributes _ = []
|
||||
|
||||
makeMethods :: CP_Infos -> TypedClass -> Method_Infos
|
||||
makeMethods constPool (Class _ fields methods) =
|
||||
makeInitMethod constPool fields : map (makeMethod constPool) methods
|
||||
|
||||
makeInitMethod :: CP_Infos -> [TypedFieldDecl] -> Method_Info
|
||||
makeInitMethod constPool fields =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool "<init>",
|
||||
methodDescIndex = lookupUtf8Index constPool "()V",
|
||||
methodAttributes = [makeInitCode constPool fields]
|
||||
}
|
||||
|
||||
makeInitCode :: CP_Infos -> [TypedFieldDecl] -> Attribute_Info
|
||||
makeInitCode constPool fields =
|
||||
Code_Attribute
|
||||
{ codeNameIndex = lookupUtf8Index constPool "Code",
|
||||
maxStack = if null initFields then 1 else 2,
|
||||
maxLocals = 1,
|
||||
codeBody = [0x2A, 0xB7, 0x00, 0x08] ++ fieldInits ++ [0xB1],
|
||||
exceptionTable = [],
|
||||
codeAttributes = []
|
||||
}
|
||||
where
|
||||
initFields = [f | f@(Field _ _ (Just _)) <- fields]
|
||||
fieldInits = concatMap (fieldInitBytes constPool) initFields
|
||||
|
||||
fieldInitBytes :: CP_Infos -> TypedFieldDecl -> [Word8]
|
||||
fieldInitBytes constPool (Field _ name (Just expr)) =
|
||||
let idx = lookupFieldRefIndex constPool ("field ref: " ++ name)
|
||||
in [0x2A]
|
||||
++ pushExpr expr
|
||||
++ [0xB5, fromIntegral (idx `shiftR` 8 .&. 0xFF), fromIntegral (idx .&. 0xFF)]
|
||||
fieldInitBytes _ _ = []
|
||||
|
||||
pushExpr :: TypedExpr -> [Word8]
|
||||
pushExpr (Integer n _) = pushInt (fromIntegral n)
|
||||
pushExpr (Bool True _) = [0x04]
|
||||
pushExpr (Bool False _) = [0x03]
|
||||
pushExpr (Char c _) = pushInt (fromEnum c)
|
||||
pushExpr _ = []
|
||||
|
||||
pushInt :: Int -> [Word8]
|
||||
pushInt n
|
||||
| n >= -1 && n <= 5 = [fromIntegral (0x03 + n)]
|
||||
| n >= -128 && n <= 127 = [0x10, fromIntegral n]
|
||||
| n >= -32768 && n <= 32767 = [0x11, fromIntegral (n `shiftR` 8 .&. 0xFF), fromIntegral (n .&. 0xFF)]
|
||||
| otherwise = [0x10, 0]
|
||||
|
||||
makeMethod :: CP_Infos -> TypedMethodDecl -> Method_Info
|
||||
makeMethod constPool (Method returnType name params _body) =
|
||||
Method_Info
|
||||
{ methodAccessFlags = AccessFlags [ACC_PUBLIC],
|
||||
methodNameIndex = lookupUtf8Index constPool name,
|
||||
methodDescIndex = lookupUtf8Index constPool (methodDescriptor params returnType),
|
||||
methodAttributes = []
|
||||
}
|
||||
|
||||
methodDescriptor :: [(Type, String)] -> Type -> String
|
||||
methodDescriptor params returnType =
|
||||
"(" ++ concatMap (typeDescriptor . fst) params ++ ")" ++ typeDescriptor returnType
|
||||
|
||||
typeDescriptor :: Type -> String
|
||||
typeDescriptor t = case t of
|
||||
"int" -> "I"
|
||||
"boolean" -> "Z"
|
||||
"char" -> "C"
|
||||
"void" -> "V"
|
||||
_ -> "L" ++ t ++ ";"
|
||||
@@ -0,0 +1,267 @@
|
||||
module Codegen.ConstPool where
|
||||
|
||||
import Codegen.Types
|
||||
import Data.List (isSuffixOf)
|
||||
import qualified Grammar.TAST as TAST
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive extraction of Tags from an already-built pool
|
||||
------------------------------------------------------------------------
|
||||
|
||||
extractTags :: CP_Infos -> [Tag]
|
||||
extractTags [] = []
|
||||
extractTags (x : xs) = tag_cp x : extractTags xs
|
||||
|
||||
countEntries :: CP_Infos -> Int
|
||||
countEntries [] = 0
|
||||
countEntries (_ : xs) = 1 + countEntries xs
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Helper constructors
|
||||
------------------------------------------------------------------------
|
||||
|
||||
mkClassInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkClassInfo = ClassInfo TagClass
|
||||
|
||||
mkFieldRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkFieldRefInfo = FieldRefInfo TagFieldRef
|
||||
|
||||
mkMethodRefInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkMethodRefInfo = MethodRefInfo TagMethodRef
|
||||
|
||||
mkStringInfo :: IndexConstantPool -> String -> CP_Info
|
||||
mkStringInfo = StringInfo TagString
|
||||
|
||||
mkIntegerInfo :: Int -> String -> CP_Info
|
||||
mkIntegerInfo = IntegerInfo TagInteger
|
||||
|
||||
mkNameAndTypeInfo :: IndexConstantPool -> IndexConstantPool -> String -> CP_Info
|
||||
mkNameAndTypeInfo = NameAndTypeInfo TagNameAndType
|
||||
|
||||
mkUtf8Info :: String -> String -> CP_Info
|
||||
mkUtf8Info str = Utf8Info TagUtf8 (length str) str
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Tag utilities
|
||||
------------------------------------------------------------------------
|
||||
|
||||
tagToByte :: Tag -> Int
|
||||
tagToByte TagClass = 7
|
||||
tagToByte TagFieldRef = 9
|
||||
tagToByte TagMethodRef = 10
|
||||
tagToByte TagString = 8
|
||||
tagToByte TagInteger = 3
|
||||
tagToByte TagNameAndType = 12
|
||||
tagToByte TagUtf8 = 1
|
||||
|
||||
tagToString :: Tag -> String
|
||||
tagToString TagClass = "Class"
|
||||
tagToString TagFieldRef = "FieldRef"
|
||||
tagToString TagMethodRef = "MethodRef"
|
||||
tagToString TagString = "String"
|
||||
tagToString TagInteger = "Integer"
|
||||
tagToString TagNameAndType = "NameAndType"
|
||||
tagToString TagUtf8 = "Utf8"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Descriptor helpers
|
||||
------------------------------------------------------------------------
|
||||
|
||||
typeToDescriptor :: String -> String
|
||||
typeToDescriptor "int" = "I"
|
||||
typeToDescriptor "boolean" = "Z"
|
||||
typeToDescriptor "char" = "C"
|
||||
typeToDescriptor "void" = "V"
|
||||
typeToDescriptor t = "L" ++ t ++ ";"
|
||||
|
||||
methodDescriptor :: [(String, String)] -> String -> String
|
||||
methodDescriptor params ret =
|
||||
"(" ++ paramsDesc params ++ ")" ++ typeToDescriptor ret
|
||||
where
|
||||
paramsDesc [] = ""
|
||||
paramsDesc ((pty, _) : ps) = typeToDescriptor pty ++ paramsDesc ps
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Recursive traversal of TAST to collect CP_Info entries
|
||||
------------------------------------------------------------------------
|
||||
|
||||
collectFromExprs :: [TAST.TypedExpr] -> CP_Infos
|
||||
collectFromExprs [] = []
|
||||
collectFromExprs (e : es) = collectFromTypedExpr e ++ collectFromExprs es
|
||||
|
||||
collectFromTypedExpr :: TAST.TypedExpr -> CP_Infos
|
||||
collectFromTypedExpr (TAST.Integer n _) =
|
||||
[mkIntegerInfo (fromIntegral n) ("int " ++ show n)]
|
||||
collectFromTypedExpr (TAST.Bool _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.Char c _) =
|
||||
[mkIntegerInfo (fromEnum c) ("char " ++ show c)]
|
||||
collectFromTypedExpr (TAST.Null _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.This _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.LocalOrFieldVar _ _) =
|
||||
[]
|
||||
collectFromTypedExpr (TAST.InstVar obj name typ) =
|
||||
collectFromTypedExpr obj
|
||||
++ [ mkUtf8Info name ("field name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor typ) ("field descriptor: " ++ typeToDescriptor typ),
|
||||
mkNameAndTypeInfo 0 0 ("field NameAndType: " ++ name),
|
||||
mkFieldRefInfo 0 0 ("field ref: " ++ name)
|
||||
]
|
||||
collectFromTypedExpr (TAST.Unary _ e _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedExpr (TAST.Binary _ e1 e2 _) =
|
||||
collectFromTypedExpr e1 ++ collectFromTypedExpr e2
|
||||
collectFromTypedExpr (TAST.StmtExprExpr se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromTypedStmtExpr :: TAST.TypedStmtExpr -> CP_Infos
|
||||
collectFromTypedStmtExpr (TAST.Assign lhs rhs _) =
|
||||
collectFromTypedExpr lhs ++ collectFromTypedExpr rhs
|
||||
collectFromTypedStmtExpr (TAST.New t args _) =
|
||||
collectFromExprs args
|
||||
++ [ mkUtf8Info t ("class name: " ++ t),
|
||||
mkClassInfo 0 ("class: " ++ t)
|
||||
]
|
||||
collectFromTypedStmtExpr (TAST.MethodCall obj name args retTyp) =
|
||||
collectFromTypedExpr obj
|
||||
++ collectFromExprs args
|
||||
++ [ mkUtf8Info name ("method name: " ++ name),
|
||||
mkUtf8Info (typeToDescriptor retTyp) ("method ret desc: " ++ typeToDescriptor retTyp),
|
||||
mkNameAndTypeInfo 0 0 ("method NameAndType: " ++ name),
|
||||
mkMethodRefInfo 0 0 ("method ref: " ++ name)
|
||||
]
|
||||
|
||||
collectFromStmts :: [TAST.TypedStmt] -> CP_Infos
|
||||
collectFromStmts [] = []
|
||||
collectFromStmts (s : ss) = collectFromTypedStmt s ++ collectFromStmts ss
|
||||
|
||||
collectFromTypedStmt :: TAST.TypedStmt -> CP_Infos
|
||||
collectFromTypedStmt (TAST.Block stmts _) =
|
||||
collectFromStmts stmts
|
||||
collectFromTypedStmt (TAST.Return (Just e) _) =
|
||||
collectFromTypedExpr e
|
||||
collectFromTypedStmt (TAST.Return Nothing _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.While cond body _) =
|
||||
collectFromTypedExpr cond ++ collectFromTypedStmt body
|
||||
collectFromTypedStmt (TAST.LocalVarDecl _ _ _) =
|
||||
[]
|
||||
collectFromTypedStmt (TAST.If cond then_ mElse _) =
|
||||
collectFromTypedExpr cond
|
||||
++ collectFromTypedStmt then_
|
||||
++ collectFromMaybeStmt mElse
|
||||
collectFromTypedStmt (TAST.StmtExprStmt se _) =
|
||||
collectFromTypedStmtExpr se
|
||||
|
||||
collectFromMaybeStmt :: Maybe TAST.TypedStmt -> CP_Infos
|
||||
collectFromMaybeStmt Nothing = []
|
||||
collectFromMaybeStmt (Just s) = collectFromTypedStmt s
|
||||
|
||||
collectFromFields :: [TAST.TypedFieldDecl] -> CP_Infos
|
||||
collectFromFields [] = []
|
||||
collectFromFields (TAST.Field fType fName _: fs ) =
|
||||
[ mkUtf8Info fName ("field: " ++ fName),
|
||||
|
||||
mkUtf8Info (typeToDescriptor fType) ("field desc: " ++ fType)
|
||||
]
|
||||
++ collectFromFields fs
|
||||
|
||||
collectFromMethods :: [TAST.TypedMethodDecl] -> CP_Infos
|
||||
collectFromMethods [] = []
|
||||
collectFromMethods (TAST.Method mType mName params body : ms) =
|
||||
[ mkUtf8Info mName ("method: " ++ mName),
|
||||
mkUtf8Info (methodDescriptor params mType) ("method desc: " ++ mName)
|
||||
]
|
||||
++ collectFromTypedStmt body
|
||||
++ collectFromMethods ms
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Build the complete constant pool for a class
|
||||
------------------------------------------------------------------------
|
||||
|
||||
generateConstPool :: TAST.TypedClass -> CP_Infos
|
||||
generateConstPool (TAST.Class cName fields methods) =
|
||||
[ mkUtf8Info cName ("this class: " ++ cName), -- #1
|
||||
mkUtf8Info "java/lang/Object" "super class", -- #2
|
||||
mkClassInfo 1 ("this class: " ++ cName), -- #3 → #1
|
||||
mkClassInfo 2 "super class", -- #4 → #2
|
||||
mkUtf8Info "<init>" "<init>", -- #5
|
||||
mkUtf8Info "()V" "init descriptor", -- #6
|
||||
mkNameAndTypeInfo 5 6 "<init>:()V", -- #7
|
||||
mkMethodRefInfo 4 7 "java/lang/Object.<init>:()V", -- #8
|
||||
mkUtf8Info "Code" "Code" -- #9
|
||||
]
|
||||
++ collectFromFields fields
|
||||
++ buildInitFieldPool fields (length fields)
|
||||
++ collectFromMethods methods
|
||||
|
||||
-- Build NameAndType + Fieldref entries for each field with an initializer.
|
||||
-- The base pool has 9 fixed entries; collectFromFields adds 2 per field.
|
||||
-- So for field at 0-based index i: name Utf8 = #(9 + 2i + 1), desc = #(9 + 2i + 2).
|
||||
-- NameAndType entries follow at #(9 + 2*numFields + 2j + 1) for j-th init field.
|
||||
buildInitFieldPool :: [TAST.TypedFieldDecl] -> Int -> CP_Infos
|
||||
buildInitFieldPool fields numFields = go 0 0 fields
|
||||
where
|
||||
go _ _ [] = []
|
||||
go fieldIdx initIdx (TAST.Field _ _ Nothing : rest) =
|
||||
go (fieldIdx + 1) initIdx rest
|
||||
go fieldIdx initIdx (TAST.Field _ name _ : rest) =
|
||||
let nameUtf8Idx = 9 + 2 * fieldIdx + 1
|
||||
descUtf8Idx = 9 + 2 * fieldIdx + 2
|
||||
natCpIdx = 9 + 2 * numFields + 2 * initIdx + 1
|
||||
in [ mkNameAndTypeInfo nameUtf8Idx descUtf8Idx ("field NameAndType: " ++ name),
|
||||
mkFieldRefInfo 3 natCpIdx ("field ref: " ++ name)
|
||||
]
|
||||
++ go (fieldIdx + 1) (initIdx + 1) rest
|
||||
|
||||
lookupClassIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupClassIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
ClassInfo {desc = descVal}
|
||||
| name == "java/lang/Object" -> descVal == "super class" || isSuffixOf name descVal
|
||||
| otherwise -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupUtf8Index :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupUtf8Index pool value =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
Utf8Info {cad_cp = cadVal, desc = descVal} -> cadVal == value || isSuffixOf value descVal
|
||||
_ -> False
|
||||
|
||||
lookupNameAndTypeIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupNameAndTypeIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> lookupUtf8Index pool name
|
||||
where
|
||||
matches entry = case entry of
|
||||
NameAndTypeInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
lookupFieldRefIndex :: CP_Infos -> String -> IndexConstantPool
|
||||
lookupFieldRefIndex pool name =
|
||||
case findIndex matches pool of
|
||||
Just i -> i
|
||||
Nothing -> 0
|
||||
where
|
||||
matches entry = case entry of
|
||||
FieldRefInfo {desc = descVal} -> descVal == name || isSuffixOf name descVal
|
||||
_ -> False
|
||||
|
||||
findIndex :: (CP_Info -> Bool) -> CP_Infos -> Maybe IndexConstantPool
|
||||
findIndex predicate pool = go 1 pool
|
||||
where
|
||||
go _ [] = Nothing
|
||||
go i (x : xs)
|
||||
| predicate x = Just i
|
||||
| otherwise = go (i + 1) xs
|
||||
@@ -0,0 +1,139 @@
|
||||
module Codegen.Lowerer where
|
||||
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Types (CP_Info (..), CP_Infos)
|
||||
import Data.Word (Word8)
|
||||
import Numeric (showHex)
|
||||
|
||||
data Instruction
|
||||
= ALoad Int
|
||||
| ALoad0
|
||||
| ILoad Int
|
||||
| AStore Int
|
||||
| IStore Int
|
||||
| GetField Int
|
||||
| PutField Int
|
||||
| InvokeSpecial Int
|
||||
| InvokeVirtual Int
|
||||
| InvokeStatic Int
|
||||
| IConst Int
|
||||
| AConstNull
|
||||
| Ldc Int
|
||||
| IAdd
|
||||
| ISub
|
||||
| IMul
|
||||
| IfEq Int
|
||||
| IfNe Int
|
||||
| Goto Int
|
||||
| Return
|
||||
| IReturn
|
||||
| AReturn
|
||||
deriving (Show, Eq)
|
||||
|
||||
data BtcLine = BtcLine
|
||||
{ lineNumber :: Integer,
|
||||
instruction :: Instruction
|
||||
}
|
||||
deriving (Show, Eq)
|
||||
|
||||
newtype BtcProgram = BtcProgram [BtcLine]
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- Lower a full ClassFile into a BtcProgram per method
|
||||
-- Returns one BtcProgram per method, paired with its name
|
||||
lowerClassFile :: ClassFile -> [(String, BtcProgram)]
|
||||
lowerClassFile cf =
|
||||
[ (methodName cf m, lowerMethod m)
|
||||
| m <- methods cf
|
||||
]
|
||||
|
||||
-- Extract method name from the constant pool
|
||||
methodName :: ClassFile -> Method_Info -> String
|
||||
methodName cf m =
|
||||
case lookupPool (constantPool cf) (methodNameIndex m) of
|
||||
Just (Utf8Info {cad_cp = name}) -> name
|
||||
_ -> "<unknown>"
|
||||
|
||||
-- Lower a single method's Code attribute into a BtcProgram
|
||||
lowerMethod :: Method_Info -> BtcProgram
|
||||
lowerMethod m =
|
||||
case findCodeAttr (methodAttributes m) of
|
||||
Just attr -> lowerCode attr
|
||||
Nothing -> BtcProgram [] -- abstract/native method, no body
|
||||
|
||||
findCodeAttr :: Attribute_Infos -> Maybe Attribute_Info
|
||||
findCodeAttr = foldr step Nothing
|
||||
where
|
||||
step attr@Code_Attribute {} _ = Just attr
|
||||
step _ acc = acc
|
||||
|
||||
-- Reconstruct BtcProgram from raw code bytes
|
||||
-- This is the tricky direction — bytes → instructions
|
||||
lowerCode :: Attribute_Info -> BtcProgram
|
||||
lowerCode attr = BtcProgram (decodeBytes 0 (codeBody attr))
|
||||
|
||||
decodeBytes :: Int -> [Word8] -> [BtcLine]
|
||||
decodeBytes _ [] = []
|
||||
decodeBytes pos (b : bs) = case b of
|
||||
0x2A -> line ALoad0 pos 1 bs
|
||||
0x2B -> line (ALoad 1) pos 1 bs
|
||||
0x2C -> line (ALoad 2) pos 1 bs
|
||||
0x2D -> line (ALoad 3) pos 1 bs
|
||||
0x19 -> withByte bs pos $ \n rest -> line (ALoad n) pos 2 rest
|
||||
0x1A -> line (ILoad 0) pos 1 bs
|
||||
0x1B -> line (ILoad 1) pos 1 bs
|
||||
0x1C -> line (ILoad 2) pos 1 bs
|
||||
0x1D -> line (ILoad 3) pos 1 bs
|
||||
0x15 -> withByte bs pos $ \n rest -> line (ILoad n) pos 2 rest
|
||||
0x4B -> line (AStore 0) pos 1 bs
|
||||
0x4C -> line (AStore 1) pos 1 bs
|
||||
0x4D -> line (AStore 2) pos 1 bs
|
||||
0x4E -> line (AStore 3) pos 1 bs
|
||||
0x3A -> withByte bs pos $ \n rest -> line (AStore n) pos 2 rest
|
||||
0x3B -> line (IStore 0) pos 1 bs
|
||||
0x3C -> line (IStore 1) pos 1 bs
|
||||
0x3D -> line (IStore 2) pos 1 bs
|
||||
0x3E -> line (IStore 3) pos 1 bs
|
||||
0x36 -> withByte bs pos $ \n rest -> line (IStore n) pos 2 rest
|
||||
0xB4 -> withIndex bs pos $ \i rest -> line (GetField i) pos 3 rest
|
||||
0xB5 -> withIndex bs pos $ \i rest -> line (PutField i) pos 3 rest
|
||||
0xB7 -> withIndex bs pos $ \i rest -> line (InvokeSpecial i) pos 3 rest
|
||||
0xB6 -> withIndex bs pos $ \i rest -> line (InvokeVirtual i) pos 3 rest
|
||||
0xB8 -> withIndex bs pos $ \i rest -> line (InvokeStatic i) pos 3 rest
|
||||
0x02 -> line (IConst (-1)) pos 1 bs
|
||||
0x03 -> line (IConst 0) pos 1 bs
|
||||
0x04 -> line (IConst 1) pos 1 bs
|
||||
0x05 -> line (IConst 2) pos 1 bs
|
||||
0x06 -> line (IConst 3) pos 1 bs
|
||||
0x07 -> line (IConst 4) pos 1 bs
|
||||
0x08 -> line (IConst 5) pos 1 bs
|
||||
0x01 -> line AConstNull pos 1 bs
|
||||
0x12 -> withByte bs pos $ \i rest -> line (Ldc i) pos 2 rest
|
||||
0x13 -> withIndex bs pos $ \i rest -> line (Ldc i) pos 3 rest
|
||||
0x60 -> line IAdd pos 1 bs
|
||||
0x64 -> line ISub pos 1 bs
|
||||
0x68 -> line IMul pos 1 bs
|
||||
0x99 -> withIndex bs pos $ \i rest -> line (IfEq i) pos 3 rest
|
||||
0x9A -> withIndex bs pos $ \i rest -> line (IfNe i) pos 3 rest
|
||||
0xA7 -> withIndex bs pos $ \i rest -> line (Goto i) pos 3 rest
|
||||
0xB1 -> line Return pos 1 bs
|
||||
0xAC -> line IReturn pos 1 bs
|
||||
0xB0 -> line AReturn pos 1 bs
|
||||
unknown -> error $ "Unknown opcode 0x" ++ showHex unknown "" ++ " at position " ++ show pos
|
||||
|
||||
-- Helpers
|
||||
line :: Instruction -> Int -> Int -> [Word8] -> [BtcLine]
|
||||
line instr pos size rest = BtcLine (toInteger pos) instr : decodeBytes (pos + size) rest
|
||||
|
||||
withByte :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
|
||||
withByte (b : rest) _ f = f (fromIntegral b) rest
|
||||
withByte [] pos _ = error $ "Unexpected end of bytecode at " ++ show pos
|
||||
|
||||
withIndex :: [Word8] -> Int -> (Int -> [Word8] -> [BtcLine]) -> [BtcLine]
|
||||
withIndex (hi : lo : rest) _ f = f (fromIntegral hi * 256 + fromIntegral lo) rest
|
||||
withIndex _ pos _ = error $ "Unexpected end of bytecode at " ++ show pos
|
||||
|
||||
lookupPool :: CP_Infos -> Index -> Maybe CP_Info
|
||||
lookupPool pool i
|
||||
| i < 1 || i > length pool = Nothing
|
||||
| otherwise = Just (pool !! (i - 1)) -- pool is 1-indexed
|
||||
@@ -0,0 +1,131 @@
|
||||
module Codegen.Serializer where
|
||||
|
||||
import Codegen.ClassFile
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
import Codegen.Types
|
||||
import Data.Bits (shiftR, (.&.), (.|.))
|
||||
import Data.Char (ord)
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
|
||||
-- Split a 16-bit Int into two bytes (big-endian), as JVM expects
|
||||
indexBytes :: Int -> [Word8]
|
||||
indexBytes n =
|
||||
[ fromIntegral (n `shiftR` 8 .&. 0xFF),
|
||||
fromIntegral (n .&. 0xFF)
|
||||
]
|
||||
|
||||
serializeInstruction :: Instruction -> [Word8]
|
||||
serializeInstruction instr = case instr of
|
||||
-- aload_<n>: 0x2A–0x2D for 0–3, else wide form 0x19 <index>
|
||||
ALoad 0 -> [0x2A]
|
||||
ALoad 1 -> [0x2B]
|
||||
ALoad 2 -> [0x2C]
|
||||
ALoad 3 -> [0x2D]
|
||||
ALoad n -> [0x19, fromIntegral n]
|
||||
ALoad0 -> [0x2A] -- shorthand for ALoad 0
|
||||
|
||||
-- iload_<n>: 0x1A–0x1D for 0–3, else wide form 0x15 <index>
|
||||
ILoad 0 -> [0x1A]
|
||||
ILoad 1 -> [0x1B]
|
||||
ILoad 2 -> [0x1C]
|
||||
ILoad 3 -> [0x1D]
|
||||
ILoad n -> [0x15, fromIntegral n]
|
||||
-- astore_<n>: 0x4B–0x4E for 0–3, else 0x3A <index>
|
||||
AStore 0 -> [0x4B]
|
||||
AStore 1 -> [0x4C]
|
||||
AStore 2 -> [0x4D]
|
||||
AStore 3 -> [0x4E]
|
||||
AStore n -> [0x3A, fromIntegral n]
|
||||
-- istore_<n>: 0x3B–0x3E for 0–3, else 0x36 <index>
|
||||
IStore 0 -> [0x3B]
|
||||
IStore 1 -> [0x3C]
|
||||
IStore 2 -> [0x3D]
|
||||
IStore 3 -> [0x3E]
|
||||
IStore n -> [0x36, fromIntegral n]
|
||||
-- Field access: opcode + 2-byte constant pool index
|
||||
GetField i -> 0xB4 : indexBytes i
|
||||
PutField i -> 0xB5 : indexBytes i
|
||||
-- Method invocation: opcode + 2-byte constant pool index
|
||||
InvokeSpecial i -> 0xB7 : indexBytes i
|
||||
InvokeVirtual i -> 0xB6 : indexBytes i
|
||||
InvokeStatic i -> 0xB8 : indexBytes i
|
||||
-- iconst_<n>: -1 through 5 have dedicated opcodes
|
||||
IConst (-1) -> [0x02]
|
||||
IConst 0 -> [0x03]
|
||||
IConst 1 -> [0x04]
|
||||
IConst 2 -> [0x05]
|
||||
IConst 3 -> [0x06]
|
||||
IConst 4 -> [0x07]
|
||||
IConst 5 -> [0x08]
|
||||
IConst n -> error $ "IConst out of range (-1..5): " ++ show n
|
||||
AConstNull -> [0x01]
|
||||
-- ldc: 1-byte index (use ldc_w 0x13 for large pool indices > 255)
|
||||
Ldc i
|
||||
| i <= 255 -> [0x12, fromIntegral i]
|
||||
| otherwise -> 0x13 : indexBytes i -- ldc_w
|
||||
|
||||
-- Arithmetic
|
||||
IAdd -> [0x60]
|
||||
ISub -> [0x64]
|
||||
IMul -> [0x68]
|
||||
-- Control flow: 2-byte signed branch offset
|
||||
IfEq offset -> 0x99 : indexBytes offset
|
||||
IfNe offset -> 0x9A : indexBytes offset
|
||||
Goto offset -> 0xA7 : indexBytes offset
|
||||
-- Return
|
||||
Return -> [0xB1]
|
||||
IReturn -> [0xAC]
|
||||
AReturn -> [0xB0]
|
||||
|
||||
-- Serialize a full program — concatenate all instruction bytes
|
||||
serializeProgram :: BtcProgram -> [Word8]
|
||||
serializeProgram (BtcProgram lines) =
|
||||
concatMap (serializeInstruction . instruction) lines
|
||||
|
||||
serializeClassFile :: ClassFile -> [Word8]
|
||||
serializeClassFile cf =
|
||||
u4 (let Magic m = magic cf in m)
|
||||
++ u2 (let MinorVersion v = minorVersion cf in v)
|
||||
++ u2 (let MajorVersion v = majorVersion cf in v)
|
||||
++ u2 (length (constantPool cf) + 1)
|
||||
++ concatMap cpEntry (constantPool cf)
|
||||
++ aflags (accessFlags cf)
|
||||
++ u2 (let ThisClass i = thisClass cf in i)
|
||||
++ u2 (let SuperClass i = superClass cf in i)
|
||||
++ u2 (length (interfaces cf))
|
||||
++ concatMap (\i -> u2 (fromIntegral (i :: Word16) :: Int)) (interfaces cf)
|
||||
++ u2 (length (fields cf)) ++ concatMap fieldEntry (fields cf)
|
||||
++ u2 (length (methods cf)) ++ concatMap methodEntry (methods cf)
|
||||
++ u2 (length (attributes cf)) ++ concatMap attrEntry (attributes cf)
|
||||
where
|
||||
u2 :: Int -> [Word8]
|
||||
u2 n = [fromIntegral ((n `shiftR` 8) .&. 0xFF), fromIntegral (n .&. 0xFF)]
|
||||
u4 :: (Integral a) => a -> [Word8]
|
||||
u4 n = let w = fromIntegral n :: Word32
|
||||
in map (\s -> fromIntegral ((w `shiftR` s) .&. 0xFF)) [24,16,8,0]
|
||||
aflags (AccessFlags fs) = u2 (foldr (\f a -> a .|. fval f) (0 :: Int) fs)
|
||||
fval :: AccessFlag -> Int
|
||||
fval ACC_PUBLIC = 0x0001; fval ACC_PRIVATE = 0x0002
|
||||
fval ACC_PROTECTED = 0x0004; fval ACC_STATIC = 0x0008
|
||||
fval ACC_FINAL = 0x0010; fval ACC_SUPER = 0x0020
|
||||
fval ACC_INTERFACE = 0x0200; fval ACC_ABSTRACT = 0x0400
|
||||
fval ACC_SYNTHETIC = 0x1000; fval ACC_ENUM = 0x4000
|
||||
cpEntry (Utf8Info _ _ s _) = [1] ++ u2 (length s) ++ map (fromIntegral . ord) s
|
||||
cpEntry (ClassInfo _ i _) = [7] ++ u2 i
|
||||
cpEntry (FieldRefInfo _ c n _) = [9] ++ u2 c ++ u2 n
|
||||
cpEntry (MethodRefInfo _ c n _) = [10] ++ u2 c ++ u2 n
|
||||
cpEntry (StringInfo _ i _) = [8] ++ u2 i
|
||||
cpEntry (IntegerInfo _ v _) = [3] ++ u4 v
|
||||
cpEntry (NameAndTypeInfo _ n d _) = [12] ++ u2 n ++ u2 d
|
||||
fieldEntry fi = aflags (fieldAccessFlags fi) ++ u2 (fieldNameIndex fi)
|
||||
++ u2 (fieldDescIndex fi) ++ u2 (0 :: Int)
|
||||
methodEntry mi = aflags (methodAccessFlags mi) ++ u2 (methodNameIndex mi)
|
||||
++ u2 (methodDescIndex mi) ++ u2 (length (methodAttributes mi))
|
||||
++ concatMap attrEntry (methodAttributes mi)
|
||||
attrEntry (Code_Attribute ni ms ml code exc ca) =
|
||||
let body = u2 ms ++ u2 ml ++ u4 (length code) ++ code
|
||||
++ u2 (length exc) ++ concatMap excEntry exc
|
||||
++ u2 (length ca) ++ concatMap attrEntry ca
|
||||
in u2 ni ++ u4 (length body) ++ body
|
||||
attrEntry (Generic_Attribute ni dat) = u2 ni ++ u4 (length dat) ++ dat
|
||||
excEntry e = u2 (startPc e) ++ u2 (endPc e) ++ u2 (handlerPc e) ++ u2 (catchType e)
|
||||
@@ -0,0 +1,57 @@
|
||||
module Codegen.Types where
|
||||
|
||||
type IndexConstantPool = Int
|
||||
|
||||
type CP_Infos = [CP_Info]
|
||||
|
||||
data CP_Info
|
||||
= ClassInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| FieldRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| MethodRefInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_nameandtype_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| StringInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| IntegerInfo
|
||||
{ tag_cp :: Tag,
|
||||
numi_cp :: Int,
|
||||
desc :: String
|
||||
}
|
||||
| NameAndTypeInfo
|
||||
{ tag_cp :: Tag,
|
||||
index_name_cp :: IndexConstantPool,
|
||||
index_descr_cp :: IndexConstantPool,
|
||||
desc :: String
|
||||
}
|
||||
| Utf8Info
|
||||
{ tag_cp :: Tag,
|
||||
tam_cp :: Int,
|
||||
cad_cp :: String,
|
||||
desc :: String
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Tag
|
||||
= TagClass
|
||||
| TagFieldRef
|
||||
| TagMethodRef
|
||||
| TagString
|
||||
| TagInteger
|
||||
| TagNameAndType
|
||||
| TagUtf8
|
||||
deriving (Eq, Ord, Show)
|
||||
+2
-2
@@ -6,7 +6,7 @@ type Program = [Class]
|
||||
|
||||
data Class = Class Type [FieldDecl] [MethodDecl] deriving (Show, Eq)
|
||||
|
||||
data FieldDecl = Field Type String deriving (Show, Eq)
|
||||
data FieldDecl = Field Type String (Maybe Expr) deriving (Show, Eq)
|
||||
|
||||
data MethodDecl = Method Type String [(Type, String)] Stmt deriving (Show, Eq)
|
||||
|
||||
@@ -19,7 +19,7 @@ data Expr
|
||||
| Integer Integer
|
||||
| Bool Bool
|
||||
| Char Char
|
||||
| Jnull
|
||||
| Null
|
||||
| StmtExprExpr StmtExpr
|
||||
deriving (Show, Eq)
|
||||
|
||||
|
||||
+19
-5
@@ -10,6 +10,8 @@ import Grammar.AST
|
||||
%error { parseError }
|
||||
|
||||
%token
|
||||
public { TokenVisibility Public }
|
||||
private { TokenVisibility Private }
|
||||
class { TokenClass }
|
||||
void { TokenVoid }
|
||||
return { TokenReturn }
|
||||
@@ -68,15 +70,26 @@ Classes : Classes ClassDecl { $2 : $1 }
|
||||
| ClassDecl { [$1] }
|
||||
|
||||
ClassDecl : class id '{' Decls '}' { Class $2 (fst $4) (snd $4) }
|
||||
| public class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
| private class id '{' Decls '}' { Class $3 (fst $5) (snd $5) }
|
||||
|
||||
Decls : FieldDecl Decls { ($1 : fst $2, snd $2) }
|
||||
| MethodDecl Decls { (fst $2, $1 : snd $2) }
|
||||
| {- leer -} { ([], []) }
|
||||
|
||||
FieldDecl : Type id ';' { Field $1 $2 }
|
||||
FieldDecl : Type id ';' { Field $1 $2 Nothing }
|
||||
| public Type id ';' { Field $2 $3 Nothing }
|
||||
| private Type id ';' { Field $2 $3 Nothing }
|
||||
| Type id '=' Expr ';' { Field $1 $2 (Just $4) }
|
||||
| public Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
| private Type id '=' Expr ';' { Field $2 $3 (Just $5) }
|
||||
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
MethodDecl : Type id '(' Params ')' Block { Method $1 $2 $4 $6 }
|
||||
| public Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| private Type id '(' Params ')' Block { Method $2 $3 $5 $7 }
|
||||
| void id '(' Params ')' Block { Method "void" $2 $4 $6 }
|
||||
| public void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
| private void id '(' Params ')' Block { Method "void" $3 $5 $7 }
|
||||
|
||||
Params : ParamList { $1 }
|
||||
| {- leer -} { [] }
|
||||
@@ -97,7 +110,8 @@ Stmts : Stmts Stmt { $2 : $1 }
|
||||
| {- leer -} { [] }
|
||||
|
||||
Stmt : Block { $1 }
|
||||
| return Expr ';' { Return $2 }
|
||||
| return Expr ';' { Return (Just $2) }
|
||||
| return ';' { Return Nothing }
|
||||
| while '(' Expr ')' Stmt { While $3 $5 }
|
||||
| if '(' Expr ')' Stmt else Stmt { If $3 $5 (Just $7) }
|
||||
| if '(' Expr ')' Stmt { If $3 $5 Nothing }
|
||||
@@ -141,5 +155,5 @@ ExprList : Expr { [$1] }
|
||||
|
||||
{
|
||||
parseError :: [Token] -> a
|
||||
parseError tokens = error $ "Lexical error or syntax error at: " ++ show tokens
|
||||
parseError tokens = error $ "abc Lexical error or syntax error at: " ++ show tokens
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ tokens :-
|
||||
"/*"([^\*]|(\*+([^\*\/])))*\*+\/ ; -- multi-line comments
|
||||
|
||||
-- key words
|
||||
public { \_ -> TokenVisibility Public }
|
||||
private { \_ -> TokenVisibility Private }
|
||||
class { \_ -> TokenClass }
|
||||
void { \_ -> TokenVoid }
|
||||
return { \_ -> TokenReturn }
|
||||
@@ -49,8 +51,14 @@ tokens :-
|
||||
$alpha [$alpha $digit]* { \s -> TokenIdent s }
|
||||
|
||||
{
|
||||
data Visibility
|
||||
= Public
|
||||
| Private
|
||||
deriving (Eq, Show)
|
||||
|
||||
data Token
|
||||
= TokenClass
|
||||
= TokenVisibility Visibility
|
||||
| TokenClass
|
||||
| TokenStatic
|
||||
| TokenVoid
|
||||
| TokenReturn
|
||||
|
||||
+30
-6
@@ -1,13 +1,37 @@
|
||||
module Grammar.TAST where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.AST (BinaryOperator, Type, UnaryOperator)
|
||||
|
||||
data TypedExpr = TypedExpr Expr Type
|
||||
data TypedClass = Class Type [TypedFieldDecl] [TypedMethodDecl] deriving (Show, Eq)
|
||||
|
||||
data TypedStmtExpr = TypedStmtExpr StmtExpr Type
|
||||
data TypedFieldDecl = Field Type String (Maybe TypedExpr) deriving (Show, Eq)
|
||||
|
||||
data TypedStmt = TypedStmt Stmt Type
|
||||
data TypedMethodDecl = Method Type String [(Type, String)] TypedStmt deriving (Show, Eq)
|
||||
|
||||
data TypedBinaryOperator = TypedBinaryOperator BinaryOperator Type
|
||||
data TypedExpr
|
||||
= This Type
|
||||
| LocalOrFieldVar String Type
|
||||
| InstVar TypedExpr String Type
|
||||
| Unary UnaryOperator TypedExpr Type
|
||||
| Binary BinaryOperator TypedExpr TypedExpr Type
|
||||
| Integer Integer Type
|
||||
| Bool Bool Type
|
||||
| Char Char Type
|
||||
| Null Type
|
||||
| StmtExprExpr TypedStmtExpr Type
|
||||
deriving (Show, Eq)
|
||||
|
||||
data TypedUnaryOperator = TypedUnaryOperator UnaryOperator Type
|
||||
data TypedStmtExpr
|
||||
= Assign TypedExpr TypedExpr Type
|
||||
| New Type [TypedExpr] Type
|
||||
| MethodCall TypedExpr String [TypedExpr] Type
|
||||
deriving (Show, Eq)
|
||||
|
||||
data TypedStmt
|
||||
= Block [TypedStmt] Type
|
||||
| Return (Maybe TypedExpr) Type
|
||||
| While TypedExpr TypedStmt Type
|
||||
| LocalVarDecl Type String Type
|
||||
| If TypedExpr TypedStmt (Maybe TypedStmt) Type
|
||||
| StmtExprStmt TypedStmtExpr Type
|
||||
deriving (Show, Eq)
|
||||
|
||||
+49
-5
@@ -1,10 +1,54 @@
|
||||
module Main where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.TAST
|
||||
import Typecheck.SemanticChecker
|
||||
import Codegen.ClassFile (ClassFile, generateClassFile)
|
||||
import Codegen.Serializer (serializeClassFile)
|
||||
import qualified Data.ByteString as BS
|
||||
import Grammar.AST (Program)
|
||||
import Grammar.Parser (parse)
|
||||
import Grammar.Scanner (Token, alexScanTokens)
|
||||
import Grammar.TAST (TypedClass (Class))
|
||||
import System.Environment (getArgs)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let t = getTypeFromExpr $ TypedExpr This "myType"
|
||||
putStrLn t
|
||||
args <- getArgs
|
||||
case args of
|
||||
[path] -> runPipeline path
|
||||
_ -> putStrLn "Usage: compoiler <input.java>"
|
||||
|
||||
runPipeline :: FilePath -> IO ()
|
||||
runPipeline path = do
|
||||
source <- readFile path
|
||||
let tokens = scan source
|
||||
let ast = parseProgram tokens
|
||||
let typedClasses = typeCheckProgram ast
|
||||
let classFiles = map generateClassFile typedClasses
|
||||
mapM_ writeClassFile (zip typedClasses classFiles)
|
||||
reportSuccess ast typedClasses classFiles
|
||||
|
||||
writeClassFile :: (TypedClass, ClassFile) -> IO ()
|
||||
writeClassFile (tc, cf) = do
|
||||
let outPath = "out/" ++ className tc ++ ".class"
|
||||
BS.writeFile outPath (BS.pack (serializeClassFile cf))
|
||||
putStrLn ("Written: " ++ outPath)
|
||||
|
||||
scan :: String -> [Token]
|
||||
scan = alexScanTokens
|
||||
|
||||
parseProgram :: [Token] -> Program
|
||||
parseProgram = parse
|
||||
|
||||
typeCheckProgram :: Program -> [TypedClass]
|
||||
typeCheckProgram classes =
|
||||
map (\cls -> typeCheckClass cls [] classes) classes
|
||||
|
||||
reportSuccess :: Program -> [TypedClass] -> [ClassFile] -> IO ()
|
||||
reportSuccess ast typed classFiles = do
|
||||
putStrLn ("Parsed classes: " ++ show (length ast))
|
||||
putStrLn ("Typed classes: " ++ show (length typed))
|
||||
putStrLn ("Generated class files: " ++ show (length classFiles))
|
||||
putStrLn ("Class names: " ++ unwords (map className typed))
|
||||
|
||||
className :: TypedClass -> String
|
||||
className (Class name _ _) = name
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
module Testsuite.AbcFiles.ArithmeticTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "basic",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 1, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 2, instruction = IAdd},
|
||||
BtcLine {lineNumber = 3, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 4, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 5, instruction = IMul},
|
||||
BtcLine {lineNumber = 6, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 7, instruction = ISub},
|
||||
BtcLine {lineNumber = 8, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 9, instruction = ISub},
|
||||
BtcLine {lineNumber = 10, instruction = IReturn}
|
||||
]
|
||||
),
|
||||
( "logic",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ILoad 0},
|
||||
BtcLine {lineNumber = 1, instruction = ILoad 2},
|
||||
BtcLine {lineNumber = 2, instruction = ILoad 1},
|
||||
BtcLine {lineNumber = 3, instruction = IAdd},
|
||||
BtcLine {lineNumber = 4, instruction = IAdd},
|
||||
BtcLine {lineNumber = 5, instruction = IReturn}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,15 @@
|
||||
module Testsuite.AbcFiles.EmptyTestABC (expectedABC) where
|
||||
|
||||
import Codegen.Lowerer (BtcLine (..), BtcProgram (..), Instruction (..))
|
||||
|
||||
expectedABC :: [(String, BtcProgram)]
|
||||
expectedABC =
|
||||
[
|
||||
( "<init>",
|
||||
BtcProgram
|
||||
[ BtcLine {lineNumber = 0, instruction = ALoad0},
|
||||
BtcLine {lineNumber = 1, instruction = InvokeSpecial 1},
|
||||
BtcLine {lineNumber = 4, instruction = Return}
|
||||
]
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
module Testsuite.AstFiles.ArithmeticTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"ArithmeticTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"basic"
|
||||
[("int", "a"), ("int", "b"), ("int", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Subtract
|
||||
(Binary Add (LocalOrFieldVar "a") (LocalOrFieldVar "b"))
|
||||
( Binary
|
||||
Modulo
|
||||
( Binary
|
||||
Divide
|
||||
(Binary Multiply (LocalOrFieldVar "c") (LocalOrFieldVar "a"))
|
||||
(LocalOrFieldVar "b")
|
||||
)
|
||||
(LocalOrFieldVar "c")
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
),
|
||||
Method
|
||||
"boolean"
|
||||
"logic"
|
||||
[("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
And
|
||||
(Unary Not (LocalOrFieldVar "a"))
|
||||
(Binary Or (LocalOrFieldVar "c") (LocalOrFieldVar "b"))
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.AstFiles.EmptyTestAST (expectedAST) where
|
||||
|
||||
import Grammar.AST
|
||||
|
||||
expectedAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
import Control.Exception (throwIO)
|
||||
import Control.Monad (unless)
|
||||
import Data.List (sort, isSuffixOf)
|
||||
import System.Directory (doesDirectoryExist, listDirectory, doesFileExist)
|
||||
import System.Exit (ExitCode (..), exitFailure)
|
||||
import System.FilePath (takeExtension, takeFileName, (</>), isExtensionOf, takeDirectory)
|
||||
import System.Process (readProcessWithExitCode)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..), exitOnFailures, printTestResults, runNamedTests)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let executableTestsDir = "src/Testsuite/ExecutableTests"
|
||||
directoryExists <- doesDirectoryExist executableTestsDir
|
||||
unless directoryExists $ do
|
||||
putStrLn ("ExecutableTests directory not found: " ++ executableTestsDir)
|
||||
exitFailure
|
||||
|
||||
harnessFiles <- discoverHarnessFiles executableTestsDir
|
||||
if null harnessFiles
|
||||
then do
|
||||
putStrLn "No harness files found to execute."
|
||||
exitFailure
|
||||
else do
|
||||
let namedHarnesses =
|
||||
[ (takeFileName harnessFile, runHarness harnessFile)
|
||||
| harnessFile <- harnessFiles
|
||||
]
|
||||
results <- runNamedTests namedHarnesses
|
||||
printTestResults "Full Testsuite Harness" results
|
||||
exitOnFailures results
|
||||
|
||||
discoverHarnessFiles :: FilePath -> IO [FilePath]
|
||||
discoverHarnessFiles baseDir = do
|
||||
entries <- listDirectory baseDir
|
||||
harnessFiles <- concat <$> mapM (findHarnessesInDir baseDir) entries
|
||||
pure (sort harnessFiles)
|
||||
|
||||
findHarnessesInDir :: FilePath -> String -> IO [FilePath]
|
||||
findHarnessesInDir baseDir entry = do
|
||||
let fullPath = baseDir </> entry
|
||||
isDir <- doesDirectoryExist fullPath
|
||||
if isDir && entry /= "." && entry /= ".."
|
||||
then do
|
||||
subEntries <- listDirectory fullPath
|
||||
let harnesses =
|
||||
[ fullPath </> file
|
||||
| file <- subEntries,
|
||||
takeExtension file == ".hs",
|
||||
"Harness.hs" `isSuffixOf` file,
|
||||
file /= "AllHarness.hs"
|
||||
]
|
||||
pure harnesses
|
||||
else pure []
|
||||
|
||||
runHarness :: FilePath -> IO ()
|
||||
runHarness harnessPath = do
|
||||
(exitCode, stdOut, stdErr) <- readProcessWithExitCode "runghc" ["-isrc", harnessPath] ""
|
||||
putStrLn ("\n--- Output from " ++ takeFileName harnessPath ++ " ---")
|
||||
unless (null stdOut) (putStrLn stdOut)
|
||||
unless (null stdErr) (putStrLn stdErr)
|
||||
case exitCode of
|
||||
ExitSuccess -> pure ()
|
||||
ExitFailure _ -> throwIO (TestFailure "")
|
||||
@@ -0,0 +1,30 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticABCTest where
|
||||
|
||||
import Codegen.ClassFile (generateClassFile)
|
||||
import Codegen.Lowerer (BtcProgram, lowerClassFile)
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AbcFiles.ArithmeticTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST)
|
||||
|
||||
mainArithmeticABCTest :: IO ()
|
||||
mainArithmeticABCTest = do
|
||||
let actualABC = generateABCProgram expectedTAST
|
||||
if actualABC == expectedABC
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual ABC:",
|
||||
show actualABC,
|
||||
"Expected ABC:",
|
||||
show expectedABC
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
generateABCProgram :: [TypedClass] -> [(String, BtcProgram)]
|
||||
generateABCProgram [typedClass] = lowerClassFile (generateClassFile typedClass)
|
||||
generateABCProgram _ = error "Expected exactly one typed class for the ABC test"
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticASTTest where
|
||||
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Control.Exception (throwIO)
|
||||
import Testsuite.AstFiles.ArithmeticTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainArithmeticASTTest :: IO ()
|
||||
mainArithmeticASTTest = do
|
||||
let javaFilePath = "src/Testsuite/javaFiles/ArithmeticTest.java"
|
||||
java <- readFile javaFilePath
|
||||
let actualAST = parse . alexScanTokens $ java
|
||||
if actualAST == expectedAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Arithmetic AST test failed.",
|
||||
"Actual AST:",
|
||||
show actualAST,
|
||||
"Expected AST:",
|
||||
show expectedAST
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,227 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticBytecodeBytesTest where
|
||||
|
||||
import Codegen.Lowerer (BtcProgram)
|
||||
import Codegen.Serializer (serializeProgram)
|
||||
import Control.Exception (throwIO)
|
||||
import Data.Bits ((.|.), shiftL)
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Testsuite.AbcFiles.ArithmeticTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainArithmeticBytecodeBytesTest :: IO ()
|
||||
mainArithmeticBytecodeBytesTest = do
|
||||
let classFilePath = "src/Testsuite/classFiles/ArithmeticTest.class"
|
||||
methodCodes <- parseMethodCodeBytesFromFile classFilePath
|
||||
failures <- fmap concat (mapM (checkMethod methodCodes) expectedABC)
|
||||
if null failures
|
||||
then pure ()
|
||||
else throwIO (TestFailure (unlines failures))
|
||||
|
||||
checkMethod :: [(String, [Word8])] -> (String, BtcProgram) -> IO [String]
|
||||
checkMethod methodCodes (methodName, expectedProgram) = do
|
||||
let expectedBytes = serializeProgram expectedProgram
|
||||
maybeActualBytes = lookup methodName methodCodes
|
||||
case maybeActualBytes of
|
||||
Nothing ->
|
||||
pure
|
||||
[ "Method '" ++ methodName ++ "' not found in reference class file"
|
||||
]
|
||||
Just actualBytes ->
|
||||
if expectedBytes == actualBytes
|
||||
then pure []
|
||||
else
|
||||
pure ["Method '" ++ methodName ++ "' bytecode mismatch"]
|
||||
|
||||
newtype Parser a = Parser
|
||||
{ runParser :: [Word8] -> Either String (a, [Word8])
|
||||
}
|
||||
|
||||
instance Functor Parser where
|
||||
fmap f p = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
pure (f value, rest)
|
||||
|
||||
instance Applicative Parser where
|
||||
pure value = Parser $ \input -> Right (value, input)
|
||||
pf <*> px = Parser $ \input -> do
|
||||
(f, rest1) <- runParser pf input
|
||||
(x, rest2) <- runParser px rest1
|
||||
pure (f x, rest2)
|
||||
|
||||
instance Monad Parser where
|
||||
p >>= f = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
runParser (f value) rest
|
||||
|
||||
parseFail :: String -> Parser a
|
||||
parseFail msg = Parser $ \_ -> Left msg
|
||||
|
||||
u1 :: Parser Word8
|
||||
u1 = Parser $ \input ->
|
||||
case input of
|
||||
[] -> Left "Unexpected end of input while reading u1"
|
||||
(b : rest) -> Right (b, rest)
|
||||
|
||||
u2 :: Parser Word16
|
||||
u2 = do
|
||||
hi <- u1
|
||||
lo <- u1
|
||||
pure ((fromIntegral hi `shiftL` 8) .|. fromIntegral lo)
|
||||
|
||||
u4 :: Parser Word32
|
||||
u4 = do
|
||||
b1 <- u1
|
||||
b2 <- u1
|
||||
b3 <- u1
|
||||
b4 <- u1
|
||||
pure
|
||||
( (fromIntegral b1 `shiftL` 24)
|
||||
.|. (fromIntegral b2 `shiftL` 16)
|
||||
.|. (fromIntegral b3 `shiftL` 8)
|
||||
.|. fromIntegral b4
|
||||
)
|
||||
|
||||
takeN :: Int -> Parser [Word8]
|
||||
takeN n
|
||||
| n < 0 = parseFail "Negative takeN requested"
|
||||
| otherwise = Parser $ \input ->
|
||||
if length input < n
|
||||
then Left ("Unexpected end of input while reading " ++ show n ++ " bytes")
|
||||
else Right (splitAt n input)
|
||||
|
||||
skipN :: Int -> Parser ()
|
||||
skipN n = do
|
||||
_ <- takeN n
|
||||
pure ()
|
||||
|
||||
parseMethodCodeBytesFromFile :: FilePath -> IO [(String, [Word8])]
|
||||
parseMethodCodeBytesFromFile path = do
|
||||
bytes <- BS.readFile path
|
||||
case runParser parseClassFile (BS.unpack bytes) of
|
||||
Left err -> error ("Failed parsing class file '" ++ path ++ "': " ++ err)
|
||||
Right (methods, _) -> pure methods
|
||||
|
||||
parseClassFile :: Parser [(String, [Word8])]
|
||||
parseClassFile = do
|
||||
magic <- u4
|
||||
if magic /= 0xCAFEBABE
|
||||
then parseFail "Invalid class file magic"
|
||||
else pure ()
|
||||
|
||||
_minorVersion <- u2
|
||||
_majorVersion <- u2
|
||||
|
||||
cpCount <- fromIntegral <$> u2
|
||||
utf8Map <- parseConstantPool cpCount 1 Map.empty
|
||||
|
||||
_accessFlags <- u2
|
||||
_thisClass <- u2
|
||||
_superClass <- u2
|
||||
|
||||
interfacesCount <- fromIntegral <$> u2
|
||||
skipN (interfacesCount * 2)
|
||||
|
||||
fieldsCount <- fromIntegral <$> u2
|
||||
skipMembers fieldsCount
|
||||
|
||||
methodsCount <- fromIntegral <$> u2
|
||||
parseMethods utf8Map methodsCount
|
||||
|
||||
parseConstantPool :: Int -> Int -> Map.Map Int String -> Parser (Map.Map Int String)
|
||||
parseConstantPool cpCount idx utf8Map
|
||||
| idx >= cpCount = pure utf8Map
|
||||
| otherwise = do
|
||||
tag <- u1
|
||||
case tag of
|
||||
1 -> do
|
||||
len <- fromIntegral <$> u2
|
||||
bytes <- takeN len
|
||||
let value = map (toEnum . fromIntegral) bytes
|
||||
parseConstantPool cpCount (idx + 1) (Map.insert idx value utf8Map)
|
||||
3 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
4 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
5 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
6 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
7 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
8 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
9 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
10 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
11 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
12 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
15 -> skipN 3 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
16 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
17 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
18 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
19 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
20 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
_ -> parseFail ("Unknown constant pool tag " ++ show tag)
|
||||
|
||||
skipMembers :: Int -> Parser ()
|
||||
skipMembers 0 = pure ()
|
||||
skipMembers n = do
|
||||
_accessFlags <- u2
|
||||
_nameIndex <- u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
skipMembers (n - 1)
|
||||
|
||||
skipAttributes :: Int -> Parser ()
|
||||
skipAttributes 0 = pure ()
|
||||
skipAttributes n = do
|
||||
_attrNameIndex <- u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
skipN attrLen
|
||||
skipAttributes (n - 1)
|
||||
|
||||
parseMethods :: Map.Map Int String -> Int -> Parser [(String, [Word8])]
|
||||
parseMethods _ 0 = pure []
|
||||
parseMethods utf8Map n = do
|
||||
_accessFlags <- u2
|
||||
nameIndex <- fromIntegral <$> u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
|
||||
let methodName = Map.findWithDefault ("<unknown-" ++ show nameIndex ++ ">") nameIndex utf8Map
|
||||
|
||||
maybeCode <- parseMethodAttributes utf8Map attrCount
|
||||
rest <- parseMethods utf8Map (n - 1)
|
||||
case maybeCode of
|
||||
Nothing -> pure rest
|
||||
Just code -> pure ((methodName, code) : rest)
|
||||
|
||||
parseMethodAttributes :: Map.Map Int String -> Int -> Parser (Maybe [Word8])
|
||||
parseMethodAttributes _ 0 = pure Nothing
|
||||
parseMethodAttributes utf8Map n = do
|
||||
attrNameIndex <- fromIntegral <$> u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
let attrName = Map.findWithDefault "" attrNameIndex utf8Map
|
||||
|
||||
current <-
|
||||
if attrName == "Code"
|
||||
then parseCodeAttribute attrLen
|
||||
else skipN attrLen >> pure Nothing
|
||||
|
||||
next <- parseMethodAttributes utf8Map (n - 1)
|
||||
pure (pickFirst current next)
|
||||
|
||||
pickFirst :: Maybe a -> Maybe a -> Maybe a
|
||||
pickFirst (Just x) _ = Just x
|
||||
pickFirst Nothing y = y
|
||||
|
||||
parseCodeAttribute :: Int -> Parser (Maybe [Word8])
|
||||
parseCodeAttribute _declaredLength = do
|
||||
_maxStack <- u2
|
||||
_maxLocals <- u2
|
||||
codeLen <- fromIntegral <$> u4
|
||||
codeBytes <- takeN codeLen
|
||||
|
||||
exceptionTableLength <- fromIntegral <$> u2
|
||||
skipN (exceptionTableLength * 8)
|
||||
|
||||
nestedAttrCount <- fromIntegral <$> u2
|
||||
skipAttributes nestedAttrCount
|
||||
|
||||
pure (Just codeBytes)
|
||||
@@ -0,0 +1,19 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticHarness where
|
||||
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticABCTest (mainArithmeticABCTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticASTTest (mainArithmeticASTTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticBytecodeBytesTest (mainArithmeticBytecodeBytesTest)
|
||||
import Testsuite.ExecutableTests.Arithmetic.ArithmeticTASTTest (mainArithmeticTASTTest)
|
||||
import Testsuite.ExecutableTests.HarnessSupport
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let tests =
|
||||
[ ("Arithmetic AST", mainArithmeticASTTest),
|
||||
("Arithmetic TAST", mainArithmeticTASTTest),
|
||||
("Arithmetic ABC", mainArithmeticABCTest),
|
||||
("Arithmetic Bytecode Bytes", mainArithmeticBytecodeBytesTest)
|
||||
]
|
||||
results <- runNamedTests tests
|
||||
printTestResults "Arithmetic Harness" results
|
||||
exitOnFailures results
|
||||
@@ -0,0 +1,29 @@
|
||||
module Testsuite.ExecutableTests.Arithmetic.ArithmeticTASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.AST (Class)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AstFiles.ArithmeticTestAST (expectedAST)
|
||||
import Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
mainArithmeticTASTTest :: IO ()
|
||||
mainArithmeticTASTTest = do
|
||||
let actualTAST = typeCheckProgram expectedAST
|
||||
if actualTAST == expectedTAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual TAST:",
|
||||
show actualTAST,
|
||||
"Expected TAST:",
|
||||
show expectedTAST
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
typeCheckProgram :: [Class] -> [TypedClass]
|
||||
typeCheckProgram classes = map (\cls -> typeCheckClass cls [] classes) classes
|
||||
@@ -0,0 +1,30 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyABCTest where
|
||||
|
||||
import Codegen.ClassFile (generateClassFile)
|
||||
import Codegen.Lowerer (BtcProgram, lowerClassFile)
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AbcFiles.EmptyTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.EmptyTestTAST (expectedTAST)
|
||||
|
||||
mainEmptyABCTest :: IO ()
|
||||
mainEmptyABCTest = do
|
||||
let actualABC = generateABCProgram expectedTAST
|
||||
if actualABC == expectedABC
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual ABC:",
|
||||
show actualABC,
|
||||
"Expected ABC:",
|
||||
show expectedABC
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
generateABCProgram :: [TypedClass] -> [(String, BtcProgram)]
|
||||
generateABCProgram [typedClass] = lowerClassFile (generateClassFile typedClass)
|
||||
generateABCProgram _ = error "Expected exactly one typed class for the ABC test"
|
||||
@@ -0,0 +1,27 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.Parser
|
||||
import Grammar.Scanner
|
||||
import Testsuite.AstFiles.EmptyTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainEmptyASTTest :: IO ()
|
||||
mainEmptyASTTest = do
|
||||
let javaFilePath = "src/Testsuite/javaFiles/EmptyTest.java"
|
||||
java <- readFile javaFilePath
|
||||
let actualAST = parse . alexScanTokens $ java
|
||||
if actualAST == expectedAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Empty AST test failed.",
|
||||
"Actual AST:",
|
||||
show actualAST,
|
||||
"Expected AST:",
|
||||
show expectedAST
|
||||
]
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,224 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyBytecodeBytesTest where
|
||||
|
||||
import Codegen.Lowerer (BtcProgram)
|
||||
import Codegen.Serializer (serializeProgram)
|
||||
import Control.Exception (throwIO)
|
||||
import Data.Bits ((.|.), shiftL)
|
||||
import qualified Data.ByteString as BS
|
||||
import qualified Data.Map.Strict as Map
|
||||
import Data.Word (Word16, Word32, Word8)
|
||||
import Testsuite.AbcFiles.EmptyTestABC (expectedABC)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
|
||||
mainEmptyBytecodeBytesTest :: IO ()
|
||||
mainEmptyBytecodeBytesTest = do
|
||||
let classFilePath = "src/Testsuite/classFiles/EmptyTest.class"
|
||||
methodCodes <- parseMethodCodeBytesFromFile classFilePath
|
||||
failures <- fmap concat (mapM (checkMethod methodCodes) expectedABC)
|
||||
if null failures
|
||||
then pure ()
|
||||
else throwIO (TestFailure (unlines failures))
|
||||
|
||||
checkMethod :: [(String, [Word8])] -> (String, BtcProgram) -> IO [String]
|
||||
checkMethod methodCodes (methodName, expectedProgram) = do
|
||||
let expectedBytes = serializeProgram expectedProgram
|
||||
maybeActualBytes = lookup methodName methodCodes
|
||||
case maybeActualBytes of
|
||||
Nothing ->
|
||||
pure
|
||||
[ "Method '" ++ methodName ++ "' not found in reference class file"
|
||||
]
|
||||
Just actualBytes ->
|
||||
if expectedBytes == actualBytes
|
||||
then pure []
|
||||
else
|
||||
pure ["Method '" ++ methodName ++ "' bytecode mismatch"]
|
||||
|
||||
newtype Parser a = Parser
|
||||
{ runParser :: [Word8] -> Either String (a, [Word8])
|
||||
}
|
||||
|
||||
instance Functor Parser where
|
||||
fmap f p = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
pure (f value, rest)
|
||||
|
||||
instance Applicative Parser where
|
||||
pure value = Parser $ \input -> Right (value, input)
|
||||
pf <*> px = Parser $ \input -> do
|
||||
(f, rest1) <- runParser pf input
|
||||
(x, rest2) <- runParser px rest1
|
||||
pure (f x, rest2)
|
||||
|
||||
instance Monad Parser where
|
||||
p >>= f = Parser $ \input -> do
|
||||
(value, rest) <- runParser p input
|
||||
runParser (f value) rest
|
||||
|
||||
parseFail :: String -> Parser a
|
||||
parseFail msg = Parser $ \_ -> Left msg
|
||||
|
||||
u1 :: Parser Word8
|
||||
u1 = Parser $ \input ->
|
||||
case input of
|
||||
[] -> Left "Unexpected end of input while reading u1"
|
||||
(b : rest) -> Right (b, rest)
|
||||
|
||||
u2 :: Parser Word16
|
||||
u2 = do
|
||||
hi <- u1
|
||||
lo <- u1
|
||||
pure ((fromIntegral hi `shiftL` 8) .|. fromIntegral lo)
|
||||
|
||||
u4 :: Parser Word32
|
||||
u4 = do
|
||||
b1 <- u1
|
||||
b2 <- u1
|
||||
b3 <- u1
|
||||
b4 <- u1
|
||||
pure
|
||||
( (fromIntegral b1 `shiftL` 24)
|
||||
.|. (fromIntegral b2 `shiftL` 16)
|
||||
.|. (fromIntegral b3 `shiftL` 8)
|
||||
.|. fromIntegral b4
|
||||
)
|
||||
|
||||
takeN :: Int -> Parser [Word8]
|
||||
takeN n
|
||||
| n < 0 = parseFail "Negative takeN requested"
|
||||
| otherwise = Parser $ \input ->
|
||||
if length input < n
|
||||
then Left ("Unexpected end of input while reading " ++ show n ++ " bytes")
|
||||
else Right (splitAt n input)
|
||||
|
||||
skipN :: Int -> Parser ()
|
||||
skipN n = do
|
||||
_ <- takeN n
|
||||
pure ()
|
||||
|
||||
parseMethodCodeBytesFromFile :: FilePath -> IO [(String, [Word8])]
|
||||
parseMethodCodeBytesFromFile path = do
|
||||
bytes <- BS.readFile path
|
||||
case runParser parseClassFile (BS.unpack bytes) of
|
||||
Left err -> error ("Failed parsing class file '" ++ path ++ "': " ++ err)
|
||||
Right (methods, _) -> pure methods
|
||||
|
||||
parseClassFile :: Parser [(String, [Word8])]
|
||||
parseClassFile = do
|
||||
magic <- u4
|
||||
if magic /= 0xCAFEBABE
|
||||
then parseFail "Invalid class file magic"
|
||||
else pure ()
|
||||
|
||||
_minorVersion <- u2
|
||||
_majorVersion <- u2
|
||||
|
||||
cpCount <- fromIntegral <$> u2
|
||||
utf8Map <- parseConstantPool cpCount 1 Map.empty
|
||||
|
||||
_accessFlags <- u2
|
||||
_thisClass <- u2
|
||||
_superClass <- u2
|
||||
|
||||
interfacesCount <- fromIntegral <$> u2
|
||||
skipN (interfacesCount * 2)
|
||||
|
||||
fieldsCount <- fromIntegral <$> u2
|
||||
skipMembers fieldsCount
|
||||
|
||||
methodsCount <- fromIntegral <$> u2
|
||||
parseMethods utf8Map methodsCount
|
||||
|
||||
parseConstantPool :: Int -> Int -> Map.Map Int String -> Parser (Map.Map Int String)
|
||||
parseConstantPool cpCount idx utf8Map
|
||||
| idx >= cpCount = pure utf8Map
|
||||
| otherwise = do
|
||||
tag <- u1
|
||||
case tag of
|
||||
1 -> do
|
||||
len <- fromIntegral <$> u2
|
||||
bytes <- takeN len
|
||||
let value = map (toEnum . fromIntegral) bytes
|
||||
parseConstantPool cpCount (idx + 1) (Map.insert idx value utf8Map)
|
||||
3 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
4 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
5 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
6 -> skipN 8 >> parseConstantPool cpCount (idx + 2) utf8Map
|
||||
7 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
8 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
9 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
10 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
11 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
12 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
15 -> skipN 3 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
16 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
17 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
18 -> skipN 4 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
19 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
20 -> skipN 2 >> parseConstantPool cpCount (idx + 1) utf8Map
|
||||
_ -> parseFail ("Unknown constant pool tag " ++ show tag)
|
||||
|
||||
skipMembers :: Int -> Parser ()
|
||||
skipMembers 0 = pure ()
|
||||
skipMembers n = do
|
||||
_accessFlags <- u2
|
||||
_nameIndex <- u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
skipMembers (n - 1)
|
||||
|
||||
skipAttributes :: Int -> Parser ()
|
||||
skipAttributes 0 = pure ()
|
||||
skipAttributes n = do
|
||||
_attrNameIndex <- u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
skipN attrLen
|
||||
skipAttributes (n - 1)
|
||||
|
||||
parseMethods :: Map.Map Int String -> Int -> Parser [(String, [Word8])]
|
||||
parseMethods _ 0 = pure []
|
||||
parseMethods utf8Map n = do
|
||||
_accessFlags <- u2
|
||||
nameIndex <- fromIntegral <$> u2
|
||||
_descriptorIndex <- u2
|
||||
attrCount <- fromIntegral <$> u2
|
||||
|
||||
let methodName = Map.findWithDefault ("<unknown-" ++ show nameIndex ++ ">") nameIndex utf8Map
|
||||
|
||||
maybeCode <- parseMethodAttributes utf8Map attrCount
|
||||
rest <- parseMethods utf8Map (n - 1)
|
||||
case maybeCode of
|
||||
Nothing -> pure rest
|
||||
Just code -> pure ((methodName, code) : rest)
|
||||
|
||||
parseMethodAttributes :: Map.Map Int String -> Int -> Parser (Maybe [Word8])
|
||||
parseMethodAttributes _ 0 = pure Nothing
|
||||
parseMethodAttributes utf8Map n = do
|
||||
attrNameIndex <- fromIntegral <$> u2
|
||||
attrLen <- fromIntegral <$> u4
|
||||
let attrName = Map.findWithDefault "" attrNameIndex utf8Map
|
||||
|
||||
current <-
|
||||
if attrName == "Code"
|
||||
then parseCodeAttribute attrLen
|
||||
else skipN attrLen >> pure Nothing
|
||||
|
||||
next <- parseMethodAttributes utf8Map (n - 1)
|
||||
pure (pickFirst current next)
|
||||
|
||||
pickFirst :: Maybe a -> Maybe a -> Maybe a
|
||||
pickFirst (Just x) _ = Just x
|
||||
pickFirst Nothing y = y
|
||||
|
||||
parseCodeAttribute :: Int -> Parser (Maybe [Word8])
|
||||
parseCodeAttribute _declaredLength = do
|
||||
_maxStack <- u2
|
||||
_maxLocals <- u2
|
||||
codeLen <- fromIntegral <$> u4
|
||||
codeBytes <- takeN codeLen
|
||||
exceptionTableLen <- fromIntegral <$> u2
|
||||
skipN (exceptionTableLen * 8)
|
||||
attrCount <- fromIntegral <$> u2
|
||||
skipAttributes attrCount
|
||||
pure (Just codeBytes)
|
||||
@@ -0,0 +1,19 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyHarness where
|
||||
|
||||
import Testsuite.ExecutableTests.Empty.EmptyABCTest (mainEmptyABCTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyASTTest (mainEmptyASTTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyBytecodeBytesTest (mainEmptyBytecodeBytesTest)
|
||||
import Testsuite.ExecutableTests.Empty.EmptyTASTTest (mainEmptyTASTTest)
|
||||
import Testsuite.ExecutableTests.HarnessSupport
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
let tests =
|
||||
[ ("Empty AST", mainEmptyASTTest),
|
||||
("Empty TAST", mainEmptyTASTTest),
|
||||
("Empty ABC", mainEmptyABCTest),
|
||||
("Empty Bytecode Bytes", mainEmptyBytecodeBytesTest)
|
||||
]
|
||||
results <- runNamedTests tests
|
||||
printTestResults "Empty Harness" results
|
||||
exitOnFailures results
|
||||
@@ -0,0 +1,29 @@
|
||||
module Testsuite.ExecutableTests.Empty.EmptyTASTTest where
|
||||
|
||||
import Control.Exception (throwIO)
|
||||
import Grammar.AST (Class)
|
||||
import Grammar.TAST (TypedClass)
|
||||
import Testsuite.AstFiles.EmptyTestAST (expectedAST)
|
||||
import Testsuite.ExecutableTests.HarnessSupport (TestFailure (..))
|
||||
import Testsuite.TastFiles.EmptyTestTAST (expectedTAST)
|
||||
import Typecheck.SemanticChecker (typeCheckClass)
|
||||
|
||||
mainEmptyTASTTest :: IO ()
|
||||
mainEmptyTASTTest = do
|
||||
let actualTAST = typeCheckProgram expectedAST
|
||||
if actualTAST == expectedTAST
|
||||
then pure ()
|
||||
else
|
||||
throwIO
|
||||
( TestFailure
|
||||
( unlines
|
||||
[ "Actual TAST:",
|
||||
show actualTAST,
|
||||
"Expected TAST:",
|
||||
show expectedTAST
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
typeCheckProgram :: [Class] -> [TypedClass]
|
||||
typeCheckProgram classes = map (\cls -> typeCheckClass cls [] classes) classes
|
||||
@@ -0,0 +1,110 @@
|
||||
module Testsuite.ExecutableTests.HarnessSupport
|
||||
( TestResult (..),
|
||||
TestFailure (..),
|
||||
runNamedTests,
|
||||
printTestResults,
|
||||
exitOnFailures
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Exception (Exception, SomeException, fromException, try)
|
||||
import Data.List (dropWhileEnd, intercalate)
|
||||
import System.Exit (ExitCode (..), exitFailure)
|
||||
import System.IO (BufferMode (LineBuffering), hFlush, hSetBuffering, stderr, stdout)
|
||||
|
||||
newtype TestFailure = TestFailure {failureDetails :: String}
|
||||
deriving (Show)
|
||||
|
||||
instance Exception TestFailure
|
||||
|
||||
data TestResult = TestResult
|
||||
{ testName :: String,
|
||||
passed :: Bool,
|
||||
details :: String
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
runNamedTests :: [(String, IO ())] -> IO [TestResult]
|
||||
runNamedTests tests = do
|
||||
hSetBuffering stdout LineBuffering
|
||||
hSetBuffering stderr LineBuffering
|
||||
runNamedTests' tests
|
||||
|
||||
runNamedTests' :: [(String, IO ())] -> IO [TestResult]
|
||||
runNamedTests' [] = pure []
|
||||
runNamedTests' (test : rest) = do
|
||||
result <- runNamedTest test
|
||||
results <- runNamedTests' rest
|
||||
pure (result : results)
|
||||
|
||||
runNamedTest :: (String, IO ()) -> IO TestResult
|
||||
runNamedTest (name, action) = do
|
||||
putStrLn ("[RUN ] " ++ name)
|
||||
hFlush stdout
|
||||
result <- try action :: IO (Either SomeException ())
|
||||
case result of
|
||||
Right () -> do
|
||||
putStrLn ("[DONE] " ++ name)
|
||||
hFlush stdout
|
||||
pure (TestResult name True "")
|
||||
Left ex -> do
|
||||
let details = failureDetailsForSummary ex
|
||||
let runtimeDetails = failureDetailsForRuntime ex
|
||||
if null runtimeDetails
|
||||
then pure ()
|
||||
else putStrLn runtimeDetails
|
||||
putStrLn ("[DONE] " ++ name)
|
||||
hFlush stdout
|
||||
pure (TestResult name False details)
|
||||
|
||||
printTestResults :: String -> [TestResult] -> IO ()
|
||||
printTestResults title results = do
|
||||
putStrLn ("\n== " ++ title ++ " ==")
|
||||
mapM_ printResult results
|
||||
let total = length results
|
||||
failures = length (filter (not . passed) results)
|
||||
successes = total - failures
|
||||
putStrLn ("Summary: " ++ show successes ++ "/" ++ show total ++ " passed")
|
||||
|
||||
printResult :: TestResult -> IO ()
|
||||
printResult (TestResult name True _) =
|
||||
putStrLn ("[PASS] " ++ name)
|
||||
printResult (TestResult name False info) = do
|
||||
putStrLn ("[FAIL] " ++ name)
|
||||
if null info
|
||||
then pure ()
|
||||
else mapM_ (putStrLn . (" " ++)) (lines info)
|
||||
|
||||
exitOnFailures :: [TestResult] -> IO ()
|
||||
exitOnFailures results =
|
||||
if any (not . passed) results
|
||||
then exitFailure
|
||||
else pure ()
|
||||
|
||||
renderException :: SomeException -> String
|
||||
renderException ex =
|
||||
case fromException ex :: Maybe ExitCode of
|
||||
Just ExitSuccess -> "unexpected ExitSuccess exception"
|
||||
Just (ExitFailure code) -> "exit failure code " ++ show code
|
||||
Nothing -> sanitize (show ex)
|
||||
|
||||
failureDetailsForRuntime :: SomeException -> String
|
||||
failureDetailsForRuntime ex =
|
||||
case fromException ex :: Maybe TestFailure of
|
||||
Just (TestFailure details) -> stripTrailingNewlines details
|
||||
Nothing -> indentOneLine (renderException ex)
|
||||
|
||||
failureDetailsForSummary :: SomeException -> String
|
||||
failureDetailsForSummary ex =
|
||||
case fromException ex :: Maybe TestFailure of
|
||||
Just (TestFailure details) -> stripTrailingNewlines details
|
||||
Nothing -> renderException ex
|
||||
|
||||
indentOneLine :: String -> String
|
||||
indentOneLine text = " " ++ text
|
||||
|
||||
stripTrailingNewlines :: String -> String
|
||||
stripTrailingNewlines = dropWhileEnd (== '\n') . dropWhileEnd (== '\r')
|
||||
|
||||
sanitize :: String -> String
|
||||
sanitize = intercalate " " . words
|
||||
@@ -0,0 +1,57 @@
|
||||
module Testsuite.TastFiles.ArithmeticTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.AST (BinaryOperator (..), UnaryOperator (..))
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"ArithmeticTest"
|
||||
[]
|
||||
[ Method
|
||||
"int"
|
||||
"basic"
|
||||
[("int", "a"), ("int", "b"), ("int", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
Subtract
|
||||
(Binary Add (LocalOrFieldVar "a" "int") (LocalOrFieldVar "b" "int") "int")
|
||||
( Binary
|
||||
Modulo
|
||||
( Binary
|
||||
Divide
|
||||
(Binary Multiply (LocalOrFieldVar "c" "int") (LocalOrFieldVar "a" "int") "int")
|
||||
(LocalOrFieldVar "b" "int")
|
||||
"int"
|
||||
)
|
||||
(LocalOrFieldVar "c" "int")
|
||||
"int"
|
||||
)
|
||||
"int"
|
||||
)
|
||||
)
|
||||
"int"
|
||||
]
|
||||
"int"
|
||||
)
|
||||
, Method
|
||||
"boolean"
|
||||
"logic"
|
||||
[("boolean", "a"), ("boolean", "b"), ("boolean", "c")]
|
||||
( Block
|
||||
[ Return
|
||||
( Just
|
||||
( Binary
|
||||
And
|
||||
(Unary Not (LocalOrFieldVar "a" "boolean") "boolean")
|
||||
(Binary Or (LocalOrFieldVar "c" "boolean") (LocalOrFieldVar "b" "boolean") "boolean")
|
||||
"boolean"
|
||||
)
|
||||
)
|
||||
"boolean"
|
||||
]
|
||||
"boolean"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
module Testsuite.TastFiles.EmptyTestTAST (expectedTAST) where
|
||||
|
||||
import Grammar.TAST
|
||||
|
||||
expectedTAST =
|
||||
[ Class
|
||||
"EmptyTest"
|
||||
[]
|
||||
[]
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,40 @@
|
||||
public class AllSyntaxTest {
|
||||
public int x;
|
||||
private static int counter = 0;
|
||||
|
||||
public AllSyntaxTest(int initial) {
|
||||
x = initial;
|
||||
counter = counter + 1;
|
||||
}
|
||||
|
||||
public int inc() {
|
||||
x = x + 1;
|
||||
return x;
|
||||
}
|
||||
|
||||
public int inc(int delta) {
|
||||
x = x + delta;
|
||||
return x;
|
||||
}
|
||||
|
||||
public static int sumUpTo(int n) {
|
||||
int s = 0;
|
||||
for (int i = 1; i <= n; i = i + 1) {
|
||||
s = s + i;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public boolean predicate(char c) {
|
||||
return c == 'a' || c == 'b';
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
AllSyntaxTest a = new AllSyntaxTest(0);
|
||||
System.out.println(a.inc()); // 1
|
||||
System.out.println(a.inc(4)); // 5
|
||||
System.out.println(AllSyntaxTest.sumUpTo(5)); // 15
|
||||
System.out.println(a.predicate('a') ? 1 : 0); // ternary used to produce int
|
||||
System.out.println(counter); // static field access
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class ArithmeticTest {
|
||||
|
||||
public int basic(int a, int b, int c) {
|
||||
return a + b - (((c * a) / b) % c);
|
||||
}
|
||||
|
||||
public boolean logic(boolean a, boolean b, boolean c) {
|
||||
return !a && (c || b);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
public class CombinedControlTest {
|
||||
int field;
|
||||
|
||||
public CombinedControlTest(int v) {
|
||||
field = v;
|
||||
}
|
||||
|
||||
public int compute() {
|
||||
int i = 0;
|
||||
int acc = 0;
|
||||
while (i < field) {
|
||||
if (i % 2 == 0) {
|
||||
acc = acc + i;
|
||||
} else {
|
||||
acc = acc - i;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
CombinedControlTest t = new CombinedControlTest(6);
|
||||
// Computation: 0 -1 +2 -3 +4 -5 = -3
|
||||
System.out.println(t.compute());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
public class ConstructorOverloadTest {
|
||||
|
||||
public int a = 42;
|
||||
|
||||
ConstructorOverloadTest() {
|
||||
// nothing here, so a will assume the default value 42.
|
||||
}
|
||||
|
||||
ConstructorOverloadTest(int a) {
|
||||
this.a = a;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class ConstructorTest
|
||||
{
|
||||
public int a = -1;
|
||||
|
||||
public ConstructorTest(int initial_value)
|
||||
{
|
||||
a = initial_value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public class EmptyTest
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
public class ExpressionTest {
|
||||
public static boolean shortCircuit(int a, int b) {
|
||||
// short-circuit: when a==0 the right side must not be evaluated
|
||||
boolean res = (a != 0) && ((10 / a) > b);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static char charArithmetic(char c, int offset) {
|
||||
char d = (char)(c + offset);
|
||||
return d;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(shortCircuit(2, 1)); // true
|
||||
System.out.println(shortCircuit(0, 1)); // false (right side not evaluated)
|
||||
System.out.println(charArithmetic('A', 2)); // 'C'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
public class FieldsTest {
|
||||
|
||||
public int a;
|
||||
public int b = 42;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
public class IfTest {
|
||||
public static boolean ifElseTest(int x) {
|
||||
if (x < 0) {
|
||||
return false;
|
||||
} else if (x == 0) {
|
||||
return true;
|
||||
} else {
|
||||
return x > 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(ifElseTest(-1)); // false
|
||||
System.out.println(ifElseTest(0)); // true
|
||||
System.out.println(ifElseTest(11)); // true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
public class LoopTest {
|
||||
public int factorial(int n)
|
||||
{
|
||||
int tally = 1;
|
||||
for(int i = 1; i <= n; i++)
|
||||
{
|
||||
tally *= i;
|
||||
}
|
||||
|
||||
return tally;
|
||||
}
|
||||
|
||||
int weirdFor() {
|
||||
int k = 0;
|
||||
for (; k < 5; k++) {
|
||||
}
|
||||
return k;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// compile all test files using:
|
||||
// ls Test/JavaSources/*.java | grep -v ".*Main.java" | xargs -I {} cabal run compiler {}
|
||||
// compile (in project root) using:
|
||||
// pushd Test/JavaSources; javac -g:none Main.java; popd
|
||||
// afterwards, run using
|
||||
// java -ea -cp Test/JavaSources/ Main
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args)
|
||||
{
|
||||
EmptyTest empty = new EmptyTest();
|
||||
FieldsTest fields = new FieldsTest();
|
||||
ConstructorTest constructor = new ConstructorTest(42);
|
||||
ArithmeticTest arithmetic = new ArithmeticTest();
|
||||
MultipleClassesTest multipleClasses = new MultipleClassesTest();
|
||||
RecursionTest recursion = new RecursionTest(10);
|
||||
MaliciousTest malicious = new MaliciousTest();
|
||||
LoopTest loop = new LoopTest();
|
||||
MethodOverloadTest overload = new MethodOverloadTest();
|
||||
ShenaniganceTest shenanigance = new ShenaniganceTest();
|
||||
AllSyntaxTest allSyntax = new AllSyntaxTest(0);
|
||||
CombinedControlTest combined = new CombinedControlTest(6);
|
||||
ExpressionTest expression = new ExpressionTest();
|
||||
IfTest ifTest = new IfTest();
|
||||
MultiClassTest multiClass = new MultiClassTest();
|
||||
SingletonTest singleton = new SingletonTest();
|
||||
WhileTest whileTest = new WhileTest();
|
||||
|
||||
// constructing a basic class works
|
||||
System.out.println("test empty non-null. Expected: non-null, Real: " + (empty != null));
|
||||
// initializers (and default initializers to 0/null) work
|
||||
System.out.println("test fields initializers. Expected: a==0 and b==42, Real: a=" + fields.a + " and b=" + fields.b);
|
||||
// constructor parameters override initializers
|
||||
System.out.println("test constructor a. Expected result: 42, Real result: " + constructor.a);
|
||||
// basic arithmetics
|
||||
System.out.println("test arithmetic basic(1,2,3). Expected: 2, Real: " + arithmetic.basic(1, 2, 3));
|
||||
// we have boolean logic as well
|
||||
System.out.println("test arithmetic logic(false,false,true). Expected: true, Real: " + arithmetic.logic(false, false, true));
|
||||
// multiple classes within one file work. Referencing another classes fields/methods works.
|
||||
System.out.println("test multiple classes field. Expected: 42, Real: " + multipleClasses.a.a);
|
||||
// self-referencing classes work.
|
||||
System.out.println("test nested child value. Expected: 5, Real: " + recursion.child.child.child.child.child.value);
|
||||
// self-referencing methods work.
|
||||
System.out.println("test recursion fibonacci(15). Expected: 610, Real: " + recursion.fibonacci(15));
|
||||
System.out.println("test factorial(5). Expected: 120, Real: " + loop.factorial(5));
|
||||
System.out.println("test weirdFor(). Expected: 5, Real: " + loop.weirdFor());
|
||||
// methods with the same name but different parameters work
|
||||
System.out.println("test MethodOverload(). Expected: 42, Real: " + overload.MethodOverload());
|
||||
System.out.println("test MethodOverload(15). Expected: 42+15, Real: " + overload.MethodOverload(15));
|
||||
// constructor overloading works, too.
|
||||
System.out.println("test ctor overload default. Expected: 42, Real: " + (new ConstructorOverloadTest()).a);
|
||||
System.out.println("test ctor overload with arg. Expected: 12, Real: " + (new ConstructorOverloadTest(12)).a);
|
||||
// intentionally dodgy expressions work
|
||||
System.out.println("test assignNegativeIncrement(42). Expected: -42, Real: " + malicious.assignNegativeIncrement(42));
|
||||
System.out.println("test tripleAddition(1,2,3). Expected: 6, Real: " + malicious.tripleAddition(1, 2, 3));
|
||||
for(int i = 0; i < 3; i++)
|
||||
{
|
||||
System.out.println("test cursedFormatting i=" + i + ". Expected: " + i + ", Real: " + malicious.cursedFormatting(i));
|
||||
}
|
||||
// other syntactic sugar
|
||||
System.out.println("test shenanigance.testAssignment(). Expected: 5, Real: " + shenanigance.testAssignment());
|
||||
System.out.println("test shenanigance.divEqual(). Expected: " + (234_343_000 / 4) + ", Real: " + shenanigance.divEqual());
|
||||
// AllSyntaxTest tests
|
||||
System.out.println("test AllSyntaxTest.inc(). Expected: 1, Real: " + allSyntax.inc());
|
||||
System.out.println("test AllSyntaxTest.inc(4). Expected: 5, Real: " + allSyntax.inc(4));
|
||||
System.out.println("test AllSyntaxTest.sumUpTo(5). Expected: 15, Real: " + AllSyntaxTest.sumUpTo(5));
|
||||
System.out.println("test AllSyntaxTest.predicate('a'). Expected: true, Real: " + allSyntax.predicate('a'));
|
||||
// CombinedControlTest tests
|
||||
System.out.println("test CombinedControlTest.compute(). Expected: -3, Real: " + combined.compute());
|
||||
// ExpressionTest tests
|
||||
System.out.println("test ExpressionTest instance created. Expected: non-null, Real: " + (expression != null));
|
||||
System.out.println("test ExpressionTest.shortCircuit(2, 1). Expected: true, Real: " + expression.shortCircuit(2, 1));
|
||||
System.out.println("test ExpressionTest.shortCircuit(0, 1). Expected: false, Real: " + expression.shortCircuit(0, 1));
|
||||
System.out.println("test ExpressionTest.charArithmetic('A', 2). Expected: C, Real: " + expression.charArithmetic('A', 2));
|
||||
// IfTest tests
|
||||
System.out.println("test IfTest instance created. Expected: non-null, Real: " + (ifTest != null));
|
||||
System.out.println("test IfTest.ifElseTest(-1). Expected: false, Real: " + ifTest.ifElseTest(-1));
|
||||
System.out.println("test IfTest.ifElseTest(0). Expected: true, Real: " + ifTest.ifElseTest(0));
|
||||
System.out.println("test IfTest.ifElseTest(11). Expected: true, Real: " + ifTest.ifElseTest(11));
|
||||
// MultiClassTest tests
|
||||
System.out.println("test MultiClassTest instance created. Expected: non-null, Real: " + (multiClass != null));
|
||||
// SingletonTest tests
|
||||
System.out.println("test SingletonTest.getInstance(). Expected: non-null, Real: " + (singleton.getInstance() != null));
|
||||
// WhileTest tests
|
||||
System.out.println("test WhileTest.whileLoopTest(5). Expected: 15, Real: " + WhileTest.whileLoopTest(5));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
public class MaliciousTest {
|
||||
public int assignNegativeIncrement(int n)
|
||||
{
|
||||
return n=-++n+1;
|
||||
}
|
||||
|
||||
public int tripleAddition(int a, int b, int c)
|
||||
{
|
||||
return a+++b+++c++;
|
||||
}
|
||||
|
||||
public int cursedFormatting(int n)
|
||||
{
|
||||
if
|
||||
|
||||
|
||||
(n == 0)
|
||||
|
||||
{
|
||||
|
||||
return ((((0))));
|
||||
}
|
||||
|
||||
else
|
||||
|
||||
|
||||
if(n ==
|
||||
|
||||
1)
|
||||
{
|
||||
return
|
||||
|
||||
|
||||
1;
|
||||
}else {
|
||||
return
|
||||
2
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
public class MethodOverloadTest {
|
||||
|
||||
public int MethodOverload() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
public int MethodOverload(int a) {
|
||||
return 42 + a;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
public class MultiClassTest {
|
||||
public static void main(String[] args) {
|
||||
Helper h = new Helper(3);
|
||||
System.out.println(h.doubleIt()); // expect 6
|
||||
}
|
||||
}
|
||||
|
||||
class Helper {
|
||||
int v;
|
||||
Helper(int v0) { v = v0; }
|
||||
int doubleIt() { return v * 2; }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
public class MultipleClassesTest
|
||||
{
|
||||
public AnotherTestClass a = new AnotherTestClass();
|
||||
}
|
||||
|
||||
class AnotherTestClass
|
||||
{
|
||||
public int a = 42;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
public class RecursionTest {
|
||||
|
||||
public int value = 0;
|
||||
public RecursionTest child = null;
|
||||
|
||||
public RecursionTest(int n)
|
||||
{
|
||||
this.value = n;
|
||||
|
||||
if(n > 0)
|
||||
{
|
||||
child = new RecursionTest(n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public int fibonacci(int n)
|
||||
{
|
||||
if(n < 2)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fibonacci(n - 1) + this.fibonacci(n - 2);
|
||||
}
|
||||
}
|
||||
|
||||
public int ackermann(int m, int n)
|
||||
{
|
||||
if (m == 0) return n + 1;
|
||||
if (n == 0) return ackermann(m - 1, 1);
|
||||
return ackermann(m - 1, ackermann(m, n - 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
class ShenaniganceTest {
|
||||
|
||||
int testAssignment() {
|
||||
int x = 1;
|
||||
int y = x = 5;
|
||||
return y;
|
||||
}
|
||||
|
||||
int divEqual() {
|
||||
int x = 234_343_000;
|
||||
x /= 4;
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public class SingletonTest {
|
||||
|
||||
SingletonTest instance;
|
||||
|
||||
SingletonTest() {
|
||||
}
|
||||
|
||||
public SingletonTest getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new SingletonTest();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public class WhileTest {
|
||||
public static int whileLoopTest(int n) {
|
||||
int sum = 0;
|
||||
while (n > 0) {
|
||||
sum = sum + n;
|
||||
n = n - 1;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Expect 1+2+3+4+5 = 15
|
||||
System.out.println(whileLoopTest(5));
|
||||
}
|
||||
}
|
||||
@@ -1,60 +1,289 @@
|
||||
module Typecheck.SemanticChecker where
|
||||
|
||||
import Grammar.AST
|
||||
import Grammar.TAST
|
||||
import Data.List (find)
|
||||
import Grammar.AST as AST
|
||||
import Grammar.TAST as TAST
|
||||
|
||||
--------------------------------------------------
|
||||
-- Helper functions
|
||||
-- Get type functions
|
||||
--------------------------------------------------
|
||||
getExpr :: TypedExpr -> Expr
|
||||
getExpr (TypedExpr e _) = e
|
||||
getTypeFromTypedExpr :: TypedExpr -> Type
|
||||
getTypeFromTypedExpr (TAST.This t) = t
|
||||
getTypeFromTypedExpr (TAST.LocalOrFieldVar _ t) = t
|
||||
getTypeFromTypedExpr (TAST.InstVar _ _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Unary _ _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Binary _ _ _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Integer _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Bool _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Char _ t) = t
|
||||
getTypeFromTypedExpr (TAST.Null t) = t
|
||||
getTypeFromTypedExpr (TAST.StmtExprExpr _ t) = t
|
||||
|
||||
getStmt :: TypedStmt -> Stmt
|
||||
getStmt (TypedStmt s _) = s
|
||||
|
||||
getTypeFromExpr :: TypedExpr -> Type
|
||||
getTypeFromExpr (TypedExpr _ typ) = typ
|
||||
getTypeFromTypedStmtExpr :: TypedStmtExpr -> Type
|
||||
getTypeFromTypedStmtExpr (TAST.Assign _ _ t) = t
|
||||
getTypeFromTypedStmtExpr (TAST.New _ _ t) = t
|
||||
getTypeFromTypedStmtExpr (TAST.MethodCall _ _ _ t) = t
|
||||
|
||||
getTypeFromStmt :: TypedStmt -> Type
|
||||
getTypeFromStmt (TypedStmt _ typ) = typ
|
||||
getTypeFromTypedStmt :: TypedStmt -> Type
|
||||
getTypeFromTypedStmt (TAST.Block _ t) = t
|
||||
getTypeFromTypedStmt (TAST.Return _ t) = t
|
||||
getTypeFromTypedStmt (TAST.While _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.LocalVarDecl _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.If _ _ _ t) = t
|
||||
getTypeFromTypedStmt (TAST.StmtExprStmt _ t) = t
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Typechecking Class
|
||||
--------------------------------------------------
|
||||
|
||||
typeCheckClass :: AST.Class -> [(String, Type)] -> [AST.Class] -> TAST.TypedClass
|
||||
typeCheckClass (AST.Class typ fields methods) symtab cls =
|
||||
let checkedFields = map (\f -> typeCheckField f symtab cls) fields
|
||||
checkedMethods = map (\m -> typeCheckMethod m symtab cls) methods
|
||||
in TAST.Class typ checkedFields checkedMethods
|
||||
|
||||
typeCheckField :: AST.FieldDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedFieldDecl
|
||||
typeCheckField (AST.Field typ name Nothing) symtab cls =
|
||||
TAST.Field typ name Nothing
|
||||
typeCheckField (AST.Field expectedTyp name (Just expr)) symtab cls =
|
||||
let checkedExpr = typeCheckExpr expr symtab cls
|
||||
actualTyp = getTypeFromTypedExpr checkedExpr
|
||||
in if expectedTyp == actualTyp
|
||||
then TAST.Field expectedTyp name (Just checkedExpr)
|
||||
else error $ "Type mismatch: Expected " ++ expectedTyp ++ " found " ++ actualTyp
|
||||
|
||||
|
||||
|
||||
typeCheckMethod :: AST.MethodDecl -> [(String, Type)] -> [AST.Class] -> TAST.TypedMethodDecl
|
||||
typeCheckMethod (AST.Method typ name params stmt) symtab cls =
|
||||
let checkedStmt = typeCheckStmt stmt symtab cls
|
||||
stmtTyp = getTypeFromTypedStmt checkedStmt
|
||||
in if typ == stmtTyp || stmtTyp == "void"
|
||||
then TAST.Method typ name params checkedStmt
|
||||
else error $ "Return type mismatch: expected " ++ typ ++ ", got " ++ stmtTyp
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object" -- // TODO: implement proper class hierarchy and find the least common ancestor of t1 and t2 in that hierarchy
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmt :: Stmt -> [(String, Type)] -> [Class] -> TypedStmt
|
||||
-- If Statement
|
||||
typeCheckStmt (If cond body Nothing) symtab cls =
|
||||
typeCheckStmt :: AST.Stmt -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmt
|
||||
|
||||
-- If-Statement
|
||||
typeCheckStmt (AST.If cond body Nothing) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
in if getTypeFromExpr checkedCond == "boolean"
|
||||
then TypedStmt (If checkedCond checkedBody Nothing) (getTypeFromStmt checkedBody)
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
-- If-Else Statement
|
||||
typeCheckStmt (If cond body (Just elseBranch)) symtab cls =
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody Nothing typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- If-Else-Statement
|
||||
typeCheckStmt (AST.If cond body (Just elseBody)) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
checkedElse = fmap (\e -> typeCheckStmt e symtab cls) elseBranch
|
||||
in if getTypeFromExpr checkedCond == "boolean"
|
||||
then TypedStmt (If checkedCond checkedBody checkedElse) (upperBound (getTypeFromStmt checkedBody) (getTypeFromStmt checkedElse))
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
checkedElse = Just (typeCheckStmt elseBody symtab cls)
|
||||
typ = upperBound (getTypeFromTypedStmt checkedBody) (getTypeFromTypedStmt (maybe checkedBody id checkedElse))
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.If checkedCond checkedBody checkedElse typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- While-Statement
|
||||
typeCheckStmt (AST.While cond body) symtab cls =
|
||||
let checkedCond = typeCheckExpr cond symtab cls
|
||||
checkedBody = typeCheckStmt body symtab cls
|
||||
typ = getTypeFromTypedStmt checkedBody
|
||||
in if getTypeFromTypedExpr checkedCond == "boolean"
|
||||
then TAST.While checkedCond checkedBody typ
|
||||
else error "Condition in if statement must be of type boolean"
|
||||
|
||||
-- Return Statement
|
||||
typeCheckStmt (AST.Return maybeExpr) symtab cls =
|
||||
let checkedExpr = fmap (\expr -> typeCheckExpr expr symtab cls) maybeExpr
|
||||
typ = maybe "void" getTypeFromTypedExpr checkedExpr
|
||||
in TAST.Return checkedExpr typ
|
||||
|
||||
|
||||
typeCheckStmt (AST.LocalVarDecl typ str) _ _ =
|
||||
TAST.LocalVarDecl typ str typ
|
||||
|
||||
-- Block statement
|
||||
typeCheckStmt (AST.Block stmts) symtab cls =
|
||||
let checkedStmts = map (\s -> typeCheckStmt s symtab cls) stmts
|
||||
typ = case reverse checkedStmts of
|
||||
[] -> "void"
|
||||
(lastStmt : _) -> getTypeFromTypedStmt lastStmt
|
||||
in TAST.Block checkedStmts typ
|
||||
|
||||
-- Expression statement
|
||||
typeCheckStmt (AST.StmtExprStmt stmtExpr) symtab cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtab cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprStmt checkedStmtExpr typ
|
||||
|
||||
--------------------------------------------------
|
||||
-- Statement Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckStmtExpr :: AST.StmtExpr -> [(String, Type)] -> [AST.Class] -> TAST.TypedStmtExpr
|
||||
|
||||
-- Assign
|
||||
typeCheckStmtExpr (AST.Assign lhs rhs) symtab cls =
|
||||
let checkedLhs = typeCheckExpr lhs symtab cls
|
||||
checkedRhs = typeCheckExpr rhs symtab cls
|
||||
lhsType = getTypeFromTypedExpr checkedLhs
|
||||
rhsType = getTypeFromTypedExpr checkedRhs
|
||||
in if lhsType == rhsType || rhsType == "null"
|
||||
then TAST.Assign checkedLhs checkedRhs lhsType
|
||||
else error "Type mismatch in assignment"
|
||||
|
||||
-- New
|
||||
typeCheckStmtExpr (AST.New typ args) symtab cls =
|
||||
let checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
in TAST.New typ checkedArgs typ
|
||||
|
||||
-- Method call
|
||||
typeCheckStmtExpr (AST.MethodCall target methodName args) symtab cls =
|
||||
let checkedTarget = typeCheckExpr target symtab cls
|
||||
checkedArgs = map (\arg -> typeCheckExpr arg symtab cls) args
|
||||
targetType = getTypeFromTypedExpr checkedTarget
|
||||
returnType = lookupMethodReturnType targetType methodName cls
|
||||
in TAST.MethodCall checkedTarget methodName checkedArgs returnType
|
||||
|
||||
-- Helper methods
|
||||
lookupMethodReturnType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupMethodReturnType classType methodName classes =
|
||||
case findMethod classType methodName classes of
|
||||
Just t -> t
|
||||
Nothing -> error $ "Method not found: " ++ methodName ++ " in class " ++ classType
|
||||
|
||||
findMethod :: Type -> String -> [AST.Class] -> Maybe Type
|
||||
findMethod classType methodName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ _ methods) ->
|
||||
case find (\(AST.Method ret name _ _) -> name == methodName) methods of
|
||||
Just (AST.Method ret _ _ _) -> Just ret
|
||||
Nothing -> Nothing
|
||||
Nothing -> Nothing
|
||||
|
||||
lookupFieldType :: Type -> String -> [AST.Class] -> Type
|
||||
lookupFieldType classType fieldName classes =
|
||||
case find (\(AST.Class clsName _ _) -> clsName == classType) classes of
|
||||
Just (AST.Class _ fields _) ->
|
||||
case find (\(AST.Field _ name _) -> name == fieldName) fields of
|
||||
Just (AST.Field t _ _) -> t
|
||||
Nothing -> error $ "Field not found: " ++ fieldName ++ " in class " ++ classType
|
||||
Nothing -> error $ "Class not found: " ++ classType
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
-- Expression Typechecking
|
||||
--------------------------------------------------
|
||||
typeCheckExpr :: Expr -> [(String, Type)] -> [Class] -> TypedExpr
|
||||
typeCheckExpr :: AST.Expr -> [(String, Type)] -> [AST.Class] -> TAST.TypedExpr
|
||||
|
||||
-- boolean literals
|
||||
typeCheckExpr (Bool boolean) symtbl cls =
|
||||
TypedExpr (Bool boolean) "boolean"
|
||||
typeCheckExpr (AST.Bool value) symtbl cls =
|
||||
TAST.Bool value "boolean"
|
||||
|
||||
-- integer literals
|
||||
typeCheckExpr (Integer integer) symtbl cls =
|
||||
TypedExpr (Integer integer) "int"
|
||||
typeCheckExpr (AST.Integer value) symtbl cls =
|
||||
TAST.Integer value "int"
|
||||
|
||||
-- variable references
|
||||
typeCheckExpr (LocalOrFieldVar varName) symtbl cls =
|
||||
typeCheckExpr (AST.LocalOrFieldVar varName) symtbl cls =
|
||||
case lookup varName symtbl of
|
||||
Just t -> TypedExpr (LocalOrFieldVar varName) t
|
||||
Just t -> TAST.LocalOrFieldVar varName t
|
||||
Nothing -> error $ "Undefined variable: " ++ varName
|
||||
|
||||
-- this reference
|
||||
typeCheckExpr AST.This _ cls =
|
||||
case cls of
|
||||
(AST.Class className _ _ : _) -> TAST.This className
|
||||
[] -> error "No class context for this expression"
|
||||
|
||||
-- field access on an object
|
||||
typeCheckExpr (AST.InstVar obj fieldName) symtbl cls =
|
||||
let checkedObj = typeCheckExpr obj symtbl cls
|
||||
objType = getTypeFromTypedExpr checkedObj
|
||||
fieldType = lookupFieldType objType fieldName cls
|
||||
in TAST.InstVar checkedObj fieldName fieldType
|
||||
|
||||
-- unary operators
|
||||
typeCheckExpr (AST.Unary op expr) symtbl cls =
|
||||
let checkedExpr = typeCheckExpr expr symtbl cls
|
||||
exprType = getTypeFromTypedExpr checkedExpr
|
||||
in case op of
|
||||
AST.Not -> if exprType == "boolean"
|
||||
then TAST.Unary AST.Not checkedExpr "boolean"
|
||||
else error "Operator ! requires boolean operand"
|
||||
AST.Negate -> if exprType == "int"
|
||||
then TAST.Unary AST.Negate checkedExpr "int"
|
||||
else error "Unary - requires integer operand"
|
||||
|
||||
-- binary operators
|
||||
typeCheckExpr (AST.Binary op left right) symtbl cls =
|
||||
let checkedLeft = typeCheckExpr left symtbl cls
|
||||
checkedRight = typeCheckExpr right symtbl cls
|
||||
leftType = getTypeFromTypedExpr checkedLeft
|
||||
rightType = getTypeFromTypedExpr checkedRight
|
||||
in case op of
|
||||
AST.Add -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Subtract -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Multiply -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Divide -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Modulo -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompLessOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterThan -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompGreaterOrEqual -> checkIntComparison leftType rightType checkedLeft checkedRight op
|
||||
AST.CompEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.CompNotEqual -> checkEquality leftType rightType checkedLeft checkedRight op
|
||||
AST.BitAnd -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitOr -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.BitXor -> checkIntOp leftType rightType checkedLeft checkedRight op
|
||||
AST.And -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
AST.Or -> checkBoolOp leftType rightType checkedLeft checkedRight op
|
||||
|
||||
-- character literals
|
||||
typeCheckExpr (AST.Char value) _ _ =
|
||||
TAST.Char value "char"
|
||||
|
||||
-- null literal
|
||||
typeCheckExpr AST.Null _ _ =
|
||||
TAST.Null "null"
|
||||
|
||||
-- statement expression
|
||||
typeCheckExpr (AST.StmtExprExpr stmtExpr) symtbl cls =
|
||||
let checkedStmtExpr = typeCheckStmtExpr stmtExpr symtbl cls
|
||||
typ = getTypeFromTypedStmtExpr checkedStmtExpr
|
||||
in TAST.StmtExprExpr checkedStmtExpr typ
|
||||
|
||||
-- Helper methods
|
||||
checkIntOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntOp leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "int"
|
||||
else error "Integer binary operator requires int operands"
|
||||
|
||||
checkBoolOp :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkBoolOp leftType rightType left right op =
|
||||
if leftType == "boolean" && rightType == "boolean"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Boolean binary operator requires boolean operands"
|
||||
|
||||
checkIntComparison :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkIntComparison leftType rightType left right op =
|
||||
if leftType == "int" && rightType == "int"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error "Comparison operator requires int operands"
|
||||
|
||||
checkEquality :: Type -> Type -> TAST.TypedExpr -> TAST.TypedExpr -> AST.BinaryOperator -> TAST.TypedExpr
|
||||
checkEquality leftType rightType left right op =
|
||||
if leftType == rightType || leftType == "null" || rightType == "null"
|
||||
then TAST.Binary op left right "boolean"
|
||||
else error $ "Equality operator: cannot compare " ++ leftType ++ " with " ++ rightType
|
||||
|
||||
upperBound :: Type -> Type -> Type
|
||||
upperBound t1 t2
|
||||
| t1 == t2 = t1
|
||||
| otherwise = "Object"
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
func Main.main : int
|
||||
entry:
|
||||
t0 = 0
|
||||
return t0
|
||||
@@ -1,11 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(Block
|
||||
[
|
||||
Return (Integer 0)
|
||||
]
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
class Main {
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[
|
||||
Class "Main" []
|
||||
[
|
||||
Method "int" "main" []
|
||||
(TypedStmt
|
||||
(Block
|
||||
[
|
||||
TypedStmt
|
||||
(Return (Integer 0))
|
||||
"int"
|
||||
]
|
||||
)
|
||||
"int"
|
||||
)
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
|
||||
# run all tests: ./test.sh 2>&1 | grep -E "Testing|Results"
|
||||
# inspect a diff: diff <(javap -c /tmp/compoiler-ref/FieldsTest.class) <(javap -c /tmp/compoiler-out/FieldsTest.class)
|
||||
|
||||
JAVA_DIR="src/Testsuite/javaFiles"
|
||||
REF_DIR="/tmp/compoiler-ref"
|
||||
OUT_DIR="/tmp/compoiler-out"
|
||||
PASS=0
|
||||
FAIL=0
|
||||
ERROR=0
|
||||
|
||||
mkdir -p "$REF_DIR" "$OUT_DIR"
|
||||
|
||||
for javafile in "$JAVA_DIR"/*.java; do
|
||||
classname=$(basename "$javafile" .java)
|
||||
echo -n "Testing $classname ... "
|
||||
|
||||
# Run our compiler
|
||||
compiler_out=$(src/Main "$javafile" 2>&1)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR (compiler crashed)"
|
||||
echo " $compiler_out"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Move generated .class to OUT_DIR
|
||||
mv "out/${classname}.class" "$OUT_DIR/" 2>/dev/null
|
||||
if [ ! -f "$OUT_DIR/${classname}.class" ]; then
|
||||
echo "ERROR (no .class generated)"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Compile reference with javac
|
||||
javac "$javafile" -d "$REF_DIR" 2>/dev/null
|
||||
if [ ! -f "$REF_DIR/${classname}.class" ]; then
|
||||
echo "ERROR (javac failed)"
|
||||
((ERROR++))
|
||||
continue
|
||||
fi
|
||||
|
||||
# Compare structure and bytecode only (strip CP indices from instructions,
|
||||
# skip debug info like LineNumberTable and SourceFile)
|
||||
normalize() {
|
||||
javap -c "$1" 2>&1 \
|
||||
| grep -v "^Classfile\|Last modified\|SHA-256\|Compiled from\|LineNumberTable\|line [0-9]\|SourceFile" \
|
||||
| sed 's/#[0-9]*/\#N/g' \
|
||||
| sed 's/[[:space:]]*\/\/.*$//'
|
||||
}
|
||||
ref=$(normalize "$REF_DIR/${classname}.class")
|
||||
mine=$(normalize "$OUT_DIR/${classname}.class")
|
||||
|
||||
if diff <(echo "$ref") <(echo "$mine") > /dev/null 2>&1; then
|
||||
echo "PASS"
|
||||
((PASS++))
|
||||
else
|
||||
echo "FAIL"
|
||||
diff <(echo "$ref") <(echo "$mine")
|
||||
((FAIL++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Results: $PASS passed, $FAIL failed, $ERROR errors"
|
||||
Reference in New Issue
Block a user