-
Notifications
You must be signed in to change notification settings - Fork 0
/
MLS.Tree.fst
143 lines (126 loc) · 4.92 KB
/
MLS.Tree.fst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module MLS.Tree
open FStar.Mul
#set-options "--fuel 1 --ifuel 1"
let divides (m:nat) (n:nat) = exists (k:nat). n = k*m
type tree_index (l:nat) = n:nat{(pow2 l) `divides` n}
val left_index: #l:pos -> tree_index l -> tree_index (l-1)
let left_index #l n =
// Beginning of math proof
eliminate exists (k:nat). n = k*(pow2 l)
returns (pow2 (l-1)) `divides` n
with _. (
introduce exists (k':nat). n = k'*(pow2 (l-1))
with (2*k)
and ()
);
// End of math proof
n
val right_index: #l:pos -> tree_index l -> tree_index (l-1)
let right_index #l n =
// Beginning of math proof
eliminate exists (k:nat). n = k*(pow2 l)
returns (pow2 (l-1)) `divides` (n + pow2 (l-1))
with _. (
introduce exists (k':nat). n + pow2 (l-1) = k'*(pow2 (l-1))
with (2*k+1)
and ()
);
// End of math proof
n + pow2 (l-1)
/// Type for complete binary tree, with height `l`.
/// The index `i` represents the leaf index of the leftmost leaf in the subtree.
/// It does not change the shape of the tree, it is however very useful:
/// many recursive functions must compute it through the recursive calls,
/// putting it in the type allows to compute it for free, using F* implicit arguments.
/// It therefore enforces a correct-by-construction tracking of leaf indices,
/// rules out programmer errors,
/// and enforces the MLS invariant that two sub-trees at different positions,
/// even if otherwise identical, are never interchangeable.
type tree (leaf_t:Type) (node_t:Type) (l:nat) (i:tree_index l) =
| TLeaf:
data: leaf_t{l == 0} ->
tree leaf_t node_t l i
| TNode:
data: node_t{l > 0} ->
left: tree leaf_t node_t (l-1) (left_index i) ->
right: tree leaf_t node_t (l-1) (right_index i) ->
tree leaf_t node_t l i
let leaf_index_inside (l:nat) (i:tree_index l) (li:nat) = i <= li && li < i+(pow2 l)
val leaf_index_inside_tree: #leaf_t:Type -> #node_t:Type -> #l:nat -> #i:tree_index l -> tree leaf_t node_t l i -> nat -> bool
let leaf_index_inside_tree #leaf_t #node_t #l #i t li = leaf_index_inside l i li
type leaf_index (l:nat) (i:tree_index l) = li:nat{leaf_index_inside l i li}
let is_left_leaf (#l:pos) (#i:tree_index l) (li:leaf_index l i) = li < i+(pow2 (l-1))
/// Type for path in a tree of height `l`, with left-most leaf index `i`,
/// from the root down to leaf index `li`.
/// `i` and `li` have no impact on the structure of the type ;
/// however like in `tree` they prove to be very useful.
type path (leaf_t:Type) (node_t:Type) (l:nat) (i:tree_index l) (li:leaf_index l i) =
| PLeaf:
data:leaf_t{l == 0} ->
path leaf_t node_t l i li
| PNode:
data:node_t{l > 0} ->
next:path leaf_t node_t (l-1) (if is_left_leaf li then left_index i else right_index i) li ->
path leaf_t node_t l i li
/// When dealing with a subtree and a specific leaf in this subtree,
/// MLS use the term `child` for the child of the subtree containing the leaf,
/// and the term `sibling` for the other child (that doesn't contain the leaf).
/// This function returns a pair containing the left child and right child,
/// with the first element being the "child" and the second being the "sibling".
/// This is why the return type signature is a bit involved.
val get_child_sibling:
#l:pos -> #i:tree_index l ->
#leaf_t:Type -> #node_t:Type ->
tree leaf_t node_t l i -> li:leaf_index l i ->
Pure (
tree leaf_t node_t (l-1) (if is_left_leaf li then left_index i else right_index i)
&
tree leaf_t node_t (l-1) (if is_left_leaf li then right_index i else left_index i)
)
(requires True)
(ensures fun _ -> leaf_index_inside (l-1) (if is_left_leaf li then left_index i else right_index i) li)
let get_child_sibling #l #i t li =
match t with
| TNode content left right ->
if is_left_leaf li then
(left, right)
else
(right, left)
#push-options "--fuel 2"
val get_leaf_list:
#l:nat -> #i:tree_index l ->
#leaf_t:Type -> #node_t:Type ->
tree leaf_t node_t l i ->
Pure (list leaf_t)
(requires True)
(fun res -> List.Tot.length res == pow2 l)
let rec get_leaf_list #l #i #leaf_t #node_t t =
let open FStar.List.Tot in
match t with
| TLeaf data -> [data]
| TNode _ left right ->
(get_leaf_list left) @ (get_leaf_list right)
#pop-options
val leaf_at:
#leaf_t:Type -> #node_t:Type ->
#l:nat -> #i:tree_index l ->
tree leaf_t node_t l i -> li:leaf_index l i ->
leaf_t
let rec leaf_at #leaf_t #node_t #l #i t li =
match t with
| TLeaf content -> content
| TNode _ left right ->
if is_left_leaf li then
leaf_at left li
else
leaf_at right li
val print_tree:
#leaf_t:Type -> #node_t:Type ->
#l:nat -> #i:tree_index l ->
(leaf_t -> string) -> (node_t -> string) -> tree leaf_t node_t l i ->
string
let rec print_tree #leaf_t #node_t #l #i print_leaf print_node t =
match t with
| TLeaf data -> print_leaf data
| TNode data left right ->
"(" ^ print_tree print_leaf print_node left ^ ") " ^ print_node data ^ " (" ^ print_tree print_leaf print_node right ^ ")"