~james/span

span/src/Frontend.thy -rw-r--r-- 13.2 KiB
0c6f163a — james chore(import): snapshot james.dad.town/span at 868a68649c22 14 hours ago
                                                                                
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
theory Frontend
  imports Machine "HOL-Library.Code_Target_Numeral"
begin

datatype 'a result = Error string | Ok 'a

fun bind :: "'a result \<Rightarrow> ('a \<Rightarrow> 'b result) \<Rightarrow> 'b result" where
  "bind (Error e) f = Error e"
| "bind (Ok x) f = f x"

lemma bind_assoc: "bind (bind r f) g = bind r (\<lambda>x. bind (f x) g)"
  by (cases r) auto

function decimal :: "nat \<Rightarrow> string" where
  "decimal n = (if n < 10 then [char_of_nat (48 + n)] else decimal (n div 10) @ [char_of_nat (48 + n mod 10)])"
  by pat_completeness auto
termination by (relation "measure id") (auto simp: div_less_dividend)

definition newline :: string where "newline = [CHR 0x0A]"

fun separate :: "char \<Rightarrow> string \<Rightarrow> string list" where
  "separate c [] = [[]]"
| "separate c (x # xs) = (if x = c then [] # separate c xs else
    case separate c xs of [] \<Rightarrow> [[x]] | y # ys \<Rightarrow> (x # y) # ys)"

definition words :: "string \<Rightarrow> string list" where
  "words s = filter (\<lambda>x. x \<noteq> []) (separate CHR '' ''
    (map (\<lambda>c. if c = CHR 0x09 \<or> c = CHR 0x0D then CHR '' '' else c) s))"

definition number :: "string \<Rightarrow> nat option" where
  "number s = (if s \<noteq> [] \<and> list_all (\<lambda>c. 48 \<le> nat_of_char c \<and> nat_of_char c < 58) s then
    let n = foldl (\<lambda>n c. min 65537 (10 * n + nat_of_char c - 48)) 0 s in
    if n \<le> 65536 then Some n else None else None)"

datatype statement = Label string | Emit "string instruction" | Pad nat

definition parse_line :: "nat \<Rightarrow> string list \<Rightarrow> statement list result" where
  "parse_line n tokens = (let bad = Error (''invalid instruction at line '' @ decimal n) in
    case tokens of [] \<Rightarrow> Ok []
    | [a] \<Rightarrow> if a = ''nop'' then Ok [Emit Nop] else if a = ''inc'' then Ok [Emit Inc]
      else if a = ''dec'' then Ok [Emit Dec] else if a = ''halt'' then Ok [Emit Stop] else bad
    | [a,b] \<Rightarrow> if a = ''label'' then Ok [Label b] else if a = ''jmp'' then Ok [Emit (Jump b)]
      else if a = ''jz'' then Ok [Emit (Zero b)] else if a = ''pad'' then
        (case number b of None \<Rightarrow> Error (''invalid padding at line '' @ decimal n) | Some k \<Rightarrow> Ok [Pad k])
      else bad
    | _ \<Rightarrow> bad)"

fun parse_lines :: "nat \<Rightarrow> string list \<Rightarrow> statement list result" where
  "parse_lines n [] = Ok []"
| "parse_lines n (s # ss) = bind (parse_line n (words s)) (\<lambda>xs.
    bind (parse_lines (Suc n) ss) (\<lambda>ys. Ok (xs @ ys)))"

fun parse_acc :: "nat \<Rightarrow> string list \<Rightarrow> statement list \<Rightarrow> statement list result" where
  "parse_acc n [] acc = Ok (rev acc)"
| "parse_acc n (s # ss) acc = (case parse_line n (words s) of Error e \<Rightarrow> Error e
    | Ok xs \<Rightarrow> parse_acc (Suc n) ss (rev xs @ acc))"

lemma parse_acc_correct:
  "parse_acc n ss acc = bind (parse_lines n ss) (\<lambda>xs. Ok (rev acc @ xs))"
  by (induction ss arbitrary: n acc) (auto simp: bind_assoc split: result.splits)

declare parse_lines.simps [code drop]

lemma parse_lines_code [code]: "parse_lines n ss = parse_acc n ss []"
  by (cases "parse_lines n ss") (simp_all add: parse_acc_correct)

fun count :: "statement \<Rightarrow> nat" where
  "count (Label l) = 0"
| "count (Emit op) = 1"
| "count (Pad n) = n"

fun collect :: "statement list \<Rightarrow> nat \<Rightarrow> (string \<times> nat) list \<Rightarrow> (string \<times> nat) list result" where
  "collect [] pc env = Ok env"
| "collect (Label l # ss) pc env = (if map_of env l \<noteq> None then Error (''duplicate label '' @ l)
    else collect ss pc ((l,pc) # env))"
| "collect (s # ss) pc env = collect ss (pc + count s) env"

fun resolve :: "(string \<times> nat) list \<Rightarrow> string instruction \<Rightarrow> nat instruction result" where
  "resolve env Nop = Ok Nop"
| "resolve env Inc = Ok Inc"
| "resolve env Dec = Ok Dec"
| "resolve env Stop = Ok Stop"
| "resolve env (Jump l) = (case map_of env l of None \<Rightarrow> Error (''undefined label '' @ l) | Some t \<Rightarrow> Ok (Jump t))"
| "resolve env (Zero l) = (case map_of env l of None \<Rightarrow> Error (''undefined label '' @ l) | Some t \<Rightarrow> Ok (Zero t))"

fun resolve_all :: "(string \<times> nat) list \<Rightarrow> statement list \<Rightarrow> nat instruction list result" where
  "resolve_all env [] = Ok []"
| "resolve_all env (Label l # ss) = resolve_all env ss"
| "resolve_all env (Emit op # ss) = bind (resolve env op) (\<lambda>x.
    bind (resolve_all env ss) (\<lambda>xs. Ok (x # xs)))"
| "resolve_all env (Pad n # ss) = bind (resolve_all env ss) (\<lambda>xs. Ok (replicate n Nop @ xs))"

fun resolve_acc :: "(string \<times> nat) list \<Rightarrow> statement list \<Rightarrow> nat instruction list \<Rightarrow> nat instruction list result" where
  "resolve_acc env [] acc = Ok (rev acc)"
| "resolve_acc env (Label l # ss) acc = resolve_acc env ss acc"
| "resolve_acc env (Emit op # ss) acc = (case resolve env op of Error e \<Rightarrow> Error e
    | Ok x \<Rightarrow> resolve_acc env ss (x # acc))"
| "resolve_acc env (Pad n # ss) acc = resolve_acc env ss (replicate n Nop @ acc)"

lemma resolve_acc_correct:
  "resolve_acc env ss acc = bind (resolve_all env ss) (\<lambda>xs. Ok (rev acc @ xs))"
proof (induction ss arbitrary: acc)
  case Nil
  then show ?case by simp
next
  case (Cons s ss)
  then show ?case by (cases s) (auto simp: bind_assoc split: result.splits)
qed

declare resolve_all.simps [code drop]

lemma resolve_all_code [code]: "resolve_all env ss = resolve_acc env ss []"
  by (cases "resolve_all env ss") (simp_all add: resolve_acc_correct)

definition source :: "string \<Rightarrow> nat instruction list result" where
  "source s = (if \<not> list_all (\<lambda>c. let n = nat_of_char c in
      n = 9 \<or> n = 10 \<or> n = 13 \<or> (32 \<le> n \<and> n < 127)) s
    then Error ''source must contain printable ASCII, tabs, or line endings'' else
    bind (parse_lines 1 (separate CHR 0x0A s)) (\<lambda>ss.
    if sum_list (map count ss) > 65536 then Error ''program exceeds 65536 instructions''
    else bind (collect ss 0 []) (\<lambda>env. bind (resolve_all env ss) (\<lambda>p.
      if wellformed p then Ok p else Error ''branch targets a label after the final instruction''))))"

definition checked :: "nat instruction list \<Rightarrow> nat list result" where
  "checked p = (if \<not> wellformed p then Error ''invalid branch target'' else
    let W = layout p in if valid p W then Ok (bytecode p W)
    else Error ''branch displacement exceeds the signed 16-bit range'')"

lemma checked_assemble:
  "checked p = Ok code \<longleftrightarrow> assemble p = Some code"
  by (auto simp: checked_def assemble_def Let_def split: if_splits)

theorem checked_execution:
  "checked p = Ok code \<Longrightarrow>
    execute (machine_step code) fuel (0,a) = relocate p (layout p) (execute (source_step p) fuel (0,a))"
  by (simp add: checked_assemble assembly_execution)

function history :: "nat instruction list \<Rightarrow> nat set \<Rightarrow> nat set list" where
  "history p W = (if widen p W = W then [W] else W # history p (widen p W))"
  by pat_completeness auto
termination
  by (relation "measure (\<lambda>(p,W). card (branches p - W))") (auto intro: widen_decreases)

declare history.simps [simp del]

lemma history_nonempty: "history p W \<noteq> []"
  by (subst history.simps) auto

lemma history_last: "last (history p W) = settle p W"
  by (induction p W rule: history.induct)
    (subst history.simps, subst settle.simps, auto simp: history_nonempty)

definition hexbyte :: "nat \<Rightarrow> string" where
  "hexbyte n = [''0123456789abcdef'' ! (n div 16 mod 16), ''0123456789abcdef'' ! (n mod 16)]"

fun joined :: "string \<Rightarrow> string list \<Rightarrow> string" where
  "joined sep [] = []"
| "joined sep (x # xs) = x @ (if xs = [] then [] else sep @ joined sep xs)"

definition hex :: "nat list \<Rightarrow> string" where
  "hex bs = joined '' '' (map hexbyte bs)"

definition signed :: "int \<Rightarrow> string" where
  "signed n = (if n < 0 then ''-'' @ decimal (nat (-n)) else ''+'' @ decimal (nat n))"

fun opname :: "operation \<Rightarrow> string" where
  "opname Idle = ''nop''"
| "opname Raise = ''inc''"
| "opname Lower = ''dec''"
| "opname Finish = ''halt''"
| "opname (Branch c wide d) = (if c then ''jz.'' else ''jmp.'') @ (if wide then ''16 '' else ''8 '') @ signed d"

definition branchline :: "nat instruction list \<Rightarrow> nat set \<Rightarrow> nat \<Rightarrow> string" where
  "branchline p W i = ''  #'' @ decimal i @ ''  @'' @ decimal (address p W i) @ ''  '' @
    opname (lower p W i (p ! i)) @ ''  -> @'' @ decimal (address p W (destination p i)) @ newline"

definition passline :: "nat instruction list \<Rightarrow> nat \<Rightarrow> nat set \<Rightarrow> string" where
  "passline p n W = ''pass '' @ decimal n @ ''  '' @ decimal (address p W (length p)) @
    '' bytes  widen ['' @ joined '', '' (map decimal (filter (\<lambda>i. i \<in> pending p W) [0..<length p])) @ '']'' @ newline @
    concat (map (branchline p W) (filter (\<lambda>i. i \<in> branches p) [0..<length p]))"

definition trace :: "nat instruction list \<Rightarrow> string" where
  "trace p = (let hs = history p {} in concat (map (\<lambda>i. passline p i (hs ! i)) [0..<length hs]))"

definition summary :: "nat instruction list \<Rightarrow> nat list \<Rightarrow> string" where
  "summary p bs = decimal (length p) @ '' instructions, '' @ decimal (length bs) @ '' bytes, '' @
    decimal (card (layout p)) @ '' long branches'' @ newline"

definition compile :: "string \<Rightarrow> nat list result" where
  "compile s = bind (source s) checked"

theorem compile_sound:
  "compile s = Ok code \<Longrightarrow> \<exists>p. source s = Ok p \<and> assemble p = Some code"
  unfolding compile_def by (cases "source s") (auto simp: checked_assemble)

theorem checked_failure:
  "wellformed p \<Longrightarrow> checked p = Error e \<Longrightarrow> \<not> (\<exists>W. valid p W)"
  using assembly_complete[of p]
  by (auto simp: checked_def assemble_def split: if_splits)

definition stopped :: "nat list \<Rightarrow> nat \<times> nat \<Rightarrow> string" where
  "stopped bs s = (case map_option fst (decode (drop (fst s) bs)) of
    Some Finish \<Rightarrow> ''halted'' | _ \<Rightarrow> if machine_step bs s = None then ''fault'' else ''fuel exhausted'')"

definition runreport :: "nat list \<Rightarrow> nat \<Rightarrow> string" where
  "runreport bs fuel = (let s = execute (machine_step bs) fuel (0,0) in
    stopped bs s @ ''  pc='' @ decimal (fst s) @ ''  acc='' @ decimal (snd s) @ newline)"

function listing :: "nat \<Rightarrow> nat list \<Rightarrow> string result" where
  "listing pc bs = (if bs = [] then Ok [] else case decode bs of None \<Rightarrow> Error (''invalid encoding at byte '' @ decimal pc)
    | Some (op,rest) \<Rightarrow> if length rest < length bs then
      bind (listing (pc + bytesize op) rest) (\<lambda>s. Ok (decimal pc @ ''  '' @
        hex (take (bytesize op) bs) @ ''  '' @ opname op @ newline @ s)) else Error ''invalid decoder progress'')"
  by pat_completeness auto
termination by (relation "measure (\<lambda>(pc,bs). length bs)") auto

definition binary :: "string \<Rightarrow> bool \<times> string" where
  "binary s = (case compile s of Error e \<Rightarrow> (False, e @ newline) | Ok bs \<Rightarrow> (True, map char_of_nat bs))"

definition disassemble :: "string \<Rightarrow> bool \<times> string" where
  "disassemble s = (case listing 0 (map nat_of_char s) of Error e \<Rightarrow> (False,e @ newline) | Ok text \<Rightarrow> (True,text))"

definition inspect :: "string list \<Rightarrow> nat instruction list \<Rightarrow> nat list \<Rightarrow> bool \<times> string" where
  "inspect args p bs = (let bad = (False, ''usage: span {assemble|trace|list|run} file [fuel]'' @ newline) in
    case args of [mode] \<Rightarrow>
      if mode = ''assemble'' then (True, summary p bs @ hex bs @ newline)
      else if mode = ''trace'' then (True, trace p @ summary p bs)
      else if mode = ''list'' then (case listing 0 bs of Error e \<Rightarrow> (False,e @ newline) | Ok s \<Rightarrow> (True,s))
      else bad
    | [mode,n] \<Rightarrow> if mode = ''run'' then
      (case number n of None \<Rightarrow> (False,''fuel must be between 0 and 65536'' @ newline)
       | Some fuel \<Rightarrow> (True, runreport bs fuel)) else bad
    | _ \<Rightarrow> bad)"

definition process :: "string list \<Rightarrow> string \<Rightarrow> bool \<times> string" where
  "process args s = (case source s of Error e \<Rightarrow> (False, e @ newline) | Ok p \<Rightarrow>
    case checked p of Error e \<Rightarrow> (False, (if args = [''trace''] then trace p else []) @ e @ newline)
    | Ok bs \<Rightarrow> inspect args p bs)"

definition artifact :: "string \<Rightarrow> (bool \<times> string) \<times> string" where
  "artifact s = (case source s of Error e \<Rightarrow> ((False, e @ newline), e @ newline)
    | Ok p \<Rightarrow> case checked p of Error e \<Rightarrow> ((False, e @ newline), e @ newline)
    | Ok bs \<Rightarrow> ((True, map char_of_nat bs), summary p bs @ hex bs @ newline))"

theorem artifact_binary: "fst (artifact s) = binary s"
  by (auto simp: artifact_def binary_def compile_def split: result.splits)

theorem artifact_report: "(fst (fst (artifact s)), snd (artifact s)) = process [''assemble''] s"
  by (auto simp: artifact_def process_def inspect_def Let_def split: result.splits)

end