Files
RosettaHaskellCompiler/app/Main.hs
2022-08-08 03:06:47 +02:00

211 lines
9.8 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
module Main where
import Parser.Enum
import Parser.Type
import Parser.Function
import Parser.General
import Model.RosettaObject
import qualified Data.Text as Text
import Text.Megaparsec
import PrettyPrinter.Enum
import PrettyPrinter.Type
import PrettyPrinter.Function
import PrettyPrinter.RosettaObject
import Semantic.TypeChecker
import Semantic.ExpressionChecker
import Semantic.FunctionChecker
import Model.Type
import Model.Function
import System.Environment.Blank (getArgs)
import Model.Enum
import Data.Either
import Model.Header
import Parser.Header
import PrettyPrinter.Header
import Data.Tuple (fst, snd)
import Data.Void
import System.FilePath
import Utils.Utils
import System.Directory
import Data.List
import Data.Text (Text)
import Parser.Expression (expressionParser)
-- :l resources/Generated/ContractDSL.hs resources/Generated/ImportsType.hs resources/Generated/ImportsEnum.hs
-- :set args resources/Rosetta/Contracts/
-- :set args resources/CDM/
-- |Reads a rosetta string from the first input argument and writes a haskell output to the file given as a second argument
main :: IO ()
main = do
args <- getArgs
let mainFolder = head args
files <- getDirectoryContents mainFolder
parseResult <- parseFolder [mainFolder ++ f | f <- files, f `notElem` [".", ".."]]
let completeFiles = [createSymbolTable f parseResult | f <- parseResult]
let checked = checkObjects completeFiles
let headers = fstlst checked
let objects = nestedRights $ sndlst checked
--_ <- error $ show [(map typeName t, map symbolName s) | (path, t, s, (head, obj)) <- completeFiles, path == "resources/Rosetta/Contracts/contractDSL.rosetta"]
if null $ lefts $ concat $ sndlst checked
then
let input = pairRights checked in
mapM_ generateFile input
else error $ show $ lefts $ concat $ sndlst checked
{- | Parse the contents of a folder, skip any files without the Rosetta extension
Return a list of Header, Rosettaobject, filename pairs
-}
parseFolder :: [FilePath] -> IO [(FilePath, Header, [RosettaObject])]
parseFolder [] = return []
parseFolder (file : files) = do
plain <- readFile file
rest <- parseFolder files
case parseFile plain of
Left errorBundle -> error $ errorBundlePretty errorBundle ++ " on file " ++ file
Right (header, objs) ->
return ((file, header, objs) : rest)
-- | Create the symbol table for each file from the imported types enums and functions
createSymbolTable :: (FilePath, Header, [RosettaObject]) -> [(FilePath, Header, [RosettaObject])] -> (FilePath, [Type], [Symbol], (Header, [RosettaObject]))
createSymbolTable (file, MakeHeader name desc ver imp, objs) imps =
(file, concat (fstlst imports), concat (sndlst imports), (MakeHeader name desc ver actualImport, objs))
where
imports = [(getTypes imObjs, getFunctions imObjs) | (_, MakeHeader imName _ _ _, imObjs) <- imps, imName `elem` map getNamespace imp || imName == name]
actualImport =
[removeChar (takeFileName (dropExtension p)) '-' | (p, MakeHeader imName _ _ _, imObjs) <- imps, imName `elem` map getNamespace imp
|| (imName == name && p /= file && not ("enum.rosetta" `isSuffixOf` file) && not ("type.rosetta" `isSuffixOf` file && "func.rosetta" `isSuffixOf` p))]
-- |Get the defined data types from a list of rosetta objects
getTypes :: [RosettaObject] -> [Type]
getTypes [] = []
getTypes (TypeObject o : os) = o{typeAttributes = map convertAttributeType (typeAttributes o)} : getTypes os
getTypes (EnumObject o : os) = convertEnumToType o : getTypes os
getTypes (FunctionObject o : os) = getTypes os
convertAttributeType :: TypeAttribute -> TypeAttribute
convertAttributeType (MakeTypeAttribute n (MakeType "int" _ _ _ _) c d) = MakeTypeAttribute n (BasicType "Integer") c d
convertAttributeType (MakeTypeAttribute n (MakeType "string" _ _ _ _) c d) = MakeTypeAttribute n (BasicType "String") c d
convertAttributeType (MakeTypeAttribute n (MakeType "number" _ _ _ _) c d) = MakeTypeAttribute n (BasicType "Double") c d
convertAttributeType (MakeTypeAttribute n (MakeType "boolean" _ _ _ _) c d) = MakeTypeAttribute n (BasicType "Bool") c d
convertAttributeType (MakeTypeAttribute n (MakeType "time" _ _ _ _) c d) = MakeTypeAttribute n (BasicType "Time") c d
convertAttributeType t = t
getFunctions :: [RosettaObject] -> [Symbol]
getFunctions [] = []
getFunctions (FunctionObject o : os) = functionToSymbol o : getFunctions os
getFunctions (o : os) = getFunctions os
functionToSymbol :: Function -> Symbol
functionToSymbol (MakeFunction (MakeFunctionSignature name _ inp out) _ _) = Func name [(attributeType (convertAttributeType i), Model.Type.cardinality i)| i <- inp] (attributeType out, Model.Type.cardinality out)
{- |Recursively parse a file and all the imports into a list of headers and objects
The first argument is a list of files already parsed (to handle cyclic imports) and the second is the name of the file to be parsed
-}
parseWithImport :: [String] -> String -> IO [(([Type], [Symbol]), (Header, [RosettaObject]))]
parseWithImport files file
| file `elem` files = error $ "cyclic dependency on " ++ file
| otherwise =
do
plain <- readFile file
case parseFile plain of
Left errorBundle -> error $ errorBundlePretty errorBundle ++ "on file" ++ file
Right (MakeHeader name desc vers imp, objs) ->
do
let modules = map namespaceToName imp
importFiles <- getFiles modules
imports <- mapM (parseWithImport (file : files)) importFiles
let importedSymbolTable = fstlst (concat imports)
let importedTypes = concat $ fstlst importedSymbolTable
let importedFunctions = concat $ sndlst importedSymbolTable
case addNewTypes importedTypes objs of
Left errors -> error $ show errors ++ "\n on file " ++ file
Right definedTypes -> case addNewFunctions (definedTypes, importedFunctions) objs of
Left errors -> error $ show errors ++ "\n on file " ++ file
Right definedFunctions -> return $ ((definedTypes, definedFunctions), (MakeHeader name desc vers imp, objs)) : concat imports
getFiles :: [String] -> IO [String]
getFiles [] = return []
getFiles (n : ns)
| ".*" `isSuffixOf` n = do
files <- getDirectoryContents $ takeDirectory n
rest <- getFiles ns
return $ [f | f <- files, dropExtension n `isPrefixOf` f] ++ rest
| otherwise = do
files <- getFiles ns
return (n : files)
-- |Parse a file into a list of RosettaObjects
parseFile :: String -> Either (ParseErrorBundle Text Void) (Header, [RosettaObject])
parseFile plainText = parse rosettaParser "" (Text.pack plainText)
-- |Checks all the objects from a list
checkObjects :: [(FilePath, [Type], [Symbol], (Header, [RosettaObject]))] -> [((FilePath, Header), [Either [TypeCheckError] CheckedRosettaObject])]
checkObjects [] = []
checkObjects ((file, definedTypes, definedSymbols, (header, objs)) : rest) = ((file, header), checked) : checkObjects rest
where
checked = map (checkObject (definedTypes, definedSymbols)) objs
-- |Checks the RosettaObject for type errors
checkObject :: ([Type], [Symbol]) -> RosettaObject -> Either [TypeCheckError] CheckedRosettaObject
-- |Checks the type and attributes of a type
checkObject (definedTypes, _) (TypeObject t) =
case checkType definedTypes t of
Left errors -> Left errors
Right typ -> Right $ CheckedTypeObject typ
-- |If an enum parses, it cannot throw an error
checkObject _ (EnumObject e) = Right $ CheckedEnumObject e
-- |Checks the function inputs, output and assignment
checkObject (definedTypes, definedFunctions) (FunctionObject fun) =
case checkFunction (definedTypes, defaultMap ++ definedFunctions) fun of
Left errors -> Left errors
Right func -> Right $ CheckedFunctionObject func
-- |Adds new defined functions into the symbol table
addNewFunctions :: ([Type], [Symbol]) -> [RosettaObject] -> Either [TypeCheckError] [Symbol]
addNewFunctions (_, s) [] = Right []
addNewFunctions (t, s) ((FunctionObject f):os) =
case addNewFunctions (t, s) os of
Left errors -> Left errors
Right symbs -> addFunction (t, symbs) f
addNewFunctions (t, s) (_:os) = addNewFunctions (t, s) os
-- |Adds new defined types into the symbol table
addNewTypes :: [Type] -> [RosettaObject] -> Either [TypeCheckError] [Type]
addNewTypes l [] = Right l
addNewTypes defined (TypeObject o: os) =
case addNewTypes defined os of
Left errors -> Left errors
Right types -> addDefinedTypes types [o]
addNewTypes defined (EnumObject e: os) = addNewTypes defined (TypeObject (convertEnumToType e) : os)
addNewTypes defined (_ :os) = addNewTypes defined os
-- |Parses any supported Rosetta types into a list of RosettaObject
rosettaParser :: Parser (Header, [RosettaObject])
rosettaParser = do
header <- headerParser
objects <- many (try parseEnum <|> try parseType <|> try parseFunction) <* eof
return (header, objects)
-- |Reads an enum into a RosettaObject
parseEnum :: Parser RosettaObject
parseEnum = do
EnumObject <$> enumParser
-- |Parse a type into a RosettaObject
parseType :: Parser RosettaObject
parseType = do
TypeObject <$> typeParser
-- |Parse a function into a RosettaObject
parseFunction :: Parser RosettaObject
parseFunction = do
FunctionObject <$> functionParser
-- |Generate a new haskell file based on the rosetta objects and header
generateFile :: ((FilePath, Header), [CheckedRosettaObject]) -> IO ()
generateFile ((path, header), objects) =
writeFile (haskellFileName $ dropExtension $ takeFileName path)
(printHeader (dropExtension $ takeFileName path) header ++ concatMap printRosettaObject objects)