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

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
This diff is collapsed.
--- Types for representing ICurry programs
--- @author Marc Andre Wittorf
module ICurry.Extended.Types where
import ICurry.Types
--- An Extended assignment
type IEAssign = (IVarIndex, IEExpr) -- Variable = Expression
--- An Extended module
data IEProg
= IEProg
String -- module name
[String] -- imports
[IEDataType] -- declared data types
[IEFunction] -- declared functions
deriving (Show, Read)
type IEQName = (IQName, Int) -- name and a module-unique number to represent
-- this name
--- An Extended visibility
--- @cons Public Visible and usable from other modules
--- @cons Private Invisible and not usable from other modules
data IEVisibility
= Public -- usable from other modules
| Private -- usable only in this module
deriving (Show, Read)
--- An Extended data type
type IEDataType = (
IEQName, -- the data type's name
IEVisibility, -- if this data type can be used from other modules
[ITVarIndex], -- type variables the type is parameterized over
[IEConstructor]) -- all possible constructors
--- An Extended constructor
data IEConstructor
= IEConstructor
IEQName -- the constructor's name
IEVisibility -- if this constructor can be used from other modules
[IETExpr] -- one type expression for each argument
deriving (Show, Read)
--- An Extended type expression
--- @cons IETVar a type argument
--- @cons IETFunc a functional type
--- @cons IETCons a type application
data IETExpr
= IETVar
ITVarIndex -- the type variable
| IETFunc
IETExpr -- domain type
IETExpr -- range type
| IETCons
IEQName -- the type's name that is applied
[IETExpr] -- the arguments
deriving (Show, Read)
--- An Extended function
data IEFunction
= IEFunction
IEQName -- the function's name
IEVisibility -- if this function can be used from other modules
IEFuncBody -- what the function does
deriving (Show, Read)
--- An Extended function's behavior
--- @cons IEExternal the function is externally defined
--- @cons IEFuncBody the function is defined here
data IEFuncBody
= IEExternal
IArity -- the function's arity
String -- the function's external name
| IEFuncBody
[IVarIndex] -- the function's arguments
IEBlock -- the function's actual behavior
deriving (Show, Read)
--- An Extended block
--- @cons IESimpleBlock unconditional evaluation
--- @cons IECaseConsBlock conditional evaluation over constructor terms
--- @cons IECaseLitBlock conditional evaluation over literals
data IEBlock
= IESimpleBlock
[IEAssign] -- assignments to local variables
IEExpr -- the return expression
| IECaseConsBlock
[IEAssign] -- assignments to local variables
IVarIndex -- the variable to differentiate by
[IEConsBranch] -- the possible branches
| IECaseLitBlock
[IEAssign] -- assignments to local variables
IVarIndex -- the variable to differentiate by
[IELitBranch] -- the possible branches
deriving (Show, Read)
--- An Extended branch over constructors
data IEConsBranch
= IEConsBranch
IEQName -- the constructor to match this branch
[IVarIndex] -- variable bindings
IEBlock -- what happens if this branch is taken
deriving (Show, Read)
--- An Extended branch over literals
data IELitBranch
= IELitBranch
ILiteral -- the literal to match this branch
IEBlock -- what happens if this branch is taken
deriving (Show, Read)
--- An Extended expression
--- @cons IEVar a variable
--- @cons IELit a literal
--- @cons IEFCall a function call
--- @cons IECCall a constructor call
--- @cons IEOr a non-deterministic choice
data IEExpr
= IEVar
IVarIndex -- the variable
| IELit
ILiteral -- the literal's value
| IEFCall
IEQName -- the function's name
[IEExpr] -- the arguments
| IECCall
IEQName -- the constructor's name
[IEExpr] -- the arguments
| IEOr
[IEExpr] -- the possibilities
deriving (Show, Read)
This diff is collapsed.
--- Extract 'import'-directives from Curry sources without completely parsing them
--- This is meant to be a much faster (although not completely correct) method
--- to just get this small piece of information
--- @author Marc Andre Wittorf
module ICurry.FindAllImports(
findAllImports,
findModuleName
) where
-- small (and probably not 100% in line with specs) module to find all imports
-- in a curry file
-- at least a line break between the `import` keyword and the module name
-- will cause problems
import Char