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
------------------------------------------------------------------------------
--- Some tests for the ICurry interpreter.
------------------------------------------------------------------------------
module InterpreterTests
where
import Test.Prop
import ICurry.Types
import ICurry.Interpreter
------------------------------------------------------------------------------
-- Goodies to support easier construction of ICurry function declarations.
-- A public ICurry function.
iFunction :: String -> IArity -> [Int] -> IFuncBody -> IFunction
iFunction f ar dmd body = IFunction (siq f) ar Public dmd body
-- An ICurry block without free variable declarations so that
-- only the indices of the local variables are provided.
iBlock :: [IVarIndex] -> [IAssign] -> IStatement -> IBlock
iBlock ivs = IBlock (map IVarDecl ivs)
iFCall :: String -> [IExpr] -> IExpr
iFCall n = IFCall (siq n)
iCCall :: String -> [IExpr] -> IExpr
iCCall n = ICCall (siq n)
iFPCall :: String -> Int -> [IExpr] -> IExpr
iFPCall n = IFPCall (siq n)
iCPCall :: String -> Int -> [IExpr] -> IExpr
iCPCall n = ICPCall (siq n)
iConsBranch :: String -> IBlock -> IConsBranch
iConsBranch n = IConsBranch (siq n)
icurryList :: [IExpr] -> IExpr
icurryList [] = ICCall (siq "[]") []
icurryList (x:xs) = ICCall (siq ":") [x, icurryList xs]
-- A simple right-hand side just returning an expression.
simpleRHS :: IExpr -> IBlock
simpleRHS e = IBlock [] [] (IReturn e)
-- Translate a string into a simple ICurry name without qualified module
-- and index.
siq :: String -> IQName
siq s = ("",s,0)
------------------------------------------------------------------------------
-- Example ICurry programs:
-- coin = 1 ? 2
funCoin :: IFunction
funCoin = iFunction "coin" 0 [] $ IFuncBody $
simpleRHS (IOr (ILit (IInt 1)) (ILit (IInt 2)))
-- id x = x
funId :: IFunction
funId = iFunction "id" 1 [] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [0])] (IReturn (IVar 1))
-- idTrue = id True
funIdTrue :: IFunction
funIdTrue = iFunction "idTrue" 1 [] $ IFuncBody $
simpleRHS (iFCall "id" [iCCall "True" []])
funHead :: IFunction
funHead = iFunction "head" 1 [0] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [0])]
(ICaseCons 1
[iConsBranch "[]" (IBlock [] [] IExempt),
iConsBranch ":" (simpleRHS (IVarAccess 1 [0]))])
funHeadEmpty :: IFunction
funHeadEmpty = iFunction "headempty" 0 [] $ IFuncBody $
simpleRHS (iFCall "head" [iCCall "[]" []])
funHead1 :: IFunction
funHead1 = iFunction "head1" 0 [] $ IFuncBody $
IBlock [] []
(IReturn (iFCall "head" [iCCall ":" [ILit (IInt 1), iCCall "[]" []]]))
funHead12 :: IFunction
funHead12 = iFunction "head12" 0 [] $ IFuncBody $
IBlock [] []
(IReturn (iFCall "head" [iCCall ":" [iFCall "coin" [],
iCCall "[]" []]]))
funNot :: IFunction
funNot = iFunction "not" 1 [0] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [0])]
(ICaseCons 1
[iConsBranch "False" (simpleRHS (iCCall "True" [])),
iConsBranch "True" (simpleRHS (iCCall "False" []))])
-- (&&) x y = case { False -> False ; True -> y }
funAnd :: IFunction
funAnd = iFunction "&&" 2 [0] $ IFuncBody $
iBlock [1,2] [IVarAssign 1 (IVarAccess 0 [0]),
IVarAssign 2 (IVarAccess 0 [1])]
(ICaseCons 1
[iConsBranch "False" (simpleRHS (iCCall "False" [])),
iConsBranch "True" (simpleRHS (IVar 2))])
-- xor x y = case { False -> y ; True -> not y }
funXor :: IFunction
funXor = iFunction "xor" 2 [0] $ IFuncBody $
iBlock [1,2]
[IVarAssign 1 (IVarAccess 0 [0]),
IVarAssign 2 (IVarAccess 0 [1])]
(ICaseCons 1
[iConsBranch "False" (simpleRHS (IVar 2)),
iConsBranch "True" (simpleRHS (iFCall "not" [IVar 2]))])
-- xorSelf x = xor x x
funXorSelf :: IFunction
funXorSelf = iFunction "xorSelf" 1 [] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [0])]
(IReturn (iFCall "xor" [IVar 1, IVar 1]))
-- aBool = False ? True
funABool :: IFunction
funABool = iFunction "aBool" 0 [] $ IFuncBody $
simpleRHS (IOr (iCCall "False" []) (iCCall "True" []))
-- notBool = not aBool
funNotBool :: IFunction
funNotBool = iFunction "notBool" 0 [] $ IFuncBody $
simpleRHS (iFCall "not" [iFCall "aBool" []])
-- xorSelfBool = xorSelf aBool
funXorSelfBool :: IFunction
funXorSelfBool = iFunction "xorSelfBool" 0 [] $ IFuncBody $
simpleRHS (iFCall "xorSelf" [iFCall "aBool" []])
-- oneTwo = let { x = 1:y ; y = 2:x } in x
funOneTwo :: IFunction
funOneTwo = iFunction "oneTwo" 0 [] $ IFuncBody $
iBlock [1,2]
[IVarAssign 1 (iCCall ":" [iCCall "1" [], IVar 2]),
IVarAssign 2 (iCCall ":" [iCCall "2" [], IVar 1])
,INodeAssign 1 [1] (IVar 2)]
(IReturn (IVar 1))
-- headOneTwo = head (let { x = 1:y ; y = 2:x } in x)
funHeadOneTwo :: IFunction
funHeadOneTwo = iFunction "headOneTwo" 0 [] $ IFuncBody $
simpleRHS (iFCall "head" [iFCall "oneTwo" []])
-- apply f x: demands f and returns (f x)
funApply :: IFunction
funApply = iFunction "apply" 2 [0] (IExternal "apply")
-- seq x y: demands x and returns y
funSeq :: IFunction
funSeq = iFunction "seq" 2 [0] $ IFuncBody $
simpleRHS (IVarAccess 0 [1])
-- f $! x: demands x and returns (f x)
funDollarBang :: IFunction
funDollarBang = iFunction "$!" 2 [1] (IExternal "$!")
-- normalForm x: demands x and returns the normal form of x
funNormalForm :: IFunction
funNormalForm =
iFunction "normalForm" 1 [0] (IExternal "normalForm")
-- xorSelfSeqBool = let x = aBool in seq x (xorSelf x)
funXorSelfSeqBool :: IFunction
funXorSelfSeqBool = iFunction "xorSelfSeqBool" 0 [] $ IFuncBody $
iBlock [1] [IVarAssign 1 (iFCall "aBool" [])]
(IReturn (iFCall "seq" [IVar 1, iFCall "xorSelf" [IVar 1]]))
-- xorSelfDollarBangBool = xorSelf $! aBool
funXorSelfDollarBangBool :: IFunction
funXorSelfDollarBangBool = iFunction "xorSelfDollarBangBool" 0 [] $
IFuncBody $
IBlock [] []
(IReturn (iFCall "$!" [iFPCall "xorSelf" 1 [], iFCall "aBool" []]))
-- andNotFalse = let x = not False in x && x
funAndNotFalse :: IFunction
funAndNotFalse = iFunction "andNotFalse" 0 [] $ IFuncBody $
iBlock [1] [IVarAssign 1 (iFCall "not" [iCCall "False" []])]
(IReturn (iFCall "&&" [IVar 1, IVar 1]))
-- coinList = [coin]
funCoinList :: IFunction
funCoinList = iFunction "coinList" 0 [] $ IFuncBody $
IBlock [] []
(IReturn (iFCall "normalForm"
[iCCall ":" [iFCall "coin" [], iCCall "[]" []]]))
-- coinCoinList = [coin,coin]
funCoinCoinList :: IFunction
funCoinCoinList = iFunction "coinCoinList" 0 [] $ IFuncBody $
IBlock [] []
(IReturn (iFCall "normalForm"
[iCCall ":" [iFCall "coin" [],
iCCall ":" [iFCall "coin" [], iCCall "[]" []]]]))
------------------------------------------------------------------------------
-- Non-deterministic list insertion:
-- ndinsert x xs = x:xs ? ndinsert1 x xs
-- ndinsert1 x xs = case xs of { [] -> fail ; y:ys -> y : ndinsert x ys
funNDInsert :: IFunction
funNDInsert = iFunction "ndinsert" 0 [] $ IFuncBody $
IBlock [] []
(IReturn
(IOr (iCCall ":" [IVarAccess 0 [0],
IVarAccess 0 [1]])
(iFCall "ndinsert1" [IVarAccess 0 [0],
IVarAccess 0 [1]])))
funNDInsert1 :: IFunction
funNDInsert1 = iFunction "ndinsert1" 1 [1] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [1])]
(ICaseCons 1
[iConsBranch "[]" (IBlock [] [] IExempt),
iConsBranch ":"
(IBlock [] []
(IReturn
(iCCall ":" [IVarAccess 1 [0],
iFCall "ndinsert" [IVarAccess 0 [0],
IVarAccess 1 [1]]])))])
-- insert123 = normalForm (ndinsert 1 [2,3])
funInsert123 :: IFunction
funInsert123 = iFunction "insert123" 0 [] $ IFuncBody $
IBlock [] []
(IReturn
(iFCall "normalForm"
[iFCall "ndinsert"
[ILit (IInt 1),
icurryList (map (ILit . IInt) [2,3])]]))
-- perm xs = case xs of { [] -> [] ; y:ys -> ndinsert y (perm ys)
funPerm :: IFunction
funPerm = iFunction "perm" 1 [0] $ IFuncBody $
iBlock [1] [IVarAssign 1 (IVarAccess 0 [0])]
(ICaseCons 1
[iConsBranch "[]" (simpleRHS (iCCall "[]" [])),
iConsBranch ":"
(IBlock [] []
(IReturn
(iFCall "ndinsert" [IVarAccess 1 [0],
iFCall "perm" [IVarAccess 1 [1]]])))])
-- perm123 = normalForm (perm [1,2,3])
funPerm123 :: IFunction
funPerm123 = iFunction "perm123" 0 [] $ IFuncBody $
IBlock [] []
(IReturn (iFCall "normalForm"
[iFCall "perm" [icurryList (map (ILit . IInt) [1,2,3])]]))
------------------------------------------------------------------------------
-- List of all ICurry function declarations.
allFuns :: [IFunction]
allFuns =
[ funCoin, funHead, funHeadEmpty, funHead1, funHead12
, funNot,funAnd, funNotBool, funXor,funXorSelf,funABool,funXorSelfBool
, funOneTwo, funHeadOneTwo
, funApply, funDollarBang, funSeq, funNormalForm
, funXorSelfSeqBool, funXorSelfDollarBangBool
, funAndNotFalse, funCoinList, funCoinCoinList
, funNDInsert, funNDInsert1, funPerm, funInsert123, funPerm123
, funId, funIdTrue
]
exampleProg :: IProg
exampleProg = IProg "Example" [] [] allFuns
------------------------------------------------------------------------------
-- Some tests:
quiet :: IOptions
quiet = defOpts
std :: IOptions
std = defOpts { verbosity = 2, withGraph = True, waitTime = 1 }
stdint :: IOptions
stdint = std { interactive = True }
details :: IOptions
details = stdint { verbosity = 4 }
m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14 :: IOptions -> IO ()
m1 o = execIProg o exampleProg "coin"
m2 o = execIProg o exampleProg "headempty"
m3 o = execIProg o exampleProg "head1"
m4 o = execIProg o exampleProg "head12"
m5 o = execIProg o exampleProg "xorSelfBool"
m6 o = execIProg o exampleProg "xorSelfDollarBangBool"
m7 o = execIProg o exampleProg "xorSelfSeqBool"
m8 o = execIProg o exampleProg "andNotFalse"
m9 o = execIProg o exampleProg "headOneTwo"
m10 o = execIProg o exampleProg "coinList"
m11 o = execIProg o exampleProg "coinCoinList"
m12 o = execIProg o exampleProg "perm123"
m13 o = execIProg o exampleProg "idTrue"
m14 o = execIProg o exampleProg "notBool"
------------------------------------------------------------------------------
-- Testing with CurryCheck.
testCoin :: Prop
testCoin = evalFun exampleProg "coin" <~> ("1" ? "2")
testHeadEmpty :: Prop
testHeadEmpty = failing (evalFun exampleProg "headempty")
testHead1 :: Prop
testHead1 = evalFun exampleProg "head1" <~> "1"
testHead12 :: Prop
testHead12 = evalFun exampleProg "head12" <~> ("1" ? "2")
testHeadOneTwo :: Prop
testHeadOneTwo = evalFun exampleProg "headOneTwo" <~> "1"
testXorSelfBool :: Prop
testXorSelfBool = evalFun exampleProg "xorSelfBool" <~> "False"
testXorSelfDollarBangBool :: Prop
testXorSelfDollarBangBool =
evalFun exampleProg "xorSelfDollarBangBool" <~> "False"
testXorSelfSeqBool :: Prop
testXorSelfSeqBool = evalFun exampleProg "xorSelfSeqBool" <~> "False"
testAndNotFalse :: Prop
testAndNotFalse = evalFun exampleProg "andNotFalse" <~> "True"
testNotBool :: Prop
testNotBool = evalFun exampleProg "notBool" <~> ("True" ? "False")
testIdTrue :: Prop
testIdTrue = evalFun exampleProg "idTrue" <~> "True"
testCoinList :: Prop
testCoinList = evalFun exampleProg "coinList" <~> ("(: 1 [])" ? "(: 2 [])")
testCoinCoinList :: Prop
testCoinCoinList =
evalFun exampleProg "coinCoinList" <~>
("(: 1 (: 1 []))" ? "(: 2 (: 1 []))" ? "(: 1 (: 2 []))" ? "(: 2 (: 2 []))")
testPerm123 :: Prop
testPerm123 =
evalFun exampleProg "perm123" <~>
("(: 1 (: 2 (: 3 [])))" ? "(: 1 (: 3 (: 2 [])))" ? "(: 2 (: 1 (: 3 [])))" ?
"(: 2 (: 3 (: 1 [])))" ? "(: 3 (: 1 (: 2 [])))" ? "(: 3 (: 2 (: 1 [])))")
------------------------------------------------------------------------------
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",