Commit 4b3736ec authored by Michael Hanus 's avatar Michael Hanus
Browse files

Generic intermediate format for compiling Curry to imperative languages packaged

parents
Pipeline #399 failed with stages
*~
.cpm
.curry
src/Main/ConfigPackage.curry
build.ninja
.ninja_deps
.ninja_log
/build
*.aux
*.log
*.pdf
*.synctex.gz
*.toc
Copyright (c) 2018, Marc Andre Wittorf
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the names of the copyright holders nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
ICurry
======
ICurry is an intermediate format to compile Curry to different imperative
languages.
Its purpose is to be mostly generic, so any target language can be supported
with a similar effort.
The `icurry` package includes a format `ICurry` (see `ICurry.Types`),
a format `Extended ICurry` (see `ICurry.Extended.Types`), a translator
from Flat- to ICurry, from I- to Extended ICurry, some goodies for
dealing with (Extended) ICurry structures and parts to quickly create a build
system based on Ninja.
The translators are available in the `icurry` binary installed
with this package.
\documentclass[12pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[english]{babel}
\usepackage[T1]{fontenc}
\usepackage{listings}
\usepackage{algorithm2e}
\usepackage{amssymb}
\usepackage{url}
\author{Marc André Wittorf\\[1ex]
{\small Institut f\"ur Informatik, CAU Kiel, Germany}}
\title{Implementation Guide}
\begin{document}
\maketitle
\tableofcontents
\newpage\noindent
\input{implementation_guide_contents}
\end{document}
This diff is collapsed.
{
"name": "icurry",
"version": "2.0.0",
"author": "Marc Andre Wittorf <stu114055@mail.uni-kiel.de>",
"synopsis": "Generic intermediate format for compiling Curry to imperative languages",
"category": [ "Metaprogramming" ],
"license": "BSD-3-Clause",
"licenseFile": "LICENSE",
"dependencies": {
"base" : ">= 1.0.0, < 2.0.0",
"currypath" : ">= 0.0.1",
"flatcurry-annotated": ">= 2.0.0",
"frontend-exec" : ">= 0.0.1",
"ninja" : ">= 1.0.0"
},
"compilerCompatibility": {
"pakcs": ">= 2.0.0",
"kics2": ">= 2.0.0"
},
"configModule": "Main.ConfigPackage",
"executable":{
"name": "icurry"
},
"documentation": {
"src-dir": "docs",
"main": "implementation_guide.tex"
},
"source": {
"git": "https://git.ps.informatik.uni-kiel.de/curry-packages/icurry.git",
"tag": "$version"
}
}
--- Module for simpler generation of Ninja build files for ICurry
--- @author Marc Andre Wittorf
module ICurry.Build(
generateFlatCurryEdges, generateFlatCurryEdge,
generateICurryEdges, generateICurryEdge,
generateEdges,
tfcyRule, icyRule,
curryToFlatPath
) where
-- generates ninja files to build icurry/extended icurry files from curry
-- source files
import List
import Maybe
import FilePath
import System.CurryPath
import ICurry.DependencyResolution
import ICurry.Files
import Ninja.Types
--- Generate build edges to translate Curry to Typed FlatCurry
--- @param deps all modules with dependencies
--- @return (build edge) ninja declarations
generateFlatCurryEdges :: [ModuleDep] -> [Decl]
generateFlatCurryEdges deps = map (generateFlatCurryEdge deps) deps
--- Generate a build edge to translate Curry to Typed FlatCurry
--- @param allDeps all dependencies
--- @param dep a module with its dependencies
--- @return a (build edge) ninja declaration
generateFlatCurryEdge :: [ModuleDep] -> ModuleDep -> Decl
generateFlatCurryEdge allDeps (ModuleDep modname path deps) =
Edge "tfcy" [curryToFlatPath modname path]
[]
[path]
(map transDep deps)
[]
[]
where
transDep d = curryToFlatPath d $
modulePath $
fromJust $
find ((d ==) . moduleName)
allDeps
--- Find the path to a Typed FlatCurry file
--- @param m the module parts
--- @param path the path to the curry file
--- @return the path to the tfcy file
curryToFlatPath :: String -> FilePath -> FilePath
curryToFlatPath [] p = replaceExtension p "tfcy"
curryToFlatPath m@(_:_) p = replaceExtension (inCurrySubdirModule m p) "tfcy"
--- Generate build edges to translate Typed FlatCurry to ICurry
--- @param prefix the icy files will be placed here
--- @param depd modules with their dependencies
--- @return (build edge) ninja declarations
generateICurryEdges :: FilePath -> [ModuleDep] -> [Decl]
generateICurryEdges prefix = map (generateICurryEdge prefix)
--- Generate a build edge to translate Typed FlatCurry to ICurry
--- @param prefix the icy file will be placed here
--- @param dep a module with its dependencies
--- @return a (build edge) ninja declaration
generateICurryEdge :: FilePath -> ModuleDep -> Decl
generateICurryEdge prefix (ModuleDep modname path deps) =
Edge "icy" [prefix </> modNameToPath modname <.> "icy"]
[prefix </> modNameToPath modname <.> "ictdeps"]
[curryToFlatPath modname path]
(map (\d -> prefix </> modNameToPath d <.> "ictdeps") deps)
[]
[]
--- Automatically generate edges for a dependency graph
---
--- generateEdges 'f2i' 'tfcy' 'icy' True '/path' deps
--- This generates edges for the `deps` dependency graph.
--- All files are in a module structure under /path.
--- Each file *.tfcy is translated into a *.icy file by the rule f2i.
--- As wantNeighborDep is True, Prelude.icy must be compiled before MyModule.icy
--- can be compiled, assuming that Prelude is a dependency of MyModule.
--- If wantNeighborDep was False, MyModule.tfcy could be translated to
--- MyModule.icy independently from the Prelude.
---
--- @param ruleName the rule to use
--- @param srcExt the source file extension
--- @param tgExt the target file extension
--- @param wantNeighborDep are targets of dependencies required?
--- @param prefix the path where the module structure is located
--- @param depgraph the module dependency graph
--- @return ninja (build edge) declarations
generateEdges :: String
-> String
-> String
-> Bool
-> Maybe FilePath
-> FilePath
-> [ModuleDep]
-> [Decl]
generateEdges ruleName srcExt tgExt wantNeighborDep outPrefix prefix =
map generateEdge
where
generateEdge (ModuleDep modname path deps) =
Edge ruleName
[maybe prefix id outPrefix </> modNameToPath modname <.> tgExt]
[]
[prefix </> modNameToPath modname <.> srcExt]
(if wantNeighborDep
then (map (\d -> prefix </> modNameToPath d <.> tgExt) deps)
else [])
[]
[]
-- TODO: autodetect curry frontend and library paths
--- Ninja rule to compile Curry to Typed FlatCurry
--- @return the rule declaration
tfcyRule :: Decl
tfcyRule = Rule "tfcy"
[("command",
"kics2-frontend --typed-flat \
\-i \"$$(realpath \"$$(dirname \"$$(which kics2-frontend)\")\")/../lib\" \
\$in")]
--- Ninja rule to compile Typed FlatCurry to ICurry
--- @return the rule declaration
icyRule :: FilePath -> Decl
icyRule path = Rule "icy"
[("command", "icurry f2i -I \"" ++ path ++ "\" $in $out")]
This diff is collapsed.
--- Find all dependencies of modules recursively
--- @author Marc Andre Wittorf
module ICurry.DependencyResolution(
ModuleDep(..),
buildDepGraph
) where
import FilePath
import Either
import List
import ICurry.Files
import ICurry.FindAllImports
--- A module with its dependencies
data ModuleDep = ModuleDep
{ moduleName :: String -- module name
, modulePath :: FilePath -- module source file path
, moduleDeps :: [String]} -- names of modules that this module depends on
deriving (Show)
--- Load a module's dependencies from a file
--- @param fname the module's file name
--- @return a moduleDep for this module
loadModuleFileDeps :: FilePath -> IO ModuleDep
loadModuleFileDeps fname = do
fileContents <- readFile fname
let imports = findAllImports fileContents
let moduleName = maybe "" id $ findModuleName fileContents
return $ ModuleDep moduleName fname imports
--- Load module's dependencies from a file. Search the module in search paths
--- @param paths search paths for modules
--- @param modname the module's name
--- @return a moduleDep for this module
loadModuleDeps :: [FilePath] -> String -> IO ModuleDep
loadModuleDeps paths modname = do
filename <- lookupCurryFile paths modname
if modname == "Prelude"
then return $
ModuleDep modname
(maybe (error "Prelude not found. Something is fishy.")
id
filename)
[]
else do
mdep <- loadModuleFileDeps $ maybe (error "Module not found") id filename
return $ mdep{moduleName = modname}
--- Recursively load module's dependencies
--- @param paths search paths for modules
--- @param initials the modules to find dependencies of
--- @return moduleDeps for the modules and all dependencies
buildDepGraph :: [FilePath] -> [(Either FilePath String)] -> IO [ModuleDep]
buildDepGraph paths initials = do
modDeps <- mapM (loadModuleDeps paths) modInitials
fileDeps <- mapM loadModuleFileDeps fileInitials
let deps = nubBy samePath $ modDeps ++ fileDeps
res <- bdg deps $ nub $ concatMap moduleDeps deps
return $ nubBy samePath res
where
fileInitials = nub $ map fromLeft $ filter isLeft initials
modInitials = nub $ map fromRight $ filter isRight initials
bdg :: [ModuleDep] -> [String] -> IO [ModuleDep]
bdg ds [] = return ds
bdg ds (m:ms) = if any (isMod m) ds then bdg ds ms else do
nd <- loadModuleDeps paths m
bdg (nd:ds) (ms ++ filter (not . (`elem` ms)) (moduleDeps nd))
--- Do moduleDeps reference modules with the same paths?
--- @param a one moduleDep
--- @param b another moduleDep
--- @return True iff the paths are equal
samePath :: ModuleDep -> ModuleDep -> Bool
samePath a b = modulePath a == modulePath b
--- Do moduleDeps reference modules with the same name?
--- @param a one moduleDep
--- @param b another moduleDep
--- @return True iff the module names are equal
sameModname :: ModuleDep -> ModuleDep -> Bool
sameModname a b = moduleName a == moduleName b
--- Check if a moduleDep references a module with a given name
--- @param m a module name
--- @param d another moduleDep
--- @return True d references a module with name n
isMod :: String -> ModuleDep -> Bool
isMod m d = m == moduleName d
--- Module for simpler generation of Ninja build files for Extended ICurry
--- @author Marc Andre Wittorf
module ICurry.Extended.Build(
generateExtendedICurryEdges,
eicyRule,
cToEicy
) where
import FilePath
import Ninja.Types
import ICurry.DependencyResolution
import ICurry.Build
--- Generate build edges to translate ICurry to Extended ICurry
--- @param prefix the icy and eicy files are here
--- @param depd modules with their dependencies
--- @return (build edge) ninja declarations
generateExtendedICurryEdges :: FilePath -> [ModuleDep] -> [Decl]
generateExtendedICurryEdges = generateEdges "eicy" "icy" "eicy" True Nothing
--- Ninja rule to compile ICurry to Extended ICurry
--- @return the rule declaration
eicyRule :: FilePath -> Decl
eicyRule path = Rule "eicy"
[("command", "icurry i2e -I \"" ++ path ++ "\" $in $out")]
--- Rules and Edges to compile Curry modules to Extended ICurry
--- @param buildDir the path where I- and Extended ICurry files will be stored
--- @param dg all modules with their dependencies
--- @return ninja declarations
cToEicy :: String -> [ModuleDep] -> [Decl]
cToEicy buildDir dg = [tfcyRule, icyRule buildDir, eicyRule buildDir]
++ generateFlatCurryEdges dg
++ generateICurryEdges buildDir dg
++ generateExtendedICurryEdges buildDir dg
--- Module for handling Extended ICurry files
--- @author Marc Andre Wittorf
module ICurry.Extended.Files where
import FileGoodies
import FilePath
import System.CurryPath
import ICurry.Extended.Types
import ICurry.Files
--- Look up an Extended ICurry file in the search paths
--- @param paths the search paths
--- @param modname the module name
--- @return the module's path
lookupIECurryFile :: [String] -> String -> IO (Maybe String)
lookupIECurryFile paths modname =
lookupFileInPath base
[".iecy"]
(map ((</> dir) . addCurrySubdir) paths)
>>= normaliser
where
(dir, base) = splitDirectoryBaseName $ modNameToPath modname
--- Get an Extended ICurry file in the search paths. Error if not found
--- @param paths the search paths
--- @param modname the module name
--- @return the module's path
getIECurryFile :: [String] -> String -> IO String
getIECurryFile = lookupToGet lookupIECurryFile
(error "Cannot find IECurry file")
--- Read an Extended ICurry file. Error if not found
--- @param paths the search paths
--- @param modname the module name
--- @return the Extended ICurry abstract representation
readIECurry :: [String] -> String -> IO (IEProg)
readIECurry paths modname = do
fname <- getIECurryFile paths modname
contents <- readFile fname
return $ read contents
--- Write an Extended ICurry file. Find target directory based on source file
--- @param paths the search paths
--- @param modname the module name (for finding correct path)
--- @param prog the Extended ICurry module
writeIECurry :: [String] -> String -> IEProg -> IO ()
writeIECurry paths modname prog = do
filename <- getPathForModule paths modname >>= return . (<.> "iecy")
writeFile filename $ show prog
--- Some convenience functions for dealing with Extended ICurry structures
--- @author Marc Andre Wittorf
module ICurry.Extended.Goodies(
isExternal, isGlobal,
isPublic, isPrivate,
escapeSpecials
) where
import ICurry.Extended.Types
--- Check if a function is defined externally
--- @param f the function
--- @return True iff the function is defined externally
isExternal :: IEFunction -> Bool
isExternal f = case f of
IEFunction _ _ (IEExternal _ _) -> True
_ -> False
--- Check if a function is a call to the special 'global' function
--- @param f the function
--- @return True iff the function is a global
isGlobal :: IEFunction -> Bool
isGlobal f = case f of
IEFunction _ _ (IEFuncBody [] (IESimpleBlock _ (IEFCall ((mn,ln),_) es))) ->
mn == "Global" && ln == "global" && length es == 2
_ -> False
--- Escape special characters in a string
---
--- This includes many special characters common in curry which are no valid
--- identifiers in many imperative languages
---
--- @param s the unescaped string
--- @param the escaped string
escapeSpecials :: String -> String
escapeSpecials = concatMap replaceBadChars
where
replaceBadChars c = case c of -- q w still available
'_' -> "_u" --Underscore
',' -> "_c" --Comma
'.' -> "_d" --Dot
':' -> "_n" --coloN
'#' -> "_h" --Hash
'+' -> "_p" --Plus
'-' -> "_m" --Minus
'*' -> "_y" --multiplY
'/' -> "_v" --diV
'\'' -> "_t" --Tick
'<' -> "_l" --Lower
'>' -> "_g" --Greater
'=' -> "_e" --Equal
'(' -> "_o" --parens Open
')' -> "_s" --parens cloSe
'[' -> "_b" --Brackets open
']' -> "_k" --bracKets close
'&' -> "_a" --Ampersand
'|' -> "_i" --pIpe
'$' -> "_r" --dollaR sign
'!' -> "_x" --eXclamation mark
'?' -> "_j" --questJon mark (running out of letters here)
'\\' -> "_z" --backZlash
'^' -> "_f" --circumFlex
otherwise -> [c]
--- Predicate to test if a visibility is Public
--- @param v the visibility
--- @return True if v is Public, False otherwise
isPublic :: IEVisibility -> Bool
isPublic Public = True
isPublic Private = False
--- Predicate to test if a visibility is Private
--- @param v the visibility
--- @return True if v is Private, False otherwise
isPrivate :: IEVisibility -> Bool
isPrivate Private = True
isPrivate Public = False
--- Transform ICurry to Extended ICurry
---
--- - Give each name an additional numerical identifier
--- - Limit the maximum nesting depth
---
--- @author Marc Andre Wittorf
module ICurry.Extended.I2E(
i2eProg,
extractDepInfoFromI
) where
import Maybe
import List
import ICurry.Extended.Types
import ICurry.Types
data DepInfo =
DepInfo
{ typeDInfo :: [IEQName]
, consDInfo :: [IEQName]
, funcDInfo :: [IEQName]
} deriving (Show)
type DepInfos = [(String, DepInfo)] -- for all imported modules
--- Translate an ICurry name to an Extended ICurry name
--- @param accessor the lookup function for names
--- @param di the information about all dependencies
--- @param qn the name
--- @return the name
i2eQN :: (DepInfo -> [IEQName]) -> DepInfos -> IQName -> IEQName
i2eQN accessor di qn@(mn,_) =
fromJust $ (lookup mn di
>>= lookup qn . accessor
>>= \x -> return (qn,x))
`mplus`
Just (error $ "Cannot find module: " ++ show qn)
--- Some special types which are not directly used by a source program
specialTypes :: [IEQName]
specialTypes = [
(("Prelude","Apply"), -1)]
--- Translate an ICurry type name to an Extended ICurry type name
--- @param di the information about all dependencies
--- @param qn the type name
--- @return the type name
i2eQNType :: DepInfos -> IQName -> IEQName
i2eQNType di qn = case lookup qn specialTypes of
Just x -> (qn,x)
Nothing -> i2eQN typeDInfo di qn
--- Translate an ICurry constructor name to an Extended ICurry constructor name
--- @param di the information about all dependencies
--- @param qn the constructor name
--- @return the constructor name
i2eQNCons :: DepInfos -> IQName -> IEQName
i2eQNCons = i2eQN consDInfo
--- Some special functions
--- these are needed as free variables are translated into generator functions
specialFunctions :: [IEQName]
specialFunctions =
[ (("Prelude","Int"), -1)
, (("Prelude","Bool"), -1)
, (("Prelude","Float"), -1)]
--- Translate an ICurry function name to an Extended ICurry function name
--- @param di the information about all dependencies
--- @param qn the function name
--- @return the function name
i2eQNFunc :: DepInfos -> IQName -> IEQName
i2eQNFunc di qn = case lookup qn specialFunctions of
Just x -> (qn,x)
Nothing -> i2eQN funcDInfo di qn
--- Get all defined names from an ICurry module
--- @param prog the module
--- @return the module name and the defined names
extractDepInfoFromI :: IProg -> (String, DepInfo)
extractDepInfoFromI (IProg n _ dts fns) = (n, DepInfo ti ci fi)
where
(ti,ci) = extractTypeInfosFromI dts
fi' = map (\(x, n) -> (x,n+1+maxType)) $ extractFuncInfosFromI fns
maxType = maximum $ -1 : map snd ti
fi = ti ++ fi'
--- Get defined type names and constructor names from ICurry data types
--- @param dts the data types
--- @return the data type names and constructor names
extractTypeInfosFromI :: [IDataType] -> ([IEQName], [IEQName])
extractTypeInfosFromI = extractTypeInfosFromI' 0
where
extractTypeInfosFromI' :: Int -> [IDataType] -> ([IEQName], [IEQName])
extractTypeInfosFromI' _ [] = ([],[])
extractTypeInfosFromI' nt (t:ts) =
let
(ti, ci) = extractTypeInfoFromI nt t
(ti', ci') = extractTypeInfosFromI' (nt + 1) ts
in
(ti:ti', ci ++ ci')
extractTypeInfoFromI nt (n, k, _, cs) = ((n,nt), ci)
where
ci = zipWith (\i (IConstructor c _ _) -> (c,i)) [0..] cs
--- Get defined function names from ICurry functions
--- @param fns the functions
--- @return the functions' names
extractFuncInfosFromI :: [IFunction] -> [IEQName]
extractFuncInfosFromI = zipWith efi [0..]
where
efi :: Int -> IFunction -> IEQName
efi i (IFunction n _ _) = (n, i)
--- Transform an ICurry module to an Extended ICurry module
--- @param di the information about all dependencies
--- @param prog the module
--- @return the module
i2eProg :: DepInfos -> IProg -> IEProg
i2eProg di' prog@(IProg n is dts fns) =
IEProg n is (map (i2eDType di) dts) (map (i2eFunc di . fixDeepNests) fns)
where
di = extractDepInfoFromI prog:di'
--- Transform an ICurry data type to an Extended ICurry data type
--- @param di the information about all dependencies
--- @param dt the data type
--- @return the data type