You'll need Idris to run most of this.
If z3 isn't installed in a location like /usr
or /usr/local
, then stack
/cabal
will have trouble finding it.
To fix, assuming it's in /Users/foo/local
:
- Add these into your
~/.stack/config.yaml
:
extra-include-dirs:
- /Users/foo/local/include
extra-lib-dirs:
- /Users/foo/local/lib
- Symlink the library into a standard path:
sudo ln -s ~/local/lib/libz3.dylib /usr/local/lib
The toy language is largely a tiny subset of Idris with a bit of extra syntax for the refinements.
Refined types are written in curly braces: { v : Int | v >= 0 & v < x }
as an example.
Path sensitivity and dependent refinements:
max : (x : Int) -> (y : Int) -> { v : Int | v >= x & v >= y }
max x y = if x > y then y else x
Subtyping and co/contravariance for function arguments:
f : (x : { v : Int | v >= 0 })
-> (y : { v : Int | v >= 0 })
-> { v : Int | v > 0 }
g : ((x : { v : Int | v > 0 }) -> (y : { v : Int | v > 0 }) -> { v : Int | v >= 0 })
-> { v : Int | v >= 0 }
h : { v : Int | v >= 0 }
h = g f
The syntax can be reasonably approximated by the following grammar,
prog
being the start nonterminal:
prog := {decl} EOF;
decl := funSig [funDef];
(* Fun signatures and types *)
funSig := funName ":" type EOL {EOL};
type := arrow
| arrowLHS;
arrow := (boundLHS | arrowLHS) "->" type;
boundLHS := "(" binder ":" type ")";
arrowLHS := "(" arrow ")"
| baseRT;
baseRT := baseTy
| "{ v :" baseTy "|" refinement "}";
baseTy := "Int" | "Bool" | "IntList";
refinement := atomicRefinement {"&" atomicRefinement};
atomicRefinement := "v" op arg;
op := "<=" | "<" | "=" | "/=" | ">=" | ">";
arg := decimal | "len" identifier | identifier;
(* Fun definitions and terms *)
funDef := funName binder* "=" term EOL {EOL};
term := tapps
| term binOp term;
tapps := atom {atom};
atom := identifier
| decimal
| "if" term "then" term "else" term
| "(" term ")";
binOp := "+" | "-" | ">" | "<";
funName := identifier;
binder := identifier;
identifier := letter {alphaNum | "'"};
Here,
EOF
,EOL
have the obvious meaning.letter
is a Unicode letter as inData.Char.isLetter
.- Similarly,
alphaNum
is whateverData.Char.isAlphaNum
accepts. decimal
is a decimal integer.
The most interesting parsers are defined in the following files:
-
The
funDef
following afunSig
is expected (and checked) to have the samefunName
as thefunSig
. -
The
binder
s infunSig
(inboundLHS
) are expected (and not checked) to have the same name as the matchingbinder
s infunDef
: that is, the following is bad:foo : (x : Int) -> Int foo y = y
Here
x
should be changed toy
or vice versa.Of course, this doesn't matter for those arguments that aren't bound in the type:
foo : (x : Int) -> Int -> Int foo x arg2 = x + arg2
Here
arg2
can be renamed arbitrarily. -
Shadowing variable names isn't checked, let alone dealt with.