This cheatsheet is to get you started with the most common constructs in Ivory. Many constructs are not covered here; see the Ivory Haddock documentation (as well as the Ivory standard library) for the full language definition.
This cheatsheet is incomplete!
IBool
IChar
SX
, where X
is int8
, int16
, int32
, int64
Sint32
UX
, where X
is int8
, int16
, int32
, int64
Uint8
IFloat
IDouble
()
. In practice, void is only used as a return type for functions.Note: there are no machine-dependent types in Ivory, like int
, short int
, long int
.
A memory area is allocated that has a type.
Stored X
, where X
is a value type.
Stored Uint32
.Array n X
, where n
is a number and X
is a memory area type
Array 10 (Stored Sint32)
: array of 10 signed 32-bit integers.Struct "name"
, where name
is the string associated with the defined struct.
Struct "foo"
Scope type is the scope of a pointer. A scope is either Local
if the memory area is allocated on the stack, Global
if its allocated in global memory, or a type variable if you don’t care.
References are non-null pointers. You almost exclusively use references in ivory.
Examples:
Ref Local (Stored Sint32)
: reference to a locally-allocated signed 32-bit int.Ref Global (Struct "foo")
: reference to a globally-allocated struct named "foo"
.
Ref s (Array 5 (Stored IBool)
: indeterminate allocation scope of an array of 5 Booleans.
Indexes are used to index into an array and for loop counters. They have a special type, Ix n
, where n
is a natural number. An index type of Ix n
supports the values 0
through n-1
.
Ix 3
.Types of initializers for memory areas.
Init X
, where X
is a memory area type.
Init (Array 3 (Stored IBool))
: initializer type an array of three Booleans.Used to initialize memory areas.
ival
.
ival 3
izero
.iarray [i0, i1, ..., in]
for an array of length n. Each element must be an initializer corresponding to the type of the array, and the length of the list be correspond to the length of the array.
Example: iarray [ival 3, ival 4]
is an initializer with type (or whatever integer type we wish to interpret 3
and 4
.
Init (Array 2 (Stored Uint32))
Arrays can be initialized to a default value using iarray []
istruct [fname0 .= exp0, fname1 .= exp1, ...]
is an initializer with type Init (Struct "Foo")
for struct "Foo"
.
istruct [field0 .= 3; field1 .= 4]
which initializes the fields to 3
and 4
, respectively. Fields can be partially initialized.istruct []
.Structs are defined at the top level as follows. Foo
is a fresh name for the struct, and field0
, field1
, … are fresh names for fields. The types of fields must be memory area types.
[ivory|
struct Foo
{ field0 :: Stored Uint32
; field1 :: Array 10 (Stored IBool)
}
|]
For the definition above, the following is generated automatically:
Struct "Foo"
field0
and field1
Note: there are no union types in Ivory.
+
, -
, *
/
: only defined for IFloat
and IDouble
==?
, /=?
(not equals)>?
, >=?
, <?
, <=?
iDiv
: like C’s /
(truncation towards 0).
iDiv (-3) 2 .== (-1)
.%
: like C’s %
.
(-3) .% (-2) .== -1
true
, false
, .&&
, .||
, iNot
.&
, .|
, .^
, iComplement
, iShiftL
, iShiftR
10 iShiftR 1 .== 5
arr ! exp
, where arr
is a reference to an array and exp
is an Ivory expression. Returns a reference to an element of an array.
Example:
x0 <- local (iarray [ival 1, ival 2])
x <- deref (x0 ! 1)
ref ~> fieldName
where ref
is a reference to a struct and fieldName
is a field of the struct (see struct definitions).
Example:
ref <- local (istruct [field0 .= 3; field1 .= 4])
f0 <- deref (ref ~> field0)
C:
struct foo = {field0 = 3; field1 = 4};
struct foo *ref = &foo;
uint32_t f0 = foo->field0;
exp0 ? (exp1, exp2)
, where exp_i
is an Ivory expression.
(a > b) ? (a, b)
(a > b) ? a : b
Statements appear in an Ivory do
block.
There are two kinds of statements:
Binding statements: statements that bind a value to a fresh variable on the left-hand side of a <-
operator. Assume that each left-hand variable shown is fresh. Example: x <- assign (3 - 4)
(assign x
the value 3-4
).
Non-binding statements: statements that do not bind. Example: ret 4
(return 4).
In the following
exp
is an Ivory expression.ref
is a reference.init
is an Ivory initializer.For each statement, we give an example and an equivalent in C.
x <- assign exp
x <- assign (a + 3)
uint32_t x = a + b;
x <- local init
x <- local (ival 3)
C:
uint32_t x0 = 3;
uint32_t *x = &x0;
Note: sometimes, Ivory can’t figure out the type of an initializer, so you have to give it explicitly. Example: x <- local (iarray [ival 3, ival 4] :: Init (Array 2 (Stored Uint32)))
x <- deref ref
.
Example:
x0 <- local (init (ival 3))
x <- deref x0
C:
uint32_t x0 = 3;
uint32_t *x1 = &x0;
uint32_t x = *x1;
store ref exp
.
Example:
ref <- local (ival 3)
store ref 2
C:
uint32_t x = 3;
uint32_t *ref = &x;
*ref = 2;
ret exp
ret (a >=? 1)
return (a >= 1);
retVoid
retVoid
ret void;
ifte_ exp stmts0 stmts1
, where stmts0
, stmts1
are blocks of Ivory statements
Example:
ifte_ (a > 2)
(do x <- assign (a + 4)
ret x)
(ret (a + 3))
C:
if (a > 2) {
x = a + 4;
return x;
} else { return (a + 3); }
(ix :: Ix 4) `for` \currIx -> stmts
. Counts from 0
to n-1
for Ix n
.
Example:
(ix :: Ix 4) `for` \currIx -> $ do
x <- deref ref
store ref (x+1)
C:
for (int currIx = 0; currIx <= 3; currIx++) {
*ref = *ref + 1;
}
(ix :: Ix 5) `times` \currIx -> stmts
. Counts from n-1
to 0
for Ix n
.
Example:
(ix :: Ix 4) `times` \currIx -> $ do
x <- deref ref
store ref (x+1)
C:
for (int currIx = 3; currIx >= 0; currIx--) {
*ref = *ref + 1;
}
arrayMap $ \currIx -> stmts
Example:
arrayMap $ \ix -> do
x <- deref (arr ! ix)
store (arr ! ix) (x+1)
C: assuming the number of elements in the array is given by len
,
for (int ix = 0; ix < len; ix++) {
arr[ix] = arr[ix] + 1
}
assert exp
assert (a > b)
assert(a > b);
x <- call func exp0 exp1 ... expn
. See function definitions below for how to define a function.
int32_t x = foo(3, a+2);
call_
.Ivory programs are encapsulated in functions.
A function type has the form Def ('[ArgsTypes] :-> RetType)
where ArgTypes
are the types of the arguments and RetType
is the return type.
Examples:
fun0 :: Def ('[] :-> ())
fun1 :: Def ('[Uint32] :-> IBool)
fun2 :: Def ('[Ref s (Stored Uint32), IBool] :-> IBool)
Function definitions take a string that will become the name of the function in the compiled C (it is good practice to make this string the same as the Haskell name). Then
func0 :: Def ('[Uint32] :-> Uint32)
func0 = proc "func0" $ \arg0 -> body $ do
ret (arg0 + 2)
compiles to
uint32_t func0(uint32_t arg0) {
return (arg0 + 2);
}
func1 :: Def ('[] :-> Uint32)
func1 = proc "func1" $ body $ do
ret 2
compiles to
uint32_t func1(void) {
return 2;
}
func2 :: Def ('[Ref s (Stored Uint32), Uint32] :-> ())
func2 = proc "func2" $ \ref val -> body $ do
store ref val
retVoid
compiles to
uint32_t func2(uint32_t *ref, uint32_t val) {
*ref = val;
return;
}
Functions and structures must be placed in a module to be compiled. The following module contains a function called foo
and a structure with type Struct "Bar"
:
aModule :: Module
aModule = package "MyPackage" $ do
incl foo
defStruct (Proxy :: Proxy "Bar")
The Ivory compiler is called by calling compile [m0, m1, ... mn]
, where m_i
are modules.
Assuming you are using GHCi (GHC interpreter), you can set arguments to the compiler by running
> :set args ... args ...
To get the list of arguments accepted, run
> :set args --help
> compile []
To run the compiler on module myModule
with division-by-zero and integer-overflow checking, run
> :set args --div-zero --overflow
> compile [myModule]
At the top of your Haskell file in which you are writing Ivory, place the following:
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}