theory Frontend imports Encoding begin fun finish :: "string \ string list \ string list" where "finish [] out = out" | "finish word out = rev word # out" fun scan :: "string \ string \ string list \ bool \ string list" where "scan [] word out comment = rev (finish word out)" | "scan (c # cs) word out comment = (if comment then scan cs [] out (c \ CHR 0x0A \ c \ CHR 0x0D) else if c = CHR '';'' then scan cs [] (finish word out) True else if c = CHR '' '' \ c = CHR 0x09 \ c = CHR 0x0A \ c = CHR 0x0D then scan cs [] (finish word out) False else if c = CHR ''('' \ c = CHR '')'' then scan cs [] ([c] # finish word out) False else scan cs (c # word) out False)" definition lex :: "string \ string \ string list \ string list" where "lex s word out = scan s word out False" fun digit :: "char \ nat" where "digit c = (let n = nat_of_char c in if 48 \ n \ n < 58 then n - 48 else if 65 \ n \ n < 71 then n - 55 else if 97 \ n \ n < 103 then n - 87 else 16)" definition parse_digits :: "nat \ string \ nat option" where "parse_digits base s = (if s = [] \ \ list_all (\c. digit c < base) s then None else let n = foldl (\n c. min (2 ^ 64) (base * n + digit c)) 0 s in if n < 2 ^ 64 then Some n else None)" definition number :: "string \ nat option" where "number s = (case s of a # b # rest \ if a = CHR ''0'' \ (b = CHR ''x'' \ b = CHR ''X'') then parse_digits 16 rest else parse_digits 10 s | _ \ parse_digits 10 s)" theorem number_bound: "number s = Some n \ n < 2 ^ 64" by (auto simp: number_def parse_digits_def Let_def split: list.splits if_splits) definition valid_name :: "string \ bool" where "valid_name s = (case s of [] \ False | c # cs \ 97 \ nat_of_char c \ nat_of_char c \ 122 \ list_all (\d. (97 \ nat_of_char d \ nat_of_char d \ 122) \ (48 \ nat_of_char d \ nat_of_char d < 58)) cs \ s \ ''let'' \ s \ ''if'')" fun lookup :: "string \ string list \ nat option" where "lookup name [] = None" | "lookup name (x # xs) = (if name = x then Some 0 else map_option Suc (lookup name xs))" lemma lookup_bound: "lookup name names = Some i \ i < length names" by (induction names arbitrary: i) (auto split: if_splits option.splits) fun operator :: "string \ operator option" where "operator s = (if s = ''+'' then Some Add else if s = ''-'' then Some Sub else if s = ''*'' then Some Mul else if s = ''and'' then Some And else if s = ''or'' then Some Or else if s = ''xor'' then Some Xor else if s = ''shl'' then Some Shl else if s = ''shr'' then Some Shr else if s = ''<'' then Some Less else None)" definition atom :: "string list \ string \ expression option" where "atom names s = (case lookup s names of Some i \ Some (Variable i) | None \ if s = ''x'' then Some (Input X) else if s = ''y'' then Some (Input Y) else if s = ''t'' then Some (Input T) else map_option (Literal \ word_of_nat) (number s))" lemma atom_scoped: "atom names s = Some e \ scoped (length names) e" by (auto simp: atom_def split: option.splits if_splits dest: lookup_bound) fun close :: "expression \ string list \ (expression \ string list) option" where "close e (s # rest) = (if s = '')'' then Some (e,rest) else None)" | "close e [] = None" lemma close_expression: "close e tokens = Some (a,rest) \ a = e" by (cases tokens) (auto split: if_splits) function parse :: "nat \ string list \ string list \ (expression \ string list) option" where "parse fuel names tokens = (if fuel = 0 then None else case tokens of [] \ None | s # rest \ if s \ ''('' then map_option (\e. (e,rest)) (atom names s) else case rest of [] \ None | op # args \ if op = ''let'' then (case args of [] \ None | name # body \ if \ valid_name name then None else Option.bind (parse (fuel - 1) names body) (\(a,tail). Option.bind (parse (fuel - 1) (name # names) tail) (\(b,tail). close (Let a b) tail))) else if op = ''if'' then Option.bind (parse (fuel - 1) names args) (\(c,tail). Option.bind (parse (fuel - 1) names tail) (\(a,tail). Option.bind (parse (fuel - 1) names tail) (\(b,tail). close (Cond c a b) tail))) else Option.bind (operator op) (\f. Option.bind (parse (fuel - 1) names args) (\(a,tail). Option.bind (parse (fuel - 1) names tail) (\(b,tail). close (Binary f a b) tail))))" by pat_completeness auto termination by (relation "measure (\(fuel,names,tokens). fuel)") auto declare parse.simps [simp del] theorem parse_scoped: "parse fuel names tokens = Some (e,rest) \ scoped (length names) e" proof (induction fuel arbitrary: names tokens e rest) case 0 then show ?case by (subst (asm) parse.simps) auto next case (Suc fuel) from Suc.prems show ?case by (subst (asm) parse.simps) (auto simp del: operator.simps dest: atom_scoped split: list.splits if_splits bind_splits option.splits prod.splits; drule close_expression; auto dest: Suc.IH) qed record limits = depth_budget :: nat node_budget :: nat stack_budget :: nat definition defaults :: limits where "defaults = \depth_budget = 128, node_budget = 4096, stack_budget = 512\" definition valid_limits :: "limits \ bool" where "valid_limits lim = (depth_budget lim \ 128 \ node_budget lim \ 4096 \ stack_budget lim \ 512)" definition prepare_with :: "limits \ string \ expression option" where "prepare_with lim s = (if \ valid_limits lim \ \ list_all (\c. nat_of_char c < 128) s then None else Option.bind (parse (depth_budget lim) [] (lex s [] [])) (\(e,rest). if rest = [] \ nodes e \ node_budget lim then let code = compile (optimise e) [] in if check code 0 = Some 1 \ demand (optimise e) \ stack_budget lim \ list_all encodable (native code) then Some e else None else None))" definition prepare :: "string \ expression option" where "prepare = prepare_with defaults" lemma prepare_with_scoped: "prepare_with lim s = Some e \ scoped 0 e" by (auto simp: prepare_with_def Let_def dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_with_bounded: "prepare_with lim s = Some e \ bounded (stack_budget lim) (compile (optimise e) []) 0" by (auto simp: prepare_with_def Let_def closed_demand optimise_scoped dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_scoped: "prepare s = Some e \ scoped 0 e" unfolding prepare_def using prepare_with_scoped by blast lemma prepare_bounded: "prepare s = Some e \ bounded 512 (compile (optimise e) []) 0" using prepare_with_bounded[where lim=defaults] by (simp add: prepare_def defaults_def) lemma prepare_encodable: "prepare s = Some e \ list_all encodable (native (compile (optimise e) []))" by (auto simp: prepare_def prepare_with_def Let_def split: if_splits bind_splits prod.splits) lemma prepare_program: "prepare s = Some e \ list_all encodable (program e)" unfolding program_def using prepare_encodable by (auto intro: transfers_property) theorem prepared_correct: "prepare source = Some e \ execute values (compile (optimise e) []) [] = Some [evaluate e [] values]" using compile_closed prepare_scoped by blast theorem prepared_native: "prepare source = Some e \ map_option observe (run (native (compile (optimise e) [])) s) = Some (inputs s, evaluate e [] (inputs s) # stack s)" using native_expression prepare_scoped by blast theorem prepared_bytes: "prepare source = Some e \ recover (length (program e)) (executable e) = Some (program e, [195])" unfolding executable_def using recover_encode prepare_program by blast theorem prepared_stacks: "prepare source = Some e \ \frames. snapshots values (compile (optimise e) []) [] = Some frames \ list_all (\s. length s \ 512) frames" using bounded_execution[where s="[]" and limit=512] prepare_bounded by 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]" datatype problem = Unexpected nat | Exhausted nat datatype 'a outcome = Failed problem | Parsed 'a fun parsed_bind :: "'a outcome \ ('a \ 'b outcome) \ 'b outcome" where "parsed_bind (Failed p) f = Failed p" | "parsed_bind (Parsed x) f = f x" fun close_at :: "expression \ string list \ nat \ (expression \ string list \ nat) outcome" where "close_at e [] pos = Failed (Unexpected pos)" | "close_at e (s # rest) pos = (if s = '')'' then Parsed (e,rest,Suc pos) else Failed (Unexpected pos))" lemma close_at_cursor: "close_at e tokens pos = Parsed (a,rest,nextpos) \ a = e \ nextpos = Suc pos" by (cases tokens) (auto split: if_splits) function diagnose :: "nat \ string list \ string list \ nat \ (expression \ string list \ nat) outcome" where "diagnose fuel names tokens pos = (if fuel = 0 then Failed (Exhausted pos) else case tokens of [] \ Failed (Unexpected pos) | s # rest \ if s \ ''('' then (case atom names s of None \ Failed (Unexpected pos) | Some e \ Parsed (e,rest,Suc pos)) else case rest of [] \ Failed (Unexpected (Suc pos)) | op # args \ if op = ''let'' then (case args of [] \ Failed (Unexpected (pos + 2)) | name # body \ if \ valid_name name then Failed (Unexpected (pos + 2)) else parsed_bind (diagnose (fuel - 1) names body (pos + 3)) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) (name # names) tail nextpos) (\(b,tail,lastpos). close_at (Let a b) tail lastpos))) else if op = ''if'' then parsed_bind (diagnose (fuel - 1) names args (pos + 2)) (\(c,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(b,tail,lastpos). close_at (Cond c a b) tail lastpos))) else case operator op of None \ Failed (Unexpected (Suc pos)) | Some f \ parsed_bind (diagnose (fuel - 1) names args (pos + 2)) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(b,tail,lastpos). close_at (Binary f a b) tail lastpos)))" by pat_completeness auto termination by (relation "measure (\(fuel,names,tokens,pos). fuel)") auto declare diagnose.simps [simp del] definition source_error_with :: "limits \ string \ string" where "source_error_with lim s = (if \ valid_limits lim then ''limits exceed compiler bounds'' @ newline else if \ list_all (\c. nat_of_char c < 128) s then ''source must contain ASCII'' @ newline else case diagnose (depth_budget lim) [] (lex s [] []) 0 of Failed (Unexpected pos) \ ''syntax error at token '' @ decimal pos @ newline | Failed (Exhausted pos) \ ''parse depth limit exceeded at token '' @ decimal pos @ newline | Parsed (e,rest,pos) \ if rest \ [] then ''syntax error at token '' @ decimal pos @ newline else if nodes e > node_budget lim then ''program exceeds '' @ decimal (node_budget lim) @ '' nodes'' @ newline else if demand (optimise e) > stack_budget lim then ''evaluation stack exceeds '' @ decimal (stack_budget lim) @ '' words'' @ newline else ''native validation failed'' @ newline)" definition artifact_with :: "limits \ string \ (bool \ string) \ string" where "artifact_with lim s = (case prepare_with lim s of None \ ((False,[]), source_error_with lim s) | Some e \ let opt = optimise e; bytes = executable e in ((True, map char_of_nat bytes), decimal (nodes e) @ '' nodes -> '' @ decimal (nodes opt) @ '' nodes, '' @ decimal (length bytes) @ '' bytes, '' @ decimal (demand opt) @ '' stack words'' @ newline))" definition source_error :: "string \ string" where "source_error = source_error_with defaults" definition artifact :: "string \ (bool \ string) \ string" where "artifact = artifact_with defaults" definition configured :: "string \ string \ string \ string \ (bool \ string) \ string" where "configured depth count stacktext source = (case (number depth, number count, number stacktext) of (Some d,Some n,Some k) \ artifact_with \depth_budget=d, node_budget=n, stack_budget=k\ source | _ \ ((False,[]),''invalid resource limits'' @ newline))" definition boundary :: "nat list" where "boundary = [0,1,63,64,65,127,255,256,2 ^ 31,2 ^ 32,2 ^ 63,2 ^ 64 - 1]" definition sample :: "nat \ input \ pixel" where "sample i = (\v. word_of_nat (if i < 12 then boundary ! ((i + (case v of X \ 0 | Y \ 3 | T \ 7)) mod 12) else case v of X \ (17*i) mod 257 | Y \ (29*i) mod 131 | T \ 13*i))" definition row :: "expression \ nat \ nat list" where "row e i = concat (map (\v. octets 8 (unat v)) [sample i X, sample i Y, sample i T, evaluate e [] (sample i)])" definition vectors :: "string \ bool \ string" where "vectors s = (case prepare s of None \ (False, source_error s) | Some e \ (True, map char_of_nat (concat (map (row e) [0..<512]))))" definition reference :: "string \ string \ string \ string \ bool \ string" where "reference source x y t = (case prepare source of None \ (False,source_error source) | Some e \ case (number x, number y, number t) of (Some a,Some b,Some c) \ (True, decimal (unat (evaluate e [] (\v. word_of_nat (case v of X \ a | Y \ b | T \ c)))) @ newline) | _ \ (False,''invalid input'' @ newline))" end