From 7d99400a08ffdfae7e1b35205b55350ef711c303 Mon Sep 17 00:00:00 2001 From: james Date: Wed, 16 Sep 2026 12:00:00 +0100 Subject: [PATCH] feat(frontend): prove parsed expressions preserve lexical scope --- src/Frontend.thy | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/Frontend.thy b/src/Frontend.thy index a404b02..6e019fb 100644 --- a/src/Frontend.thy +++ b/src/Frontend.thy @@ -26,6 +26,9 @@ 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 @@ -36,10 +39,16 @@ definition atom :: "string list \ string \ expression op 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) @@ -58,20 +67,35 @@ function parse :: "nat \ string list \ string list \(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 + definition prepare :: "string \ expression option" where "prepare s = (if \ list_all (\c. nat_of_char c < 128) s then None else Option.bind (parse 128 [] (lex s [] [])) (\(e,rest). - if rest = [] \ scoped 0 e \ nodes e \ 4096 then + if rest = [] \ nodes e \ 4096 then let code = compile (optimise e) [] in if check code 0 = Some 1 \ demand (optimise e) \ 512 \ list_all encodable (native code) then Some e else None else None))" lemma prepare_scoped: "prepare s = Some e \ scoped 0 e" - by (auto simp: prepare_def Let_def split: if_splits bind_splits prod.splits) + by (auto simp: prepare_def Let_def dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_bounded: "prepare s = Some e \ bounded 512 (compile (optimise e) []) 0" - by (auto simp: prepare_def Let_def closed_demand optimise_scoped split: if_splits bind_splits prod.splits) + by (auto simp: prepare_def Let_def closed_demand optimise_scoped dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_encodable: "prepare s = Some e \ list_all encodable (native (compile (optimise e) []))"