theory Frontend imports Machine "HOL-Library.Code_Target_Numeral" begin datatype 'a result = Error string | Ok 'a fun bind :: "'a result \ ('a \ 'b result) \ '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 (\x. bind (f x) g)" by (cases r) auto function decimal :: "nat \ 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 \ string \ string list" where "separate c [] = [[]]" | "separate c (x # xs) = (if x = c then [] # separate c xs else case separate c xs of [] \ [[x]] | y # ys \ (x # y) # ys)" definition words :: "string \ string list" where "words s = filter (\x. x \ []) (separate CHR '' '' (map (\c. if c = CHR 0x09 \ c = CHR 0x0D then CHR '' '' else c) s))" definition number :: "string \ nat option" where "number s = (if s \ [] \ list_all (\c. 48 \ nat_of_char c \ nat_of_char c < 58) s then let n = foldl (\n c. min 65537 (10 * n + nat_of_char c - 48)) 0 s in if n \ 65536 then Some n else None else None)" datatype statement = Label string | Emit "string instruction" | Pad nat definition parse_line :: "nat \ string list \ statement list result" where "parse_line n tokens = (let bad = Error (''invalid instruction at line '' @ decimal n) in case tokens of [] \ Ok [] | [a] \ 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] \ 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 \ Error (''invalid padding at line '' @ decimal n) | Some k \ Ok [Pad k]) else bad | _ \ bad)" fun parse_lines :: "nat \ string list \ statement list result" where "parse_lines n [] = Ok []" | "parse_lines n (s # ss) = bind (parse_line n (words s)) (\xs. bind (parse_lines (Suc n) ss) (\ys. Ok (xs @ ys)))" fun parse_acc :: "nat \ string list \ statement list \ 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 \ Error e | Ok xs \ parse_acc (Suc n) ss (rev xs @ acc))" lemma parse_acc_correct: "parse_acc n ss acc = bind (parse_lines n ss) (\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 \ nat" where "count (Label l) = 0" | "count (Emit op) = 1" | "count (Pad n) = n" fun collect :: "statement list \ nat \ (string \ nat) list \ (string \ nat) list result" where "collect [] pc env = Ok env" | "collect (Label l # ss) pc env = (if map_of env l \ 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 \ nat) list \ string instruction \ 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 \ Error (''undefined label '' @ l) | Some t \ Ok (Jump t))" | "resolve env (Zero l) = (case map_of env l of None \ Error (''undefined label '' @ l) | Some t \ Ok (Zero t))" fun resolve_all :: "(string \ nat) list \ statement list \ 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) (\x. bind (resolve_all env ss) (\xs. Ok (x # xs)))" | "resolve_all env (Pad n # ss) = bind (resolve_all env ss) (\xs. Ok (replicate n Nop @ xs))" fun resolve_acc :: "(string \ nat) list \ statement list \ nat instruction list \ 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 \ Error e | Ok x \ 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) (\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 \ nat instruction list result" where "source s = (if \ list_all (\c. let n = nat_of_char c in n = 9 \ n = 10 \ n = 13 \ (32 \ n \ n < 127)) s then Error ''source must contain printable ASCII, tabs, or line endings'' else bind (parse_lines 1 (separate CHR 0x0A s)) (\ss. if sum_list (map count ss) > 65536 then Error ''program exceeds 65536 instructions'' else bind (collect ss 0 []) (\env. bind (resolve_all env ss) (\p. if wellformed p then Ok p else Error ''branch targets a label after the final instruction''))))" definition checked :: "nat instruction list \ nat list result" where "checked p = (if \ 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 \ assemble p = Some code" by (auto simp: checked_def assemble_def Let_def split: if_splits) theorem checked_execution: "checked p = Ok code \ 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 \ nat set \ 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 (\(p,W). card (branches p - W))") (auto intro: widen_decreases) declare history.simps [simp del] lemma history_nonempty: "history p W \ []" 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 \ string" where "hexbyte n = [''0123456789abcdef'' ! (n div 16 mod 16), ''0123456789abcdef'' ! (n mod 16)]" fun joined :: "string \ string list \ string" where "joined sep [] = []" | "joined sep (x # xs) = x @ (if xs = [] then [] else sep @ joined sep xs)" definition hex :: "nat list \ string" where "hex bs = joined '' '' (map hexbyte bs)" definition signed :: "int \ string" where "signed n = (if n < 0 then ''-'' @ decimal (nat (-n)) else ''+'' @ decimal (nat n))" fun opname :: "operation \ 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 \ nat set \ nat \ 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 \ nat \ nat set \ string" where "passline p n W = ''pass '' @ decimal n @ '' '' @ decimal (address p W (length p)) @ '' bytes widen ['' @ joined '', '' (map decimal (filter (\i. i \ pending p W) [0..i. i \ branches p) [0.. string" where "trace p = (let hs = history p {} in concat (map (\i. passline p i (hs ! i)) [0.. nat list \ string" where "summary p bs = decimal (length p) @ '' instructions, '' @ decimal (length bs) @ '' bytes, '' @ decimal (card (layout p)) @ '' long branches'' @ newline" definition compile :: "string \ nat list result" where "compile s = bind (source s) checked" theorem compile_sound: "compile s = Ok code \ \p. source s = Ok p \ assemble p = Some code" unfolding compile_def by (cases "source s") (auto simp: checked_assemble) theorem checked_failure: "wellformed p \ checked p = Error e \ \ (\W. valid p W)" using assembly_complete[of p] by (auto simp: checked_def assemble_def split: if_splits) definition stopped :: "nat list \ nat \ nat \ string" where "stopped bs s = (case map_option fst (decode (drop (fst s) bs)) of Some Finish \ ''halted'' | _ \ if machine_step bs s = None then ''fault'' else ''fuel exhausted'')" definition runreport :: "nat list \ nat \ 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 \ nat list \ string result" where "listing pc bs = (if bs = [] then Ok [] else case decode bs of None \ Error (''invalid encoding at byte '' @ decimal pc) | Some (op,rest) \ if length rest < length bs then bind (listing (pc + bytesize op) rest) (\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 (\(pc,bs). length bs)") auto definition binary :: "string \ bool \ string" where "binary s = (case compile s of Error e \ (False, e @ newline) | Ok bs \ (True, map char_of_nat bs))" definition disassemble :: "string \ bool \ string" where "disassemble s = (case listing 0 (map nat_of_char s) of Error e \ (False,e @ newline) | Ok text \ (True,text))" definition inspect :: "string list \ nat instruction list \ nat list \ bool \ string" where "inspect args p bs = (let bad = (False, ''usage: span {assemble|trace|list|run} file [fuel]'' @ newline) in case args of [mode] \ 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 \ (False,e @ newline) | Ok s \ (True,s)) else bad | [mode,n] \ if mode = ''run'' then (case number n of None \ (False,''fuel must be between 0 and 65536'' @ newline) | Some fuel \ (True, runreport bs fuel)) else bad | _ \ bad)" definition process :: "string list \ string \ bool \ string" where "process args s = (case source s of Error e \ (False, e @ newline) | Ok p \ case checked p of Error e \ (False, (if args = [''trace''] then trace p else []) @ e @ newline) | Ok bs \ inspect args p bs)" definition artifact :: "string \ (bool \ string) \ string" where "artifact s = (case source s of Error e \ ((False, e @ newline), e @ newline) | Ok p \ case checked p of Error e \ ((False, e @ newline), e @ newline) | Ok bs \ ((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