Commit f95850db authored by Michael Hanus 's avatar Michael Hanus

ICurry format according to WFLP'19 paper on ICurry

parent 9745f00a
*~
.cpm
.curry
src/Main/ConfigPackage.curry
build.ninja
.ninja_deps
.ninja_log
/build
*.aux
*.log
*.pdf
......
Copyright (c) 2018, Marc Andre Wittorf
Copyright (c) 2020, Michael Hanus
All rights reserved.
Redistribution and use in source and binary forms, with or without
......
......@@ -2,62 +2,74 @@ icurry
======
ICurry is an intermediate format to compile Curry to different imperative
languages.
Its purpose is to be mostly generic so that different target languages
languages. ICurry is intended to be generic so that different target languages
can be supported with a similar effort.
The definition of ICurry is inspired by the Curry compiler
[Sprite](http://dx.doi.org/10.1007/978-3-319-63139-4_6)
which compiles Curry programs into LLVM code.
The definition of ICurry implemented in this package follows the
[paper on ICurry](http://arxiv.org/abs/1908.11101).
The `icurry` package supports two kinds of intermediate formats:
This package contains the definition of ICurry as
Curry data types (module `ICurry.Types`), a simple compiler
to translate Curry programs to ICurry, and an interpreter
for ICurry programs based on the small-step semantics of ICurry.
The latter can also be used to visualize the graph constructed
during the execution of ICurry programs.
* `ICurry` (see `ICurry.Types` for detailed definitions)
* `Extended ICurry` (see `ICurry.Extended.Types` for detailed definitions)
The package also contains a translator from FlatCurry to ICurry programs
(which have the suffix `.icy`), a translator from ICurry to
Extended ICurry programs (which have the suffix `.eicy`), some goodies for
dealing with (Extended) ICurry structures and auxiliary to quickly
create a build system based on Ninja (see package `ninja`).
The translators are available in the `icurry` binary installed
These tools are available in the `icurry` binary installed
with this package.
Usage:
------
In contrast to other general compilers which can translate
complete applications consisting of several modules,
the `icurry` compiler translates only single modules
so that it should be used as a tool invoked by
appropriate build tools (e.g., `ninja`, `make`).
In particular, before compiling some module, all its
imported modules have to be compiled so that their interfaces
(data and function types) are already stored in
ICurry type dependencies files (suffix `.ictdeps`).
Note that the ICurry compiler is a prototype used to compile
single Curry modules into corresponding ICurry programs.
If a Curry module has several imports, one has to compile
these imports into ICurry manually (the automation of this
process will be done in the future).
In the following, we describe various uses of the `icurry` tool.
1. To compile a Curry program `Prog.curry` into the ICurry format,
invoke the command
For instance, to compile the Prelude manually,
one can change to the library directory containing
the file `Prelude.curry` and invoke the following commands:
> icurry Prog
1. Generate `Prelude.tfcy` (the typed FlatCurry file of the Prelude):
This will generate the file `.curry/Prog.icy`, i.e., the suffix `icy`
is used for generated ICurry programs.
> pakcs-frontend -i "." --typed-flat Prelude
In order to see a human-readable presentation of the generated program,
use option `-v`, i.e.,
or
> kics2-frontend -i "." --typed-flat Prelude
> icurry -v Prog
2. One can also use a simple (i.e., not efficient) interpreter
to execute ICurry programs and visualize their behavior.
In this case, one has to provide the name of a 0-ary function `mymain`
and invoke `icurry` by
> icurry -m mymain Prog
This compiles `Prog.curry` into ICurry (but do not store the
compiled program in a file), invokes the ICurry interpreter
(see `ICurry.Interpreter`), and shows the results of evaluating `mymain`.
The ICurry interpreter can also visualize the term graph manipulated
during execution as a PDF generated by `dot`. If `icurry` is invoked by
2. Generate `Prelude.icy` (the ICurry representation of the Prelude,
which also generates the ICurry type dependency file `Prelude.ictdeps`):
> icurry -m mymain --graph Prog
> icurry f2i -I ".curry" .curry/Prelude.tfcy .curry/Prelude.icy
the graph after each step is shown by `evince` (see parameter `--viewer`)
and each step is executed in one second.
3. Generate `Prelude.eicy` (the Extended ICurry representation of the Prelude):
To see more output information and perform individual steps,
add the option `--interactive`, e.g.,
> icurry i2e -I ".curry" .curry/Prelude.icy .curry/Prelude.eicy
> icurry -m mymain --graph --interactive Prog
Now one can compile modules using the Prelude in the same way.
More executions options are available by invoking the interpreter
manually via the operation `ICurry.Interpreter.execProg`.
----------------------------------------------------------------------------
-- Testing user-defined data types.
data BW = Black | White
invBW Black = White
invBW White = Black
aBW = White
aBW = Black
invSomeBW = invBW aBW
This diff is collapsed.
Compiling example programs
==========================
As mentioned in the README of the package, the current version
of the `icurry` compiler is intended to be invoked by
other build tools. Hence, the direct usage needs
some manual efforts. Thus, we describe below as
an example the direct translation of the example program `Rev.curry`
into the ICurry format.
Assumption:
* $CURRYHOME is defined as the main directory of the Curry implementation
(KiCS2 or PAKCS)
1. Generate the typed FlatCurry files of module `Rev`
(in `.curry/Rev.tfcy`) and the prelude (if PAKCS is used,
replace `kics2` by `pakcs`):
> $CURRYHOME/bin/kics2-frontend --extended -i $CURRYHOME/lib --typed-flat Rev
2. Generate `Prelude.icy` (the ICurry representation of the Prelude,
which also generates the ICurry type dependency file `Prelude.ictdeps`):
> icurry f2i -I $CURRYHOME/lib/.curry $CURRYHOME/lib/.curry/Prelude.tfcy $CURRYHOME/lib/.curry/Prelude.icy
Note that this takes a *lot of time* if `icurry` has been generated
with PAKCS instead of KiCS2!
3. Now one can compile modules using the prelude in the same way.
For instance, to compile module `Rev` in this directory:
> icurry f2i -I .curry -I $CURRYHOME/lib/.curry .curry/Rev.tfcy .curry/Rev.icy
With the additional option `-p`, the a pretty-printed version of the
generated ICurry program is shown on the terminal:
> icurry f2i -p -I .curry -I $CURRYHOME/lib/.curry .curry/Rev.tfcy .curry/Rev.icy
4. If desired, generate the Extended ICurry representation of `Rev`
(in `.curry/Rev.eicy`):
> icurry i2e -I .curry -I $CURRYHOME/lib/.curry .curry/Rev.icy .curry/Rev.eicy
or similarly for the prelude:
> icurry i2e -I $CURRYHOME/lib/.curry $CURRYHOME/lib/.curry/Prelude.icy $CURRYHOME/lib/.curry/Prelude.eicy
----------------------------------------------------------------------------
-- Concatenating two lists:
-- (predefined as `++' in the standard prelude)
append :: [a] -> [a] -> [a]
append [] x = x
append (x:xs) ys = x : append xs ys
-- Reverse the order of elements in a list:
rev :: [a] -> [a]
rev [] = []
rev (x:xs) = append (rev xs) [x]
goal1 :: [Int]
goal1 = append [1,2] [3,4]
goal2 :: [Int]
goal2 = rev [1,2,3,4]
-- end of program
-- Various simple functions for testing.
id x = x
not False = True
not True = False
notBool = not aBool
fnot = not x where x free
-- the classical example from the KiCS2 paper:
xor False y = y
xor True y = not y
xorSelf x = xor x x
aBool = False
aBool = True
xorSelfBool = xorSelf aBool
-- infinite list of 1,2,1,2,...
oneTwo :: [Int]
oneTwo = let x = 1 : y
y = 2 : x
in x
head (x:_) = x
headOneTwo = head oneTwo
-- computing permutations
ndinsert x xs = x:xs
ndinsert x (y:ys) = y : ndinsert x ys
perm [] = []
perm (x:xs) = ndinsert x (perm xs)
perm123 :: [Int]
perm123 = normalForm (perm [1,2,3])
-- Testing deep patterns
zip :: [a] -> [b] -> [(a,b)]
zip [] _ = []
zip (_:_) [] = []
zip (x:xs) (y:ys) = (x,y) : zip xs ys
ones :: [Int]
ones = let xs = 1 : xs in xs
zipTest :: [(Int,Int)]
zipTest = normalForm (zip [1,2,3] ones)
{
"name": "icurry",
"version": "2.0.0",
"author": "Marc Andre Wittorf <stu114055@mail.uni-kiel.de>",
"maintainer": "Michael Hanus <mh@informatik.uni-kiel.de>",
"synopsis": "Generic intermediate format for compiling Curry to imperative languages",
"version": "3.0.0",
"author": "Michael Hanus <mh@informatik.uni-kiel.de>",
"synopsis": "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"
"base" : ">= 1.0.0, < 2.0.0",
"currypath" : ">= 0.0.1",
"flatcurry" : ">= 2.0.0, < 3.0.0",
"graphviz" : ">= 2.0.0, < 3.0.0"
},
"compilerCompatibility": {
"kics2": ">= 2.0.0, < 3.0.0",
"pakcs": ">= 2.0.0, < 3.0.0"
"pakcs": ">= 2.0.0, < 3.0.0",
"kics2": ">= 2.0.0, < 3.0.0"
},
"exportedModules": [ "ICurry.Types", "ICurry.Files", "ICurry.Pretty",
"ICurry.C2I", "ICurry.Build",
"ICurry.Extended.Types", "ICurry.Extended.Files",
"ICurry.Extended.I2E", "ICurry.Extended.Goodies",
"ICurry.Extended.Build" ],
"configModule": "Main.ConfigPackage",
"executable":{
"name": "icurry"
"ICurry.Compiler", "ICurry.Interpreter" ],
"executable": {
"name" : "icurry",
"main" : "ICurry.Main"
},
"testsuite": {
"src-dir": "examples",
"modules": [ "InterpreterTests" ]
},
"documentation": {
"src-dir": "docs",
......@@ -35,4 +34,5 @@
"git": "https://git.ps.informatik.uni-kiel.de/curry-packages/icurry.git",
"tag": "$version"
}
}
------------------------------------------------------------------------------
--- This module contains an implementation of case completion, i.e.,
--- all case expressions occurring in a FlatCurry program are completed
--- (with calls to `Prelude.failed`) and ordered according to the
--- constructor ordering of the corresponding data definitions.
---
--- @author Michael Hanus
--- @version January 2020
------------------------------------------------------------------------------
module FlatCurry.CaseCompletion where
import List
import FlatCurry.Files
import FlatCurry.Types
------------------------------------------------------------------------------
--- Type to represent algebraic data declarations
--- (pair of type name and list of constructor names and arities).
type DataDecl = (QName, [(QName,Int)])
--- Options for case completion.
data CaseOptions = CaseOptions
{ dataDecls :: [DataDecl]
}
typeOfConstructor :: CaseOptions -> QName -> DataDecl
typeOfConstructor opts qn =
let ctypes = filter (\ (_,cs) -> qn `elem` map fst cs) (dataDecls opts)
in if null ctypes
then error $ "Type of constructor '" ++ snd qn ++ "' not found!"
else head ctypes
------------------------------------------------------------------------------
--- Complete all nested cases in a FlatCurry program.
completeProg :: CaseOptions -> Prog -> Prog
completeProg opts (Prog mn imps types funs ops) =
Prog mn imps types (map (completeFun opts) funs) ops
--- Complete all nested cases in a FlatCurry function.
completeFun :: CaseOptions -> FuncDecl -> FuncDecl
completeFun opts (Func fn ar vis texp rule) =
Func fn ar vis texp (completeRule opts rule)
completeRule :: CaseOptions -> Rule -> Rule
completeRule _ (External n) = External n
completeRule opts (Rule args rhs) =
Rule args (completeExp opts rhs)
completeExp :: CaseOptions -> Expr -> Expr
completeExp _ (Var v) = Var v
completeExp _ (Lit l) = Lit l
completeExp opts (Comb ct qn es) =
Comb ct qn (map (completeExp opts) es)
completeExp opts (Let bs e) =
Let (zip (map fst bs) (map (completeExp opts . snd) bs)) (completeExp opts e)
completeExp opts (Free vs e) = Free vs (completeExp opts e)
completeExp opts (Or e1 e2) =
Or (completeExp opts e1) (completeExp opts e2)
completeExp opts (Typed e te) = Typed (completeExp opts e) te
completeExp opts (Case ct e brs) = case brs of
[] -> Case ct ce []
Branch (LPattern _) _ : _ -> Case ct ce cbrs
Branch (Pattern cn _) _ : _ ->
let consdecls = snd (typeOfConstructor opts cn)
sbrs = map (\c -> maybe (failedBranch c) id
(find (isBranchForCons c) cbrs))
consdecls
in Case ct ce sbrs
where
isBranchForCons c (Branch pat _) = case pat of Pattern pn _ -> fst c == pn
_ -> False
ce = completeExp opts e
cbrs = map (\ (Branch pat be) -> Branch pat (completeExp opts be)) brs
failedBranch (c,ar) = Branch (Pattern c [101 .. 100+ar])
(Comb FuncCall ("Prelude","failed") [])
------------------------------------------------------------------------------
--- Get all constructors occurring in case expressions in a FlatCurry program.
allConsProg :: Prog -> [QName]
allConsProg (Prog _ _ _ funs _) = unionMap allConsFun funs
--- AllCons all nested cases in a FlatCurry function.
allConsFun :: FuncDecl -> [QName]
allConsFun (Func _ _ _ _ (External _)) = []
allConsFun (Func _ _ _ _ (Rule _ exp)) = allConsExp exp
allConsExp :: Expr -> [QName]
allConsExp (Var _) = []
allConsExp (Lit _) = []
allConsExp (Comb _ _ es) = unionMap allConsExp es
allConsExp (Case _ e brs) =
union (allConsExp e) (unionMap allConsBranch brs)
where
allConsBranch (Branch (LPattern _) be) = allConsExp be
allConsBranch (Branch (Pattern qn _) be) = union [qn] (allConsExp be)
allConsExp (Let bs e) =
union (allConsExp e) (unionMap (allConsExp . snd) bs)
allConsExp (Free _ e) = allConsExp e
allConsExp (Or e1 e2) = union (allConsExp e1) (allConsExp e2)
allConsExp (Typed e _) = allConsExp e
unionMap :: Eq b => (a -> [b]) -> [a] -> [b]
unionMap f = foldr union [] . map f
------------------------------------------------------------------------------
--- Get all data types (pairs of type name and list of constructor names
--- and arities) in a given FlatCurry program.
dataDeclsOf :: Prog -> [DataDecl]
dataDeclsOf (Prog _ _ tdecls _ _) = concatMap dataDeclsOfTypeDecl tdecls
where
dataDeclsOfTypeDecl (TypeSyn _ _ _ _) = []
dataDeclsOfTypeDecl (Type tn _ _ cdecl) =
[(tn, map (\ (Cons cn ar _ _) -> (cn,ar)) cdecl)]
------------------------------------------------------------------------------
------------------------------------------------------------------------------
--- This module contains an implementation of a case lifter, i.e.,
--- an operation which lifts all nested cases in a FlatCurry program
--- into new operations.
---
--- NOTE: the new operations contain nonsense types, i.e., this transformation
--- should only be used when the actual function types are irrelevant!
---
--- @author Michael Hanus
--- @version January 2020
------------------------------------------------------------------------------
module FlatCurry.CaseLifting where
import List ( maximum, union )
import FlatCurry.Goodies ( allVars )
import FlatCurry.Types
------------------------------------------------------------------------------
--- Options for case lifting.
data LiftOptions = LiftOptions
{ caseFun :: QName -- name of possibly case function
}
defaultOpts :: LiftOptions
defaultOpts = LiftOptions ("","")
-- Add suffix to case function
add2caseFun :: LiftOptions -> String -> LiftOptions
add2caseFun opts suff =
let (mn,fn) = caseFun opts
in opts { caseFun = (mn, fn ++ suff) }
------------------------------------------------------------------------------
--- Lift all nested cases in a FlatCurry program.
liftProg :: LiftOptions -> Prog -> Prog
liftProg opts (Prog mn imps types funs ops) =
Prog mn imps types (concatMap (liftFun opts) funs) ops
--- Lift all nested cases in a FlatCurry function.
liftFun :: LiftOptions -> FuncDecl -> [FuncDecl]
liftFun opts (Func fn ar vis texp rule) =
let (nrule, nfs) = liftRule opts { caseFun = fn } rule
in Func fn ar vis texp nrule : nfs
liftRule :: LiftOptions -> Rule -> (Rule, [FuncDecl])
liftRule _ (External n) = (External n, [])
liftRule opts (Rule args rhs) =
let (nrhs, nfs) = liftExp opts False rhs
in (Rule args nrhs, nfs)
liftExp :: LiftOptions -> Bool -> Expr -> (Expr, [FuncDecl])
liftExp _ _ (Var v) = (Var v, [])
liftExp _ _ (Lit l) = (Lit l, [])
liftExp opts _ (Comb ct qn es) =
let (nes,nfs) = unzip (map (\ (n,e) -> liftExpArg opts n e) (zip [1..] es))
in (Comb ct qn nes, concat nfs)
liftExp opts lft exp@(Case ct e brs) = case e of
Var _ -> liftCaseExp lft
_ -> liftCaseArg
where
liftCaseExp False =
let (ne, nefs) = liftExpArg opts 0 e
(nbrs, nfs) = unzip (map (liftBranch opts) (zip [1..] brs))
in (Case ct ne nbrs, nefs ++ concat nfs)
-- lift case expression by creating new function call:
liftCaseExp True =
let vs = unboundVars exp
cfn = caseFun (add2caseFun opts "$CASE")
noneType = TCons ("Prelude","None") []
caseFunc = Func cfn (length vs) Private noneType (Rule vs exp)
in (Comb FuncCall cfn (map Var vs), liftFun opts caseFunc)
-- lift case with complex (non-variable) case argument:
liftCaseArg =
let (ne, nefs) = liftExpArg opts 0 e
casevar = maximum (0 : allVars exp) + 1
vs = unionMap unboundVarsInBranch brs
cfn = caseFun (add2caseFun opts "$COMPLEXCASE")
noneType = TCons ("Prelude","None") []
caseFunc = Func cfn (length vs + 1) Private noneType
(Rule (vs ++ [casevar]) (Case ct (Var casevar) brs))
in (Comb FuncCall cfn (map Var vs ++ [ne]), nefs ++ liftFun opts caseFunc)
liftExp opts False (Let bs e) =
let (nes,nfs1) = unzip (map (\ (n,be) -> liftExpArg opts n be)
(zip [1..] (map snd bs)))
(ne,nfs2) = liftExpArg opts 0 e
in (Let (zip (map fst bs) nes) ne, concat nfs1 ++ nfs2)
-- lift let expression by creating new function call:
liftExp opts True exp@(Let _ _) =
let vs = unboundVars exp
cfn = caseFun (add2caseFun opts "$LET")
noneType = TCons ("Prelude","None") []
letFunc = Func cfn (length vs) Private noneType (Rule vs exp)
in (Comb FuncCall cfn (map Var vs), liftFun opts letFunc)
liftExp opts _ (Free vs e) =
let (ne, nfs) = liftExp opts True e
in (Free vs ne, nfs)
liftExp opts _ (Or e1 e2) =
let (ne1, nfs1) = liftExpArg opts 1 e1
(ne2, nfs2) = liftExpArg opts 2 e2
in (Or ne1 ne2, nfs1 ++ nfs2)
liftExp opts lft (Typed e te) =
let (ne, nfs) = liftExp opts lft e
in (Typed ne te, nfs)
-- Lift an argument of an expression so that the argument number
-- is added to the case function in order to obtain unique names.
liftExpArg :: LiftOptions -> Int -> Expr -> (Expr, [FuncDecl])
liftExpArg opts argnum = liftExp (add2caseFun opts ('_' : show argnum)) True
liftBranch :: LiftOptions -> (Int,BranchExpr) -> (BranchExpr, [FuncDecl])
liftBranch opts (bnum, Branch pat e) =
let (ne,nfs) = liftExpArg opts bnum e
in (Branch pat ne, nfs)
--- Find all variables which are not bound in an expression.
unboundVars :: Expr -> [VarIndex]
unboundVars (Var idx) = [idx]
unboundVars (Lit _) = []
unboundVars (Comb _ _ es) = unionMap unboundVars es
unboundVars (Or e1 e2) = union (unboundVars e1) (unboundVars e2)
unboundVars (Typed e _) = unboundVars e
unboundVars (Free vs e) = filter (not . flip elem vs) (unboundVars e)
unboundVars (Let bs e) =
let unbounds = unionMap unboundVars $ e : map snd bs
bounds = map fst bs
in filter (not . flip elem bounds) unbounds
unboundVars (Case _ e bs) =
union (unboundVars e) (unionMap unboundVarsInBranch bs)
unboundVarsInBranch :: BranchExpr -> [VarIndex]
unboundVarsInBranch (Branch (Pattern _ vs) be) =
filter (not . flip elem vs) (unboundVars be)
unboundVarsInBranch (Branch (LPattern _) be) = unboundVars be
unionMap :: Eq b => (a -> [b]) -> [a] -> [b]
unionMap f = foldr union [] . map f
--- 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