diff options
Diffstat (limited to 'primrose/src')
27 files changed, 4849 insertions, 0 deletions
diff --git a/primrose/src/analysis.rs b/primrose/src/analysis.rs new file mode 100644 index 0000000..40fa0d9 --- /dev/null +++ b/primrose/src/analysis.rs @@ -0,0 +1,336 @@ +use crate::description::{Tag, InforMap}; +use crate::parser::{Prog, Block, Spec, Decl, Term, Refinement, Id, spec}; +use crate::spec_map::{PropSpecs}; + +use std::ops::Deref; +use std::env; +use std::fs; +use std::io::{Write, BufReader, BufRead, Error, ErrorKind}; + +type AnalyserError = String; +const LANGDECL: &str = "#lang rosette\n"; +const REQUIRE: &str = "(require \"../combinators.rkt\")\n"; +const EXTRAREQUIRE: &str = "(require \"../gen_lib_spec/ops.rkt\")\n"; +const GENPATH: &str = "./racket_specs/gen_prop_spec/"; + +// length can be adjusted +// set to 5 to speed up testing +const LISTMODEL: &str = +r#" +(define (generate-list n) + (define-symbolic* y integer? #:length n) + y) +(define-symbolic len (bitvector 32)) +(define ls (take-bv (generate-list 5) len)) +"#; + +fn gen_list_model(size: usize) -> String { + format!( +r#" +(define (generate-list n) + (define-symbolic* y integer? #:length n) + y) +(define-symbolic len (bitvector 32)) +(define ls (take-bv (generate-list {size}) len)) +"#) +} + +fn gen_symbolic(n: &str) -> String { + format!( +r#" +(define-symbolic {n} integer?) +"#) +} + +fn gen_symbolics(symbolics: &Vec<String>) -> String { + let provide = symbolics.join(" "); + let mut code = String::new(); + for s in symbolics.iter () { + code = code + &gen_symbolic(s); + } + let provide = format!( +r#" +(provide {provide} ls) +"#); + code = code + &provide; + code +} + +pub struct Analyser { + ctx: InforMap, + prop_specs: PropSpecs, +} + +impl Analyser { + pub fn new() -> Analyser { + Analyser { + ctx: InforMap::new(), + prop_specs: PropSpecs::new() + } + } + + pub fn get_ctx(&self) -> &InforMap { + &self.ctx + } + + pub fn get_prop_specs(&self) -> &PropSpecs { + &self.prop_specs + } + + + pub fn analyse_prog(&mut self, prog: Prog, model_size: usize) -> Result<(), AnalyserError> { + let specs: Vec<Spec> = + prog.iter() + .filter(| block | block.is_spec_block()) + .map(| block | block.extract_spec()) + .collect(); + self.analyse_specs(specs, model_size) + } + + pub fn analyse_specs(&mut self, specs: Vec<Spec>, model_size: usize) -> Result<(), AnalyserError> { + let concat_specs = specs.concat(); + let prop_decls: Vec<&Decl> = + concat_specs.iter() + .filter(| decl | decl.is_prop_decl()) + .collect(); + let contype_decls: Vec<&Decl> = + concat_specs.iter() + .filter(| decl | decl.is_contype_decl()) + .collect(); + match self.analyse_prop_decls(prop_decls, model_size) { + Ok(_) => match self.analyse_contype_decls(contype_decls.clone()) { + Ok(_) => self.analyse_bound_decls(contype_decls), + Err(e) => Err(e) + } + Err(e) => Err(e) + } + } + + pub fn analyse_prop_decls(&mut self, decls: Vec<&Decl>, model_size: usize) -> Result<(), AnalyserError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.analyse_prop_decl(decl, model_size) { + Ok(_) => continue, + Err(e) => result = Err(e) + } + } + result + } + + pub fn analyse_prop_decl(&mut self, decl: &Decl, model_size: usize) -> Result<(), AnalyserError> { + match decl { + Decl::PropertyDecl((id, _), term) => { + let mut mterm = term.clone(); + let mut cdr_added = Vec::<String>::new(); + let mut symbolics = Vec::<String>::new(); + let code = "(define ".to_string() + id + " " + &self.analyse_term(&mut mterm, true, false, &mut cdr_added, &mut symbolics) + ")\n" + "(provide " + id + ")"; + let filename = id.to_string() + ".rkt"; + let mut symbolics_provided = gen_symbolics(&vec!["n".to_string()]); + if (symbolics.len() != 0) { + symbolics_provided = gen_symbolics(&symbolics); + } + self.write_prop_spec_file(filename.clone(), code, symbolics_provided, model_size); + let prop_tag = Tag::Prop(Box::new(id.to_string())); + self.ctx.put(id.to_string(), prop_tag); + if (symbolics.len() == 0) { + self.prop_specs.insert(id.to_string(), (filename, vec!["n".to_string()])); + } else { + self.prop_specs.insert(id.to_string(), (filename, symbolics)); + } + Ok(()) + }, + _ => Err("Not a valid property declaration".to_string()) + } + } + + pub fn analyse_bound_decls(&mut self, decls: Vec<&Decl>) -> Result<(), AnalyserError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.analyse_bound_decl(decl) { + Ok(_) => continue, + Err(e) => result = Err(e) + } + } + result + } + + pub fn analyse_bound_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> { + match decl { + Decl::ConTypeDecl(con_ty, (_, ins, tags)) => { + let (c, t) = con_ty.get_con_elem().unwrap(); + let mut name = c.clone() + "Trait"; + let bound_tag = Tag::Bound((c.clone(), t), Box::new(ins.clone().into_iter().collect::<Vec<String>>())); + let immut_ctx = self.ctx.clone(); + // prevent generating existing name + let mut i: usize = 0; + while immut_ctx.contains(&name) { + name = name + &i.to_string(); + i = i+1; + } + let con_tag = immut_ctx.get_id(c.clone()).unwrap(); + match con_tag { + Tag::Con(elem_ty, _, tags) => { + self.ctx.update(c.clone(), Tag::Con(elem_ty.to_string(), name.clone(), Box::new(tags.to_vec()))); + }, + _ => { + return Err("Not a valid container declaration.".to_string()); + } + } + self.ctx.put(name, bound_tag); + Ok(()) + }, + _ => Err("Not a valid bound declaration".to_string()) + } + } + + pub fn analyse_contype_decls(&mut self, decls: Vec<&Decl>) -> Result<(), AnalyserError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.analyse_contype_decl(decl) { + Ok(_) => continue, + Err(e) => result = Err(e) + } + } + result + } + + pub fn analyse_contype_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> { + let mut tags = Vec::<Tag>::new(); + match decl { + Decl::ConTypeDecl(con_ty, (vid, ins, r)) => { + let (c, t) = con_ty.get_con_elem().unwrap(); + let i_tag = Tag::Bound((c.clone(), t.clone()), Box::new(ins.clone().into_iter().collect::<Vec<String>>())); + tags.push(i_tag); + match self.analyse_ref(r.deref(), vid) { + Ok(prop_tags) => { + let mut prop_tags_mut = prop_tags.clone(); + tags.append(&mut prop_tags_mut); + let con_tag = Tag::Con(t, String::new(), Box::new(tags)); + self.ctx.put(c, con_tag); + Ok(()) + }, + Err(e) => Err(e) + } + }, + _ => Err("Not a valid container type declaration".to_string()) + } + } + + fn analyse_ref<'a>(&self, r: &'a Refinement, vid: &'a Box<String>) -> Result<Vec<Tag>, AnalyserError> { + match r { + Refinement::Prop(term) => { + match term.deref() { + Term::AppTerm(term1, term2) => { + match self.retrive_ref_term(term1) { + Ok(t) => { + let tags = vec![t.clone()]; + Ok(tags) + }, + Err(e) => Err(e) + } + }, + _ => Err("Not a valid term for refining the type Con<T>".to_string()) + } + }, + Refinement::AndProps(r1, r2) => { + match self.analyse_ref(r1, vid) { + Ok(tags1) => { + match self.analyse_ref(r2, vid) { + Ok(tags2) => { + Ok([tags1, tags2].concat()) + }, + Err(e) => Err(e) + } + }, + Err(e) => Err(e) + } + } + } + } + + fn retrive_ref_term(&self, term: &Term) -> Result<&Tag, AnalyserError> { + match term { + Term::VarTerm(id) => { + match self.ctx.get_id(id.to_string()) { + Some(t) => { + match t { + Tag::Prop(_) => Ok(t), + _ => Err(id.to_string() + " does not have a valid property") + } + }, + _ => Err("Undefined variable: ".to_string() + id) + } + }, + _ => Err("Should be a varible term".to_string()) + } + } + + pub fn analyse_term(&self, term: &mut Term, is_outter_app: bool, is_quantifier: bool, cdr_added: &mut Vec<String>, symbolics: &mut Vec<String>) -> String { + match term { + Term::LitTerm(lit) => { + if (lit.to_string() == "true".to_string()) { + "#t".to_string() + } else { + "#f".to_string() + } + }, + Term::VarTerm(id) => { + id.to_string() + }, + Term::LambdaTerm((id, _), t) => { + if (is_quantifier) { + symbolics.push(id.to_string()); + "(list ".to_string() + id + ") " + &self.analyse_term(t, true, false, cdr_added, symbolics) + } else { + "(lambda (".to_string() + id + ") " + &self.analyse_term(t, true, false, cdr_added, symbolics) + ")" + } + + }, + Term::AppTerm(t1, t2) => { + // Temporary solution of cdr required to adjust model ops + if ((*t1.clone()).require_cdr() && !cdr_added.contains(&t1.to_string())) { + cdr_added.push(t1.to_string()); + *term = Term::AppTerm(Box::new(Term::VarTerm(Box::new("cdr".to_string()))), Box::new(term.clone())); + self.analyse_term(term, is_outter_app, is_quantifier, cdr_added, symbolics) + } else { + let mut result = String::new(); + match ((*t1.clone()).is_quantifier(), *t2.clone()) { + (_, Term::AppTerm(_, _)) => { + if (is_outter_app) { + "(".to_string() + &self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, true, false, cdr_added, symbolics) + ")" + } else { + self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, true, false, cdr_added, symbolics) + } + }, + (false, _) => { + if (is_outter_app) { + "(".to_string() + &self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, false, false, cdr_added, symbolics) + ")" + } else { + self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, false, false, cdr_added, symbolics) + } + }, + (true, _) => { + if (is_outter_app) { + "(".to_string() + &self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, false, true, cdr_added, symbolics) + ")" + } else { + self.analyse_term(t1, false, false, cdr_added, symbolics) + " " + &self.analyse_term(t2, false, true, cdr_added, symbolics) + } + } + } + } + } + } + } + + fn write_prop_spec_file(&self, filename : String, contents: String, symbolics: String, model_size: usize) -> Result<(), Error> { + let mut output = fs::File::create(GENPATH.to_owned() + &filename)?; + write!(output, "{}", LANGDECL.to_string())?; + write!(output, "{}", REQUIRE.to_string())?; + write!(output, "{}", EXTRAREQUIRE.to_string())?; + let list_model = gen_list_model(model_size); + write!(output, "{}", list_model)?; + write!(output, "{}", contents)?; + write!(output, "{}", symbolics)?; + Ok(()) + } +} diff --git a/primrose/src/bounded_ops.rs b/primrose/src/bounded_ops.rs new file mode 100644 index 0000000..aae7410 --- /dev/null +++ b/primrose/src/bounded_ops.rs @@ -0,0 +1,25 @@ +use std::collections::HashMap; +use std::collections::hash_map::Iter; +use crate::types::{Type, TypeVar, Bounds}; + +type BoundName = String; +type OpName = String; +type OpInfo = (OpName, Type); +pub type BoundedOps = HashMap<BoundName, Vec<OpInfo>>; + +pub fn generate_bounded_ops() -> BoundedOps { + let mut ops = BoundedOps::new(); + let push = ("push".to_string(), Type::Fun( + Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Stack".to_string()])))), + Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Stack".to_string()])))))))); + let pop = ("pop".to_string(), Type::Fun(Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Stack".to_string()])))), Box::new(Type::Var(TypeVar::new("T".to_string()))))); + ops.insert("Stack".to_string(), vec![push, pop]); + ops + +}
\ No newline at end of file diff --git a/primrose/src/description.rs b/primrose/src/description.rs new file mode 100644 index 0000000..9adf0be --- /dev/null +++ b/primrose/src/description.rs @@ -0,0 +1,95 @@ +use std::collections::HashMap; +use std::collections::hash_map::Iter; + +use crate::parser::{Id}; + +pub type Description = String; +type ElemTypeName = String; +type ConName = String; +type BoundName = String; + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum Tag { + Prop(Box<Description>), // analysis of a property + Bound((ConName, ElemTypeName), Box<Vec<Description>>), + Con(ElemTypeName, BoundName, Box<Vec<Tag>>) // analysis of a container type with refinements +} + +impl Tag { + pub fn is_prop_tag(&self) -> bool { + match self { + Tag::Prop(_) => true, + _ => false + } + } + + pub fn is_bound_tag(&self) -> bool { + match self { + Tag::Bound(_, _) => true, + _ => false + } + } + + pub fn is_con_tag(&self) -> bool { + match self { + Tag::Con(..) => true, + _ => false + } + } + + pub fn extract_prop_desc(&self) -> Description { + match self { + Tag::Prop(desc) => desc.to_string(), + _ => String::new() + } + } + + pub fn extract_bound_descs(&self) -> Vec<Description> { + match self { + Tag::Bound(_, descs) => descs.to_vec(), + _ => Vec::new() + } + } +} + +#[derive(Clone, Debug)] +pub struct InforMap { + infor_map: HashMap<Id, Tag>, +} + +impl InforMap { + pub fn new() -> InforMap { + InforMap { + infor_map: HashMap::new(), + } + } + + pub fn put(&mut self, id: Id, tag: Tag) -> bool { + if self.infor_map.contains_key(&id) { + false + } else { + self.infor_map.insert(id, tag); + true + } + } + + pub fn update(&mut self, id: Id, tag: Tag) { + self.infor_map.insert(id, tag); + } + + pub fn get_id(&self, id: Id) -> Option<&Tag> { + self.infor_map.get(&id) + } + + pub fn iter(&self) -> Iter<'_, Id, Tag> { + self.infor_map.iter() + } + + pub fn contains(&self, id: &Id) -> bool { + self.infor_map.contains_key(id) + } + + fn sz(&self) -> usize { + self.infor_map.len() + } +}
\ No newline at end of file diff --git a/primrose/src/generator.rs b/primrose/src/generator.rs new file mode 100644 index 0000000..aa18b6d --- /dev/null +++ b/primrose/src/generator.rs @@ -0,0 +1,378 @@ +use std::process::Command; +use std::env; +use std::fs; +use std::io::{Write, BufReader, BufRead, Error, ErrorKind}; +use indicatif::{ProgressBar, ProgressStyle}; + +use crate::parser::{Block, Spec, spec}; +use crate::type_check::{TypeChecker}; + +use crate::analysis::{Analyser}; +use crate::description::{Tag, Description, InforMap}; +use crate::lib_spec_processor::{process_lib_specs}; +use crate::spec_map::{PropSpecs, MatchSetup, ProvidedOps}; +use crate::run_matching::{LANGDECL, initialise_match_setup, gen_match_script, run_matching, cleanup_script, setup_dirs}; + +const CODEGEN: &str = "/*CODEGEN*/\n"; +const CODEGENEND: &str = "/*ENDCODEGEN*/\n"; + +const CODE: &str = "/*CODE*/"; +const CODEEND: &str = "/*ENDCODE*/"; + +const SPEC: &str = "/*SPEC*"; +const SPECEND: &str = "*ENDSPEC*/"; + +const LIB: &str = "./src/library/"; +const MATCHSCRIPT: &str = "./racket_specs/gen_match/match-script.rkt"; + +const IMPORT: &str = "use primrose::traits::container_constructor::ContainerConstructor;\n"; +const TRAITCRATE: &str = "primrose::traits::"; + +const OPS: &str = "./racket_specs/gen_lib_spec/ops.rkt"; + +type ErrorMessage = String; + +pub fn readfile(filename : String) -> String { + let contents = fs::read_to_string(filename) + .expect("Something went wrong reading the file"); + mark_src_blocks(contents) +} + +pub fn writefile(pathname: String, filename: String, contents: String) -> Result<(), Error> { + // Create the directory if it does not exist + Command::new("sh") + .arg("-c") + .arg("mkdir -p ".to_owned() + "./gen_code/" + &pathname) + .output() + .expect("Fail to create the library specification directory"); + + let path = "./gen_code/".to_string() + &pathname + "/"; + + let mut output = fs::File::create(path.to_owned() + &filename)?; + write!(output, "{}", contents)?; + + Ok(()) +} + +pub fn process_block(block: &Block) -> String { + match block { + Block::SpecBlock(_, _) => { + String::new() + }, + Block::CodeBlock(code, n) => { + code.to_string() + } + } +} + +fn process_bound_elem_ty(t: &str, elem_ty: &str) -> String { + return TRAITCRATE.to_string() + t + "<" + elem_ty + ">"; +} + +pub fn process_bound_decl(ctx: &InforMap) -> Result<String, ErrorMessage> { + let mut code = String::new(); + let match_setup = initialise_match_setup(); + for (id, tag) in ctx.iter() { + match tag { + Tag::Bound((c, t), decs) => { + let traits = decs.iter().map(|name| process_bound_elem_ty(name, t)).collect::<Vec<String>>().join(" + "); + code = code + &gen_trait_code(id, c, t, &traits); + }, + _ => continue + } + } + Ok(code) +} + +// Generate the code ro replace the container delcaration in property specification +pub fn process_con_decl(ctx: &InforMap, prop_specs: &PropSpecs) -> Result<Vec<String>, ErrorMessage> { + let mut code = String::new(); + // initialise a vector of generated container code + let mut gen_con_code: Vec<String> = Vec::new(); + let match_setup = initialise_match_setup(); + let cons = ctx.iter().filter(|(_, tag)| tag.is_con_tag()).collect::<Vec<(&String, &Tag)>>(); + if cons.len() > 1 { + for (id, tag) in ctx.iter() { + match tag { + Tag::Con(elem_ty, i_name, tags) => { + let prop_descs: Vec<Description> = + tags.iter() + .filter(| t | t.is_prop_tag()) + .map(| t | t.extract_prop_desc()) + .collect(); + let bounds: Vec<Description> = + tags.iter() + .filter(| t | t.is_bound_tag()) + .flat_map(| t | t.extract_bound_descs()) + .collect(); + let lookup_result = library_spec_lookup(id.to_string(), prop_descs, bounds, prop_specs, &match_setup); + match lookup_result { + Ok(struct_choices) => { + if struct_choices.is_empty() { + return Err("Unable to find a struct which matches the specification in the library".to_string()); + } else { + let opt = struct_choices.join(", "); + code = code + &gen_output_code(id, elem_ty, &struct_choices[0], i_name, &opt); + } + }, + Err(e) => { + return Err(e); + } + } + }, + _ => continue + } + } + gen_con_code.push(code); + Ok(gen_con_code) + } else { + for (id, tag) in ctx.iter() { + match tag { + Tag::Con(elem_ty, i_name, tags) => { + let prop_descs: Vec<Description> = + tags.iter() + .filter(| t | t.is_prop_tag()) + .map(| t | t.extract_prop_desc()) + .collect(); + let bounds: Vec<Description> = + tags.iter() + .filter(| t | t.is_bound_tag()) + .flat_map(| t | t.extract_bound_descs()) + .collect(); + let lookup_result = library_spec_lookup(id.to_string(), prop_descs, bounds, prop_specs, &match_setup); + match lookup_result { + Ok(struct_choices) => { + if struct_choices.is_empty() { + return Err("Unable to find a struct which matches the specification in the library".to_string()); + } else { + let opt = struct_choices.join(", "); + for struct_choice in struct_choices { + gen_con_code.push(gen_output_code(id, elem_ty, &struct_choice, i_name, &opt)); + } + } + }, + Err(e) => { + return Err(e); + } + } + }, + _ => continue + } + } + Ok(gen_con_code) + } +} + +fn write_provided_ops(provided_ops: &ProvidedOps) -> Result<(), Error> { + let ops_path = OPS; + let (code, ops) = provided_ops; + let mut output = fs::File::create(ops_path.to_owned())?; + write!(output, "{}", LANGDECL.to_string())?; + for i in 0..code.len() { + write!(output, "{}", code[i])?; + } + let ops_string = ops.join(" "); + let provide = "\n(provide ".to_string() + &ops_string + ")"; + write!(output, "{}", provide)?; + Ok(()) +} + +fn library_spec_lookup(id: String, properties: Vec<Description>, bounds: Vec<Description>, prop_specs: &PropSpecs, match_setup: &MatchSetup) -> Result<Vec<String>, ErrorMessage> { + let pb = ProgressBar::new_spinner(); + pb.enable_steady_tick(200); + pb.set_style( + ProgressStyle::default_spinner() + .tick_strings(&[ + "(>'-')>", + " ('-') ", + "<('-'<)", + " ('-') ", + "^('-')^", + " ('-') ", + "v('-')v", + " ('-') ", + " (^-^) ", + ]) + .template("{spinner:.magenta} {msg}"), + ); + pb.set_message("Finding library implementations for ".to_owned() + &id + "..."); + let lib_spec = process_lib_specs(LIB.to_string()).expect("Error: Unable to process library files"); // The specifications of library structs + let mut structs = Vec::new(); + // select library structs implement bounds decl in contype + let mut lib_spec_impls = lib_spec.clone(); + for (name, (_, impls, _)) in lib_spec.iter() { + if (!bounds.iter().all(|i| impls.keys().cloned().collect::<String>().contains(i))) { + lib_spec_impls.remove(name); + } + } + for (name, (lib_spec_dir, bound_ctx, provided_ops)) in lib_spec_impls.iter() { + match write_provided_ops(provided_ops) { + Ok(_) => { }, + Err(_) => { + return Err("Error, cannot obtain provided operations from the library specifiction".to_string()); + } + } + let mut is_match = false; + for p in &properties { + let mut is_partial_match = false; + for i in &bounds { + let (prop_file, symbolics) = prop_specs.get(p).expect(&("Error: No property specification found for: ".to_string() + &p)); + match gen_match_script(p.to_string(), match_setup.get(i).unwrap().to_string(), prop_file.to_string(), lib_spec_dir.to_string(), bound_ctx.get(i).unwrap().to_string(), symbolics) { + Ok(_) => { + let result = run_matching(MATCHSCRIPT.to_string()); + match result { + Ok(r) => { // true - match; false - not match + if (r) { + is_partial_match = true; + } else { + is_partial_match = false; + break; + } + }, + Err(e) => { + return Err(e); + } + } + }, + Err(e) => { + return Err(e.to_string()); + } + } + } + is_match = is_partial_match; + if (!is_match) { + break; + } + } + if (is_match) { + structs.push(name.to_string()); + } + } + pb.finish_with_message("Done. ".to_owned() + &structs.len().to_string() + " implementation(s) for " + &id + " found."); + cleanup_script(); + Ok(structs) +} + +pub fn process_src(filename : String, model_size: usize) -> Result<Vec<String>, ErrorMessage> { + setup_dirs(); + println!("{}", "Ready..."); + let f = readfile(filename); + match spec::prog(&f) { + Ok(blocks) => { + let mut tc = TypeChecker::new(); + match tc.check_prog(blocks.clone()) {// type checking + Ok(_) => { + // type checking ok + // run analyser + let mut analyser = Analyser::new(); + match analyser.analyse_prog(blocks.clone(), model_size) { + Ok(_) => { + let mut gen_code = Vec::new(); + // let mut result = String::new(); + // generate con types according to the information in con decl + match process_bound_decl(analyser.get_ctx()) { + Ok(code) => { + let bound = code.clone(); + match process_con_decl(analyser.get_ctx(), analyser.get_prop_specs()) { + Ok(gen_con_code) => { + for code in gen_con_code.iter() { + let mut result = bound.clone(); + result = CODEGEN.to_string() + IMPORT + &result + &code + CODEGENEND; + // generate rust source code + let code_blocks: Vec<&Block> = + blocks.iter() + .filter(| block | block.is_code_block()) + .collect(); + for block in code_blocks.iter() { + result = result + &process_block(block.to_owned()); + } + gen_code.push(result); + } + Ok(gen_code) + }, + Err(e) => Err(e) + } + }, + Err(e) => Err(e) + } + }, + Err(e) => Err(e) + } + }, + Err(e) => Err(e) + } + }, + _ => Err("Error, invalid source code.".to_string()) + } +} + +pub fn run(input: String, output_path: String, model_size: usize) -> Result<(), Error> { + match process_src(input, model_size) { + Ok(gen_code) => { + let mut i = 0; + while i < gen_code.len() { + let code = gen_code[i].clone(); + let output_file = output_path.clone() + &i.to_string() + ".rs"; + writefile(output_path.clone(), output_file, code); + i = i + 1; + } + Ok(()) + }, + Err(e) => Err(Error::new(ErrorKind::Other, e.to_string())) + } +} + +fn mark_src_blocks(src : String) -> String { + let mut trimed_src = src.trim(); + let mut result = String::new(); + while trimed_src.len() > 0 { + match trimed_src.find(SPEC) { + Some(n) => { + match trimed_src.find(SPECEND) { + Some(m) => { + if (n > 0) { + let code = &trimed_src[..n]; + result = result + CODE + &code + CODEEND; + } + let spec = &trimed_src[n..(m+SPECEND.len())]; + trimed_src = &trimed_src[(m+SPECEND.len())..].trim(); + result = result + &spec; + }, + None => { + result = result + CODE + trimed_src + CODEEND; + break; + } + } + }, + None => { + result = result + CODE + trimed_src + CODEEND; + break; + } + } + } + result +} + +pub fn gen_output_code(s: &str, elem_type: &str, chosen: &str, trait_name: &str, choices: &str) -> String { + format!( +r#"struct {s}<{elem_type}> {{ + elem_t: core::marker::PhantomData<{elem_type}>, +}} + +impl<{elem_type}: 'static + Ord + std::hash::Hash> ContainerConstructor for {s}<{elem_type}> {{ + type Impl = {chosen}<{elem_type}>; // All possible choices: {choices} + type Bound = dyn {trait_name}<{elem_type}>; + fn new() -> Box<Self::Bound> {{ + Box::new(Self::Impl::new()) + }} +}} +"#) +} + +pub fn gen_trait_code(trait_name: &str, s: &str, elem_type: &str, traits: &str) -> String { + format!( +r#" +trait {trait_name}<{elem_type}> : {traits} {{}} +impl<{elem_type}: 'static + Ord + std::hash::Hash> {trait_name}<{elem_type}> for <{s}<{elem_type}> as ContainerConstructor>::Impl {{}} +"#) +}
\ No newline at end of file diff --git a/primrose/src/inference.rs b/primrose/src/inference.rs new file mode 100644 index 0000000..6d55b09 --- /dev/null +++ b/primrose/src/inference.rs @@ -0,0 +1,120 @@ +use std::collections::{HashSet, HashMap}; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; +use std::fmt; +use std::result; + +use crate::parser::{Id, Term}; +use crate::types::{Name, Type, TypeVar, Types, TypeVarGen, Subst, TypeScheme}; +use crate::bounded_ops::{BoundedOps, generate_bounded_ops}; + +/// A type environment +#[derive(Clone, Debug)] +pub struct TypeEnv(HashMap<Id, TypeScheme>); + +impl Deref for TypeEnv { + type Target = HashMap<Id, TypeScheme>; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for TypeEnv { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Types for TypeEnv { + fn ftv(&self) -> HashSet<TypeVar> { + self.values().map(|x| x.clone()).collect::<Vec<TypeScheme>>().ftv() + } + + fn apply(&self, s: &Subst) -> TypeEnv { + TypeEnv(self.iter() + .map(|(k, v)| (k.clone(), v.apply(s))) + .collect()) + } +} + +impl TypeEnv { + pub fn new() -> TypeEnv { + TypeEnv(HashMap::new()) + } + + fn generalise(&self, ty: &Type) -> TypeScheme { + TypeScheme { + vars: ty.ftv().difference(&self.ftv()).cloned().collect(), + ty: ty.clone(), + } + } + + // Main type inference algorithm + fn ti(&self, term: &Term, tvg: &mut TypeVarGen) -> Result<(Subst, Type), InferenceError> { + // Get types of operations defined in traits + let bounded_ops = generate_bounded_ops(); + let (s, t) = (match term { + // Infer literal: currently only boolean + Term::LitTerm(_) => { + Ok((Subst::new(), Type::Bool())) + } + // Infer variable + Term::VarTerm(v) => { + match self.get(&v.to_string()) { + Some(s) => Ok((Subst::new(), s.instantiate(tvg))), + None => Err("unbound variable".to_string() + " " + &v.to_string()), + } + } + // Infer abstraction + Term::LambdaTerm((n, bounds), ref e) => { + let mut tv = Type::Var(tvg.next()); + let mut env = self.clone(); + if (!bounds.is_empty()) { + tv = Type::Con(Box::new("Con".to_string()), Box::new(tv), bounds.clone()); + for b in bounds.iter() { + if bounded_ops.contains_key(b) { + let ops_info = bounded_ops.get(b).unwrap(); + for (op_name, op_ty) in ops_info { + env.insert( + op_name.to_string(), + TypeScheme { + vars: Vec::new(), + ty: op_ty.clone(), + } + ); + } + } + } + } + env.remove(&n.to_string()); + + env.insert( + n.to_string(), + TypeScheme { + vars: Vec::new(), + ty: tv.clone(), + } + ); + let (s1, t1) = env.ti(e, tvg)?; + let result_ty = Type::Fun(Box::new(tv.apply(&s1)), Box::new(t1)); + Ok((s1.clone(), result_ty)) + } + // Infer application + Term::AppTerm(ref e1, ref e2) => { + let (s1, t1) = self.ti(e1, tvg)?; + let (s2, t2) = self.apply(&s1).ti(e2, tvg)?; + let tv = Type::Var(tvg.next()); + let s3 = t1.apply(&s2).mgu(&Type::Fun(Box::new(t2), Box::new(tv.clone())))?; + Ok((s3.compose(&s2.compose(&s1)), tv.apply(&s3))) + } + })?; + Ok((s, t)) + } + + // perform type inference on term + pub fn type_inference(&self, term: &Term, tvg: &mut TypeVarGen) -> Result<Type, InferenceError> { + let (s, t) = self.ti(term, tvg)?; + Ok(t.apply(&s)) + } +} + +pub type InferenceError = String;
\ No newline at end of file diff --git a/primrose/src/lib.rs b/primrose/src/lib.rs new file mode 100644 index 0000000..5286b39 --- /dev/null +++ b/primrose/src/lib.rs @@ -0,0 +1,20 @@ +#![allow(unused)] +#![feature(linked_list_cursors)] + +pub mod parser; +pub mod generator; +pub mod type_check; +pub mod description; +pub mod types; +pub mod analysis; +pub mod inference; +pub mod lib_spec_processor; +pub mod spec_map; +pub mod run_matching; +pub mod bounded_ops; + +pub mod library; +pub mod traits; + +pub mod proptest; +pub mod tools;
\ No newline at end of file diff --git a/primrose/src/lib_spec_processor.rs b/primrose/src/lib_spec_processor.rs new file mode 100644 index 0000000..30df5f1 --- /dev/null +++ b/primrose/src/lib_spec_processor.rs @@ -0,0 +1,227 @@ +use std::env; +use std::fs; +use std::io::{Write, BufReader, BufRead, Error, ErrorKind}; +use std::collections::BTreeMap; +use std::collections::btree_map::Iter; +//use std::collections::hash_map::Iter; +use std::collections::HashMap; + +use crate::spec_map::{LibSpecs, Bounds, ProvidedOps}; + +const LIBSPECNAME: &str = "/*LIBSPEC-NAME*"; +const LIBSPECNAMEEND: &str = "*ENDLIBSPEC-NAME*/"; +const LIBSPEC: &str = "/*LIBSPEC*"; +const LIBSPECEND: &str = "*ENDLIBSPEC*/"; +const LANGDECL: &str = "#lang rosette\n"; +const GENPATH: &str = "./racket_specs/gen_lib_spec/"; +const OPNAME: &str = "/*OPNAME*"; +const OPNAMEEND: &str = "*ENDOPNAME*/"; +const IMPL: &str = "/*IMPL*"; +const IMPLEND: &str = "*ENDIMPL*/"; + +type ErrorMessage = String; + +fn is_next_pragma_impl(src: &String) -> bool { + match src.find(IMPL) { + Some(impl_pos) => { + match src.find(LIBSPEC) { + Some(spec_pos) => { + impl_pos < spec_pos + }, + None => true + } + }, + None => false + } +} + +fn has_pragma_impl(src: &String) -> bool { + src.contains(IMPL) +} + +fn has_pragma_spec(src: &String) -> bool { + src.contains(LIBSPEC) +} + +pub fn read_lib_file(filename : String) -> Result<(String, String, Vec<String>, String, Bounds, ProvidedOps), ErrorMessage> { + let contents = fs::read_to_string(filename) + .expect("Something went wrong reading the file"); + let trimed_contents = contents.trim().to_string(); + + let name_pragmas: Vec<&str> = trimed_contents.matches(LIBSPECNAME).collect(); + let name_end_pragmas: Vec<&str> = trimed_contents.matches(LIBSPECNAMEEND).collect(); + let spec_pragmas: Vec<&str> = trimed_contents.matches(LIBSPEC).collect(); + let spec_end_pragmas : Vec<&str> = trimed_contents.matches(LIBSPECEND).collect(); + if ((name_pragmas.len() != 1) || (name_end_pragmas.len() != 1)) { + return Err("Error, invalid declaration of library specification name**.".to_string()); + } else if (spec_pragmas.len() != spec_end_pragmas.len()) { + return Err("Error, invalid declaration of library specification.".to_string()); + } else { + let mut specs = String::new(); + let v1: Vec<&str> = trimed_contents.split(LIBSPECNAME).collect(); + let s = v1.get(1).expect("Error, invalid declaration of library specification name."); + let v2: Vec<&str> = s.split(LIBSPECNAMEEND).collect(); + let s3 = v2.get(0).unwrap().trim().to_string(); + let v3: Vec<&str> = s3.split(" ").collect(); + let spec_name = v3.get(0).unwrap().trim().to_string(); + let struct_name = v3.get(1).unwrap().trim().to_string(); + let s1 = v1.get(0).expect("Unexpected error."); + let s2 = v2.get(1).expect("Unexpected error."); + // process interface blocks + let mut trimed_contents = String::new(); + trimed_contents.push_str(&s1); + trimed_contents.push_str(&s2); + if (!is_next_pragma_impl(&trimed_contents) && has_pragma_spec(&trimed_contents)) { + return Err("Specification without declared interface is not allowed".to_string()); + } else { + let mut interfaces = Vec::<String>::new(); + let mut interface_info = HashMap::<String, BTreeMap<String, (String, String, String)>>::new(); + let mut code = Vec::<String>::new(); + let mut provided_ops = Vec::<String>::new(); + while (has_pragma_impl(&trimed_contents)) { + let v4: Vec<&str> = trimed_contents.splitn(2, IMPL).collect(); + let s4 = v4.get(1).expect("Error, invalid interface declaration."); + let v5: Vec<&str> = s4.splitn(2, IMPLEND).collect(); + let mut interface_name = v5.get(0).unwrap().trim().to_string(); + trimed_contents = v5.get(1).unwrap().trim().to_string(); + let lib_specs = extract_lib_specs(trimed_contents); + match lib_specs { + Ok((rest, mut v, infos, mut ops)) => { + code.append(&mut v); + interface_info.insert(interface_name.clone(), infos); + interfaces.push(interface_name.clone()); + provided_ops.append(&mut ops); + trimed_contents = rest; + }, + Err(e) => { + return Err(e); + } + } + } + let (provide, interface_provide_map) = generate_provide(interface_info); + Ok((spec_name, struct_name, code.clone(), provide, interface_provide_map, (code, provided_ops))) + } + } +} + +pub fn generate_provide(interface_info: HashMap<String, BTreeMap<String, (String, String, String)>>) -> (String, Bounds) { + let mut interfaces = Vec::<String>::new(); + let mut provide = String::new(); + let mut interface_provide_map = Bounds::new(); + for (interface, infos) in interface_info.iter() { + let mut specs = Vec::<String>::new(); + let mut pres = Vec::<String>::new(); + for (key, value) in infos.iter() { + specs.push(value.0.clone()); + pres.push(value.1.clone()); + } + let specs_name = interface.to_lowercase() + "-specs"; + let pres_name = interface.to_lowercase() + "-pres"; + let interface_name = interface.to_lowercase(); + let specs_str = "\n(define ".to_string() + &specs_name + " (list " + &specs.join(" ") + "))\n"; + let pres_str = "(define ".to_string() + &pres_name + " (list " + &pres.join(" ") + "))\n"; + let interface_str = "(define ".to_string() + &interface_name + " (cons " + &specs_name + " " + &pres_name + "))\n"; + provide = provide + &specs_str + &pres_str + &interface_str; + interfaces.push(interface.to_lowercase()); + interface_provide_map.insert(interface.to_string(), interface_name); + } + let provide_str = "(provide ".to_string() + &interfaces.join(" ") + ")"; + + provide = provide + &provide_str; + (provide, interface_provide_map) +} + +pub fn extract_lib_specs(src: String) -> Result<(String, Vec<String>, BTreeMap<String, (String, String, String)>, Vec<String>), ErrorMessage> { + let mut result = Vec::<String>::new(); + let mut contents = src.trim(); + let mut op_infos = BTreeMap::<String, (String, String, String)>::new(); + let mut provided_ops = Vec::<String>::new(); + while (contents.len() > 0 && !is_next_pragma_impl(&contents.to_string())) { + if (contents.contains(LIBSPEC) && contents.contains(LIBSPECEND)) { + let v1: Vec<&str> = contents.splitn(2, LIBSPEC).collect(); + let s = v1.get(1).expect("Error, invalid specification."); + let v2: Vec<&str> = s.splitn(2, LIBSPECEND).collect(); + let spec = v2.get(0).unwrap().trim().to_string(); + let info = extract_op_info(spec.clone()).unwrap(); + op_infos.insert(info.0, (info.1.clone(), info.2, info.3)); + provided_ops.push(info.1); + let v3: Vec<&str> = spec.splitn(2, OPNAMEEND).collect(); + let code = v3.get(1).unwrap().trim_matches(|c| c == '\t' || c == ' ').to_string(); + result.push(code); + contents = v2.get(1).unwrap().trim(); + } else { + break; + } + } + Ok((contents.to_string(), result, op_infos, provided_ops)) +} + +pub fn extract_op_info(spec: String) -> Result<(String, String, String, String), ErrorMessage> { + let op_name_pragmas: Vec<&str> = spec.matches(OPNAME).collect(); + let op_name_end_pragmas : Vec<&str> = spec.matches(OPNAMEEND).collect(); + if (spec.chars().nth(0).unwrap() == '/' && op_name_pragmas.len()==1 && op_name_end_pragmas.len() == 1) { + let v1: Vec<&str> = spec.split(OPNAME).collect(); + let s = v1.get(1).expect("Error, invaild operation information declaration."); + let v2: Vec<&str> = s.split(OPNAMEEND).collect(); + let info_string = v2.get(0).expect("Error, invaild operation information declaration."); + let mut infos: Vec<&str> = info_string.trim().split(" ").collect(); + if (infos.len() == 4) { + let post = infos.pop().unwrap(); + let pre = infos.pop().unwrap(); + let op_spec = infos.pop().unwrap(); + let name = infos.pop().unwrap(); + Ok((name.to_string(), op_spec.to_string(), pre.to_string(), post.to_string())) + } else { + Err("Error, invaild operation information declaration.".to_string()) + } + } else { + Err("Error, invaild operation information declaration.".to_string()) + } +} + +pub fn write_lib_file(filename : String, contents: Vec<String>, provide: String) -> Result<(), Error> { + let path = GENPATH; + + let mut output = fs::File::create(path.to_owned() + &filename)?; + write!(output, "{}", LANGDECL.to_string())?; + for i in 0..contents.len() { + write!(output, "{}", contents[i])?; + } + write!(output, "{}", provide)?; + Ok(()) +} + +pub fn process_lib_spec(filename: String) -> Result<(String, String, Bounds, ProvidedOps), ErrorMessage> { + let result = read_lib_file(filename); + match result { + Ok((name, struct_name, specs, provide, interface_provide_map, provided_ops)) => { + let spec_name = name + ".rkt"; + let state = write_lib_file(spec_name.clone(), specs, provide); + if (!state.is_ok()) { + return Err("Unable to create lib specification file".to_string()); + } + Ok((spec_name, struct_name, interface_provide_map, provided_ops)) + }, + Err(e) => Err(e) + } +} + +pub fn process_lib_specs(dirname: String) -> Result<LibSpecs, ErrorMessage> { + let paths = fs::read_dir(dirname).unwrap(); + let files : Vec<String> = paths.into_iter() + .map(|path| path.unwrap().path().to_str().unwrap().to_string()) + .filter(|path| !path.contains("/mod.rs")) + .collect(); + let mut lib_specs = LibSpecs::new(); + for path in files { + match process_lib_spec(path) { + Ok((spec_name, struct_name, interface_provide_map, provided_ops)) => { + lib_specs.insert(struct_name, (spec_name, interface_provide_map, provided_ops)); + }, + Err(e) => { + return Err(e); + } + } + } + Ok(lib_specs) +}
\ No newline at end of file diff --git a/primrose/src/library/eager_sorted_vector.rs b/primrose/src/library/eager_sorted_vector.rs new file mode 100644 index 0000000..403a9b4 --- /dev/null +++ b/primrose/src/library/eager_sorted_vector.rs @@ -0,0 +1,337 @@ +/*LIBSPEC-NAME* +rust-eager-sorted-vec-spec primrose::library::eager_sorted_vector::EagerSortedVec +*ENDLIBSPEC-NAME*/ + +use std::vec::Vec; +use std::slice::Iter; +use std::ops::Deref; +use crate::traits::{Container, Stack, Indexable}; +use std::iter::FromIterator; + +use proptest::prelude::*; +use crate::proptest::strategies::{eager_sorted_vec}; +use crate::proptest::*; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + + +// A Sorted Vector +#[derive(Debug, Clone)] +pub struct EagerSortedVec<T> { + v: Vec<T>, +} + +impl<T: Ord> EagerSortedVec<T> { + pub fn from_vec(mut v: Vec<T>) -> EagerSortedVec<T> { + v.sort(); + EagerSortedVec{ v: v } + } + + pub fn new() -> EagerSortedVec<T> { + EagerSortedVec { v: Vec::new() } + } + + pub fn len(&mut self) -> usize { + self.v.len() + } + + pub fn contains(&mut self, x: &T) -> bool { + match self.v.binary_search(x) { + Ok(_) => true, + Err(_) => false, + } + } + + pub fn is_empty(&mut self) -> bool { + self.len() == 0 + } + + pub fn push(&mut self, value: T) { + let index = self.v.binary_search(&value).unwrap_or_else(|i| i); + self.v.insert(index, value); + } + + pub fn pop(&mut self) -> Option<T> { + self.v.pop() + } + + pub fn remove(&mut self, index: usize) -> T { + self.v.remove(index) + } + + pub fn clear(&mut self) { + self.v.clear() + } + + pub fn first(&mut self) -> Option<&T> { + self.v.first() + } + + pub fn last(&mut self) -> Option<&T> { + self.v.last() + } + + pub fn iter(&mut self) -> Iter<'_, T> { + self.v.iter() + } + + pub fn to_vec(self) -> Vec<T> { + self.v + } +} + + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord> Container<T> for EagerSortedVec<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (sort xs <))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + EagerSortedVec::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (sort xs <))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + EagerSortedVec::contains(self, x) + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (sort xs <))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + EagerSortedVec::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (sort xs <))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + EagerSortedVec::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (sort (append xs (list x)) <)) + (define (pre-insert xs) (equal? xs (sort xs <))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + EagerSortedVec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (sort xs <))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match self.iter().position(|x| *x == elt) { + Some(index) => { + Some(self.remove(index)) + }, + None => None + } + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T: Ord> Indexable<T> for EagerSortedVec<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) (equal? xs (sort xs <))) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + EagerSortedVec::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) (equal? xs (sort xs <))) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + EagerSortedVec::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) (equal? xs (sort xs <))) + (define (post-nth xs n r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + EagerSortedVec::iter(self).nth(n) + } +} + +fn abstraction<T>(v: EagerSortedVec<T>) -> ConsList<T> +where T: Ord +{ + let list: ConsList<T> = ConsList::from(v.to_vec()); + list +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_eager_sorted_vec_len(ref mut v in eager_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::len(v), abs_list.len()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_sorted_vec_contains(ref mut v in eager_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::contains(v, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_sorted_vec_is_empty(ref mut v in eager_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::is_empty(v), abs_list.is_empty()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_sorted_vec_insert(ref mut v in eager_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let after_list = abs_list.append(conslist![a.clone()]).sort(); + Container::<String>::insert(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_eager_sorted_vec_clear(ref mut v in eager_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let after_list = clear(&abs_list); + Container::<String>::clear(v); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_eager_sorted_vec_remove(ref mut v in eager_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_eager_sorted_vec_first(ref mut v in eager_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::first(v); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_sorted_vec_last(ref mut v in eager_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::last(v); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_sorted_vec_nth(ref mut v in eager_sorted_vec(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::nth(v, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(v.clone()), abs_list); + } +}
\ No newline at end of file diff --git a/primrose/src/library/eager_unique_vector.rs b/primrose/src/library/eager_unique_vector.rs new file mode 100644 index 0000000..0f4f389 --- /dev/null +++ b/primrose/src/library/eager_unique_vector.rs @@ -0,0 +1,339 @@ +/*LIBSPEC-NAME* +rust-eager-unique-vec-spec primrose::library::eager_unique_vector::EagerUniqueVec +*ENDLIBSPEC-NAME*/ + +use std::vec::Vec; +use std::slice::Iter; +use std::ops::Deref; +use crate::traits::{Container, Stack, Indexable}; +use std::iter::FromIterator; + +use proptest::prelude::*; +use crate::proptest::strategies::{eager_unique_vec}; +use crate::proptest::*; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +// A Unique Vector +#[derive(Debug, Clone)] +pub struct EagerUniqueVec<T> { + v: Vec<T>, +} + +impl<T: PartialEq> EagerUniqueVec<T> { + pub fn from_vec(v: Vec<T>) -> EagerUniqueVec<T> { + let mut vec = Vec::<T>::new(); + for i in v { + if !vec.contains(&i) { + vec.push(i); + } + } + EagerUniqueVec{ v: vec } + } + + pub fn new() -> EagerUniqueVec<T> { + EagerUniqueVec { v: Vec::new() } + } + + pub fn len(&mut self) -> usize { + self.v.len() + } + + pub fn contains(&mut self, x: &T) -> bool { + self.v.contains(x) + } + + pub fn is_empty(&mut self) -> bool { + self.len() == 0 + } + + // Duplicated elements will be discarded + pub fn push(&mut self, value: T) { + if !self.contains(&value) { + self.v.push(value); + } + } + + pub fn pop(&mut self) -> Option<T> { + self.v.pop() + } + + pub fn remove(&mut self, index: usize) -> T { + self.v.remove(index) + } + + pub fn clear(&mut self) { + self.v.clear() + } + + pub fn first(&mut self) -> Option<&T> { + self.v.first() + } + + pub fn last(&mut self) -> Option<&T> { + self.v.last() + } + + pub fn iter(&mut self) -> Iter<'_, T> { + self.v.iter() + } + + pub fn to_vec(self) -> Vec<T> { + self.v + } +} + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: PartialEq> Container<T> for EagerUniqueVec<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (remove-duplicates xs))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + EagerUniqueVec::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (remove-duplicates xs))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + EagerUniqueVec::contains(self, x) // use fully qualified syntax to avoid function name collision + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (remove-duplicates xs))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + EagerUniqueVec::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (remove-duplicates xs))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + EagerUniqueVec::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (remove-duplicates (append xs (list x)))) + (define (pre-insert xs) (equal? xs (remove-duplicates xs))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + EagerUniqueVec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (remove-duplicates xs))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match self.iter().position(|x| *x == elt) { + Some(index) => { + Some(self.remove(index)) + }, + None => None + } + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T: PartialEq> Indexable<T> for EagerUniqueVec<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) #t) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + EagerUniqueVec::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) #t) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + EagerUniqueVec::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) #t) + (define (post-nth xs n r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + EagerUniqueVec::iter(self).nth(n) + } +} + +fn abstraction<T>(v: EagerUniqueVec<T>) -> ConsList<T> +where T: PartialEq +{ + let list: ConsList<T> = ConsList::from(v.to_vec()); + list +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_eager_unique_vec_len(ref mut v in eager_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + assert_eq!(Container::<String>::len(v), abs_list.len()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_unique_vec_contains(ref mut v in eager_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + assert_eq!(Container::<String>::contains(v, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_unique_vec_is_empty(ref mut v in eager_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + assert_eq!(Container::<String>::is_empty(v), abs_list.is_empty()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_unique_vec_insert(ref mut v in eager_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let after_list = unique(&abs_list.append(conslist![a.clone()])); + Container::<String>::insert(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_eager_unique_vec_clear(ref mut v in eager_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let after_list = clear(&abs_list); + Container::<String>::clear(v); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_eager_unique_vec_remove(ref mut v in eager_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_eager_unique_vec_first(ref mut v in eager_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let elem = Indexable::<String>::first(v); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_unique_vec_last(ref mut v in eager_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let elem = Indexable::<String>::last(v); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_eager_unique_vec_nth(ref mut v in eager_unique_vec(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list)); + //post + let elem = Indexable::<String>::nth(v, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(v.clone()), abs_list); + } +}
\ No newline at end of file diff --git a/primrose/src/library/hashset.rs b/primrose/src/library/hashset.rs new file mode 100644 index 0000000..a41b2e6 --- /dev/null +++ b/primrose/src/library/hashset.rs @@ -0,0 +1,199 @@ +/*LIBSPEC-NAME* +rust-hashset-spec std::collections::HashSet +*ENDLIBSPEC-NAME*/ + +use std::collections::HashSet; +use std::hash::Hash; +use crate::traits::Container; + +use crate::proptest::*; +use proptest::prelude::*; +use proptest::collection::hash_set; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; +use std::iter::FromIterator; + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord + Hash> Container<T> for HashSet<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + HashSet::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + HashSet::contains(self, x) + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + HashSet::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + HashSet::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (remove-duplicates (sort (append xs (list x)) <))) + (define (pre-insert xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + HashSet::insert(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match HashSet::remove(self, &elt) { + true => Some(elt), + false => None + } + } +} + +fn abstraction<T: Ord>(h: HashSet<T>) -> ConsList<T> { + let list: ConsList<T> = ConsList::from_iter(h); + list.sort() +} + +proptest!{ + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_hashset_len(ref mut h in hash_set(".*", 0..100)) { + let abs_list = abstraction(h.clone()); + // pre: our list model is a sorted and unique list + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::len(h), abs_list.len()); + assert_eq!(abstraction(h.clone()), abs_list); + } + + #[test] + fn test_hashset_contains(ref mut h in hash_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(h.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::contains(h, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(h.clone()), abs_list); + } + + #[test] + fn test_hashset_is_empty(ref mut h in hash_set(".*", 0..100)) { + let abs_list = abstraction(h.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::is_empty(h), abs_list.is_empty()); + assert_eq!(abstraction(h.clone()), abs_list); + } + + #[test] + fn test_hashset_insert(ref mut h in hash_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(h.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let after_list = unique(&abs_list.append(conslist![a.clone()]).sort()); + Container::<String>::insert(h, a.clone()); + assert_eq!(abstraction(h.clone()), after_list); + } + + #[test] + fn test_hash_clear(ref mut h in hash_set(".*", 0..100)) { + let abs_list = abstraction(h.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let after_list = clear(&abs_list); + Container::<String>::clear(h); + assert_eq!(abstraction(h.clone()), after_list); + } + + #[test] + fn test_hashset_remove(ref mut h in hash_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(h.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(h, a.clone()); + assert_eq!(abstraction(h.clone()), after_list); + assert_eq!(elem, abs_elem); + } +} + +// #[cfg(test)] +// mod tests { +// use crate::traits::Container; +// use std::collections::HashSet; + +// #[test] +// fn test_hashset_container_trait() { +// let set : &mut dyn Container<u32> = &mut HashSet::<u32>::new(); +// assert_eq!(set.len(), 0); +// set.insert(1); +// set.insert(4); +// assert_eq!(set.len(), 2); +// assert_eq!(set.remove(9), None); +// assert_eq!(set.remove(1), Some(1)); +// assert_eq!(set.len(), 1); +// assert!(set.contains(&4)); +// set.clear(); +// assert_eq!(set.len(), 0); +// } +// }
\ No newline at end of file diff --git a/primrose/src/library/lazy_sorted_vector.rs b/primrose/src/library/lazy_sorted_vector.rs new file mode 100644 index 0000000..4409f44 --- /dev/null +++ b/primrose/src/library/lazy_sorted_vector.rs @@ -0,0 +1,365 @@ +/*LIBSPEC-NAME* +rust-lazy-sorted-vec-spec primrose::library::lazy_sorted_vector::LazySortedVec +*ENDLIBSPEC-NAME*/ + +use std::vec::Vec; +use std::slice::Iter; +use std::ops::Deref; +use crate::traits::{Container, Stack, Indexable}; +use std::iter::FromIterator; + +use proptest::prelude::*; +use crate::proptest::strategies::{lazy_sorted_vec}; +use crate::proptest::*; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +// A Sorted Vector +#[derive(Debug, Clone)] +pub struct LazySortedVec<T> { + v: Vec<T>, + modified: bool +} + +impl<T: Ord> LazySortedVec<T> { + pub fn from_vec(mut v: Vec<T>) -> LazySortedVec<T> { + v.sort(); + LazySortedVec{ v: v, modified: false } + } + + pub fn new() -> LazySortedVec<T> { + LazySortedVec { v: Vec::new(), modified: false } + } + + pub fn len(&mut self) -> usize { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.len() + } + + pub fn contains(&mut self, x: &T) -> bool { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.binary_search(x).is_ok() + } + + pub fn is_empty(&mut self) -> bool { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.len() == 0 + } + + pub fn push(&mut self, value: T) { + self.v.push(value); + self.modified = true; + } + + pub fn pop(&mut self) -> Option<T> { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.pop() + } + + pub fn remove(&mut self, index: usize) -> T { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.remove(index) + } + + pub fn clear(&mut self) { + self.v.clear() + } + + pub fn first(&mut self) -> Option<&T> { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.first() + } + + pub fn last(&mut self) -> Option<&T> { + if (self.modified) { + self.v.sort(); + self.modified = false; + } + self.v.last() + } + + pub fn get(&mut self, index: usize) -> Option<&T> { + self.v.get(index) + } + + pub fn iter(&mut self) -> Iter<T> { + self.v.iter() + } + + pub fn to_vec(self) -> Vec<T> { + self.v + } +} + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord> Container<T> for LazySortedVec<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (sort xs <))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + LazySortedVec::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (sort xs <))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + LazySortedVec::contains(self, x) + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (sort xs <))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + LazySortedVec::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (sort xs <))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + LazySortedVec::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (sort (append xs (list x)) <)) + (define (pre-insert xs) (equal? xs (sort xs <))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + LazySortedVec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (sort xs <))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match self.iter().position(|x| *x == elt) { + Some(index) => { + Some(self.remove(index)) + }, + None => None + } + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T: Ord> Indexable<T> for LazySortedVec<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) (equal? xs (sort xs <))) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + LazySortedVec::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) (equal? xs (sort xs <))) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + LazySortedVec::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) (equal? xs (sort xs <))) + (define (post-nth xs n r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + LazySortedVec::iter(self).nth(n) + } +} + +fn abstraction<T>(v: LazySortedVec<T>) -> ConsList<T> +where T: Ord +{ + let list: ConsList<T> = ConsList::from(v.to_vec()); + list.sort() +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_lazy_sorted_vec_len(ref mut v in lazy_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::len(v), abs_list.len()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_sorted_vec_contains(ref mut v in lazy_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::contains(v, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_sorted_vec_is_empty(ref mut v in lazy_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + assert_eq!(Container::<String>::is_empty(v), abs_list.is_empty()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_sorted_vec_insert(ref mut v in lazy_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let after_list = abs_list.append(conslist![a.clone()]).sort(); + Container::<String>::insert(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_lazy_sorted_vec_clear(ref mut v in lazy_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let after_list = clear(&abs_list); + Container::<String>::clear(v); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_lazy_sorted_vec_remove(ref mut v in lazy_sorted_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_lazy_sorted_vec_first(ref mut v in lazy_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::first(v); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_sorted_vec_last(ref mut v in lazy_sorted_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::last(v); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_sorted_vec_nth(ref mut v in lazy_sorted_vec(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, abs_list.sort()); + //post + let elem = Indexable::<String>::nth(v, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(v.clone()), abs_list); + } +}
\ No newline at end of file diff --git a/primrose/src/library/lazy_unique_vector.rs b/primrose/src/library/lazy_unique_vector.rs new file mode 100644 index 0000000..07175be --- /dev/null +++ b/primrose/src/library/lazy_unique_vector.rs @@ -0,0 +1,366 @@ +/*LIBSPEC-NAME* +rust-lazy-unique-vec-spec primrose::library::lazy_unique_vector::LazyUniqueVec +*ENDLIBSPEC-NAME*/ + +use std::vec::Vec; +use std::slice::Iter; +use std::ops::Deref; +use crate::traits::{Container, Stack, Indexable}; + +use proptest::prelude::*; +use crate::proptest::strategies::{lazy_unique_vec}; +use crate::proptest::*; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +// A Unique and Ascending Vector +#[derive(Debug, Clone)] +pub struct LazyUniqueVec<T> { + v: Vec<T>, + modified: bool +} + +impl<T: Ord> LazyUniqueVec<T> { + pub fn new() -> LazyUniqueVec<T> { + LazyUniqueVec { + v: Vec::new(), + modified: false + } + } + + pub fn from_vec(mut v: Vec<T>) -> LazyUniqueVec<T> { + v.sort(); + v.dedup(); + LazyUniqueVec{ v: v, modified: false } + } + + pub fn len(&mut self) -> usize { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.len() + } + + pub fn contains(&mut self, x: &T) -> bool { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.binary_search(x).is_ok() + } + + pub fn is_empty(&mut self) -> bool { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.len() == 0 + } + + // Duplicated elements will be discarded + pub fn push(&mut self, value: T) { + self.v.push(value); + self.modified = true; + } + + pub fn pop(&mut self) -> Option<T> { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.pop() + } + + pub fn remove(&mut self, index: usize) -> T { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.remove(index) + } + + pub fn clear(&mut self) { + self.v.clear() + } + + pub fn first(&mut self) -> Option<&T> { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.first() + } + + pub fn last(&mut self) -> Option<&T> { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.last() + } + + pub fn iter(&mut self) -> Iter<'_, T> { + if (self.modified) { + self.v.sort(); + self.v.dedup(); + self.modified = false; + } + self.v.iter() + } + + pub fn to_vec(self) -> Vec<T> { + self.v + } +} + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord> Container<T> for LazyUniqueVec<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + LazyUniqueVec::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + LazyUniqueVec::contains(self, x) // use fully qualified syntax to avoid function name collision + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + LazyUniqueVec::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + LazyUniqueVec::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (remove-duplicates (sort (append xs (list x)) <))) + (define (pre-insert xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + LazyUniqueVec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match self.iter().position(|x| *x == elt) { + Some(index) => { + Some(self.remove(index)) + }, + None => None + } + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T: Ord> Indexable<T> for LazyUniqueVec<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + LazyUniqueVec::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + LazyUniqueVec::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-nth n xs r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + LazyUniqueVec::iter(self).nth(n) + } +} + +fn abstraction<T>(v: LazyUniqueVec<T>) -> ConsList<T> +where T: Ord +{ + let list: ConsList<T> = ConsList::from(v.to_vec()); + unique(&list.sort()) +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_lazy_unique_vec_len(ref mut v in lazy_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::len(v), abs_list.len()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_unique_vec_contains(ref mut v in lazy_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::contains(v, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_unique_vec_insert(ref mut v in lazy_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let after_list = unique(&abs_list.append(conslist![a.clone()]).sort()); + Container::<String>::insert(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_lazy_vec_is_empty(ref mut v in lazy_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::is_empty(v), abs_list.is_empty()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_unique_vec_remove(ref mut v in lazy_unique_vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_lazy_unique_vec_first(ref mut v in lazy_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::first(v); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_unique_vec_last(ref mut v in lazy_unique_vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::last(v); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_lazy_unique_vec_nth(ref mut v in lazy_unique_vec(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(v.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::nth(v, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(v.clone()), abs_list); + } +}
\ No newline at end of file diff --git a/primrose/src/library/list.rs b/primrose/src/library/list.rs new file mode 100644 index 0000000..fe898b5 --- /dev/null +++ b/primrose/src/library/list.rs @@ -0,0 +1,319 @@ +/*LIBSPEC-NAME* +rust-linked-list-spec std::collections::LinkedList +*ENDLIBSPEC-NAME*/ + +use std::collections::LinkedList; +use std::cmp::Ordering; +use std::marker::PhantomData; +use std::iter::FromIterator; +// nightly features +use std::collections::linked_list::CursorMut; +use crate::traits::{Container, Stack, Indexable}; +use crate::proptest::*; +use proptest::prelude::*; +use proptest::collection::linked_list; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord> Container<T> for LinkedList<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) #t) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + LinkedList::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) #t) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + LinkedList::contains(self, x) + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) #t) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + LinkedList::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) #t) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + LinkedList::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (append xs (list x))) + (define (pre-insert xs) #t) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + LinkedList::push_back(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) #t) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + let mut c = self.cursor_front_mut(); + loop { + match c.current() { + Some(x) => { + match &elt.cmp(x) { + Ordering::Equal => { + return c.remove_current() + }, + _ => c.move_next() + } + }, + None => { // empty list + return None; + } + } + } + } +} + +/*IMPL* +Stack +*ENDIMPL*/ +impl<T> Stack<T> for LinkedList<T> { + /*LIBSPEC* + /*OPNAME* + push push pre-push post-push + *ENDOPNAME*/ + (define (push xs x) (append xs (list x))) + (define (pre-push xs) #t) + (define (post-push xs x ys) (equal? ys (push xs x))) + *ENDLIBSPEC*/ + fn push(&mut self, elt: T) { + LinkedList::push_back(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + pop pop pre-pop post-pop + *ENDOPNAME*/ + (define (pop xs) + (cond + [(null? xs) (cons xs null)] + [else (cons (take xs (- (length xs) 1)) (last xs))])) + (define (pre-pop xs) #t) + (define (post-pop xs r) (equal? r (pop xs))) + *ENDLIBSPEC*/ + fn pop(&mut self) -> Option<T> { + LinkedList::pop_back(self) + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T> Indexable<T> for LinkedList<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) #t) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + LinkedList::front(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) #t) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + LinkedList::back(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) #t) + (define (post-nth xs n r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + LinkedList::iter(self).nth(n) + } +} + +struct Con<T> { + elem_t: PhantomData<T> +} + +pub trait Constructor { + type Impl: ?Sized; + type Interface: ?Sized; + fn new() -> Box<Self::Interface>; +} + +impl<T: 'static + Ord> Constructor for Con<T> { + type Impl = LinkedList::<T>; + type Interface = dyn Container<T>; + fn new() -> Box<Self::Interface> { + Box::new(Self::Impl::new()) + } +} + +fn abstraction<T>(l: LinkedList<T>) -> ConsList<T> { + let list: ConsList<T> = ConsList::from_iter(l); + list +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_list_len(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + assert_eq!(Container::<String>::len(l), abs_list.len()); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_contains(ref mut l in linked_list(".*", 0..100), a in ".*") { + let abs_list = abstraction(l.clone()); + assert_eq!(Container::<String>::contains(l, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_is_empty(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + assert_eq!(Container::<String>::is_empty(l), abs_list.is_empty()); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_insert(ref mut l in linked_list(".*", 0..100), a in ".*") { + let abs_list = abstraction(l.clone()); + let after_list = abs_list.append(conslist![a.clone()]); + Container::<String>::insert(l, a.clone()); + assert_eq!(abstraction(l.clone()), after_list); + } + + #[test] + fn test_list_clear(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + let after_list = clear(&abs_list); + Container::<String>::clear(l); + assert_eq!(abstraction(l.clone()), after_list); + } + + #[test] + fn test_list_remove(ref mut l in linked_list(".*", 0..100), a in ".*") { + let abs_list = abstraction(l.clone()); + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(l, a.clone()); + assert_eq!(abstraction(l.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_list_first(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + let elem = Indexable::<String>::first(l); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_last(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + let elem = Indexable::<String>::last(l); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_nth(ref mut l in linked_list(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(l.clone()); + let elem = Indexable::<String>::nth(l, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(l.clone()), abs_list); + } + + #[test] + fn test_list_push(ref mut l in linked_list(".*", 0..100), a in ".*") { + let abs_list = abstraction(l.clone()); + let after_list = push(&abs_list, a.clone()); + Stack::<String>::push(l, a.clone()); + assert_eq!(abstraction(l.clone()), after_list); + } + + #[test] + fn test_list_pop(ref mut l in linked_list(".*", 0..100)) { + let abs_list = abstraction(l.clone()); + let (after_list, abs_elem) = pop(&abs_list); + let elem = Stack::<String>::pop(l); + assert_eq!(abstraction(l.clone()), after_list); + assert_eq!(elem.map(|x| Arc::new(x)), abs_elem); + } +}
\ No newline at end of file diff --git a/primrose/src/library/mod.rs b/primrose/src/library/mod.rs new file mode 100644 index 0000000..238a3f4 --- /dev/null +++ b/primrose/src/library/mod.rs @@ -0,0 +1,8 @@ +pub mod list; +pub mod vector; +pub mod treeset; +pub mod hashset; +pub mod eager_unique_vector; +pub mod lazy_unique_vector; +pub mod eager_sorted_vector; +pub mod lazy_sorted_vector;
\ No newline at end of file diff --git a/primrose/src/library/treeset.rs b/primrose/src/library/treeset.rs new file mode 100644 index 0000000..0e19f3d --- /dev/null +++ b/primrose/src/library/treeset.rs @@ -0,0 +1,263 @@ +/*LIBSPEC-NAME* +rust-btreeset-spec std::collections::BTreeSet +*ENDLIBSPEC-NAME*/ + +use std::collections::BTreeSet; +use std::iter::FromIterator; +use crate::traits::{Container, Indexable}; +use crate::proptest::*; + +use proptest::prelude::*; +use proptest::collection::btree_set; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: Ord> Container<T> for BTreeSet<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + BTreeSet::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + BTreeSet::contains(self, x) + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + BTreeSet::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + BTreeSet::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (remove-duplicates (sort (append xs (list x)) <))) + (define (pre-insert xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + BTreeSet::insert(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match BTreeSet::remove(self, &elt) { + true => Some(elt), + false => None + } + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T: Ord> Indexable<T> for BTreeSet<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + BTreeSet::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + BTreeSet::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) (equal? xs (remove-duplicates (sort xs <)))) + (define (post-nth n xs r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + BTreeSet::iter(self).nth(n) + } +} + +fn abstraction<T>(t: BTreeSet<T>) -> ConsList<T> { + let list: ConsList<T> = ConsList::from_iter(t); + list +} + +proptest!{ + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + #[test] + fn test_btree_len(ref mut t in btree_set(".*", 0..100)) { + let abs_list = abstraction(t.clone()); + // pre: our list model is a sorted and unique list + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::len(t), abs_list.len()); + assert_eq!(abstraction(t.clone()), abs_list); + } + + #[test] + fn test_btree_contains(ref mut t in btree_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::contains(t, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(t.clone()), abs_list); + } + + #[test] + fn test_btree_is_empty(ref mut t in btree_set(".*", 0..100)) { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + assert_eq!(Container::<String>::is_empty(t), abs_list.is_empty()); + assert_eq!(abstraction(t.clone()), abs_list); + } + + #[test] + fn test_btree_insert(ref mut t in btree_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let after_list = unique(&abs_list.append(conslist![a.clone()]).sort()); + Container::<String>::insert(t, a.clone()); + assert_eq!(abstraction(t.clone()), after_list); + } + + #[test] + fn test_btree_clear(ref mut t in btree_set(".*", 0..100)) { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let after_list = clear(&abs_list); + Container::<String>::clear(t); + assert_eq!(abstraction(t.clone()), after_list); + } + + #[test] + fn test_btree_remove(ref mut t in btree_set(".*", 0..100), a in ".*") { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(t, a.clone()); + assert_eq!(abstraction(t.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_btree_first(ref mut t in btree_set(".*", 0..100)) { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::first(t); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(t.clone()), abs_list); + } + + #[test] + fn test_btree_last(ref mut t in btree_set(".*", 0..100)) { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::last(t); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(t.clone()), abs_list); + } + + #[test] + fn test_btree_nth(ref mut t in btree_set(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(t.clone()); + //pre + assert_eq!(abs_list, unique(&abs_list.sort())); + //post + let elem = Indexable::<String>::nth(t, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(t.clone()), abs_list); + } +}
\ No newline at end of file diff --git a/primrose/src/library/vector.rs b/primrose/src/library/vector.rs new file mode 100644 index 0000000..36f2a82 --- /dev/null +++ b/primrose/src/library/vector.rs @@ -0,0 +1,287 @@ +/*LIBSPEC-NAME* +rust-vec-spec std::vec::Vec +*ENDLIBSPEC-NAME*/ + +use std::vec::Vec; +use crate::traits::{Container, Stack, Indexable}; +use crate::proptest::*; + +use proptest::prelude::*; +use proptest::collection::vec; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +/*IMPL* +Container +*ENDIMPL*/ +impl<T: PartialEq> Container<T> for Vec<T> { + + /*LIBSPEC* + /*OPNAME* + len op-len pre-len post-len + *ENDOPNAME*/ + (define (op-len xs) (cons xs (length xs))) + (define (pre-len xs) #t) + (define (post-len xs r) (equal? r (op-len xs))) + *ENDLIBSPEC*/ + fn len(&mut self) -> usize { + Vec::len(self) + } + + /*LIBSPEC* + /*OPNAME* + contains op-contains pre-contains post-contains + *ENDOPNAME*/ + (define (op-contains xs x) + (cond + [(list? (member x xs)) (cons xs #t)] + [else (cons xs #f)])) + (define (pre-contains xs) #t) + (define (post-contains xs x r) (equal? r (op-contains xs x))) + *ENDLIBSPEC*/ + fn contains(&mut self, x: &T) -> bool { + <[T]>::contains(self, x) // use fully qualified syntax to avoid function name collision + } + + /*LIBSPEC* + /*OPNAME* + is-empty op-is-empty pre-is-empty post-is-empty + *ENDOPNAME*/ + (define (op-is-empty xs) (cons xs (null? xs))) + (define (pre-is-empty xs) #t) + (define (post-is-empty xs r) (equal? r (op-is-empty xs))) + *ENDLIBSPEC*/ + fn is_empty(&mut self) -> bool { + Vec::is_empty(self) + } + + /*LIBSPEC* + /*OPNAME* + clear op-clear pre-clear post-clear + *ENDOPNAME*/ + (define (op-clear xs) null) + (define (pre-clear xs) #t) + (define (post-clear xs r) (equal? r (op-clear xs))) + *ENDLIBSPEC*/ + fn clear(&mut self) { + Vec::clear(self); + } + + /*LIBSPEC* + /*OPNAME* + insert op-insert pre-insert post-insert + *ENDOPNAME*/ + (define (op-insert xs x) (append xs (list x))) + (define (pre-insert xs) #t) + (define (post-insert xs x ys) (equal? ys (op-insert xs x))) + *ENDLIBSPEC*/ + fn insert(&mut self, elt: T) { + Vec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + remove op-remove pre-remove post-remove + *ENDOPNAME*/ + (define (op-remove xs x) + (cond + [(list? (member x xs)) (cons (remove x xs) x)] + [else (cons xs null)])) + (define (pre-remove xs) #t) + (define (post-remove xs r) (equal? r (op-remove xs))) + *ENDLIBSPEC*/ + fn remove(&mut self, elt: T) -> Option<T> { + match self.iter().position(|x| *x == elt) { + Some(index) => { + Some(self.remove(index)) + }, + None => None + } + } +} + +/*IMPL* +Stack +*ENDIMPL*/ +impl<T> Stack<T> for Vec<T> { + /*LIBSPEC* + /*OPNAME* + push push pre-push post-push + *ENDOPNAME*/ + (define (push xs x) (append xs (list x))) + (define (pre-push xs) #t) + (define (post-push xs x ys) (equal? ys (push xs x))) + *ENDLIBSPEC*/ + fn push(&mut self, elt: T) { + Vec::push(self, elt); + } + + /*LIBSPEC* + /*OPNAME* + pop pop pre-pop post-pop + *ENDOPNAME*/ + (define (pop xs) + (cond + [(null? xs) (cons xs null)] + [else (cons (take xs (- (length xs) 1)) (last xs))])) + (define (pre-pop xs) #t) + (define (post-pop xs r) (equal? r (pop xs))) + *ENDLIBSPEC*/ + fn pop(&mut self) -> Option<T> { + Vec::pop(self) + } +} + +/*IMPL* +Indexable +*ENDIMPL*/ +impl<T> Indexable<T> for Vec<T> { + /*LIBSPEC* + /*OPNAME* + first op-first pre-first post-first + *ENDOPNAME*/ + (define (op-first xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (first xs))])) + (define (pre-first xs) #t) + (define (post-first xs r) (equal? r (op-first xs))) + *ENDLIBSPEC*/ + fn first(&mut self) -> Option<&T> { + <[T]>::first(self) + } + + /*LIBSPEC* + /*OPNAME* + last op-last pre-last post-last + *ENDOPNAME*/ + (define (op-last xs) + (cond + [(null? xs) (cons xs null)] + [else (cons xs (last xs))])) + (define (pre-last xs) #t) + (define (post-last xs r) (equal? r (op-last xs))) + *ENDLIBSPEC*/ + fn last(&mut self) -> Option<&T> { + <[T]>::last(self) + } + + /*LIBSPEC* + /*OPNAME* + nth op-nth pre-nth post-nth + *ENDOPNAME*/ + (define (op-nth xs n) + (cond + [(>= n (length xs)) (cons xs null)] + [(< n 0) (cons xs null)] + [else (cons xs (list-ref xs n))])) + (define (pre-nth xs) #t) + (define (post-nth xs n r) (equal? r (op-nth xs n))) + *ENDLIBSPEC*/ + fn nth(&mut self, n: usize) -> Option<&T> { + <[T]>::iter(self).nth(n) + } +} + +fn abstraction<T>(v: Vec<T>) -> ConsList<T> { + let list: ConsList<T> = ConsList::from(v); + list +} + +proptest! { + #![proptest_config(ProptestConfig { + cases: 100, .. ProptestConfig::default() + })] + + #[test] + fn test_vec_len(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + assert_eq!(Container::<String>::len(v), abs_list.len()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_contains(ref mut v in vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + assert_eq!(Container::<String>::contains(v, &a), contains(&abs_list, &a)); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_is_empty(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + assert_eq!(Container::<String>::is_empty(v), abs_list.is_empty()); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_insert(ref mut v in vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + let after_list = abs_list.append(conslist![a.clone()]); + Container::<String>::insert(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_vec_clear(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + let after_list = clear(&abs_list); + Container::<String>::clear(v); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_vec_remove(ref mut v in vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + let (after_list, abs_elem) = remove(&abs_list, a.clone()); + let elem = Container::<String>::remove(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem, abs_elem); + } + + #[test] + fn test_vec_first(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + let elem = Indexable::<String>::first(v); + let abs_first = first(&abs_list); + assert_eq!(elem, abs_first); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_last(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + let elem = Indexable::<String>::last(v); + let abs_last = last(&abs_list); + assert_eq!(elem, abs_last); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_nth(ref mut v in vec(".*", 0..100), n in 0usize..100) { + let abs_list = abstraction(v.clone()); + let elem = Indexable::<String>::nth(v, n.clone()); + let abs_nth = nth(&abs_list, n); + assert_eq!(elem, abs_nth); + assert_eq!(abstraction(v.clone()), abs_list); + } + + #[test] + fn test_vec_push(ref mut v in vec(".*", 0..100), a in ".*") { + let abs_list = abstraction(v.clone()); + let after_list = push(&abs_list, a.clone()); + Stack::<String>::push(v, a.clone()); + assert_eq!(abstraction(v.clone()), after_list); + } + + #[test] + fn test_vec_pop(ref mut v in vec(".*", 0..100)) { + let abs_list = abstraction(v.clone()); + let (after_list, abs_elem) = pop(&abs_list); + let elem = Stack::<String>::pop(v); + assert_eq!(abstraction(v.clone()), after_list); + assert_eq!(elem.map(|x| Arc::new(x)), abs_elem); + } +}
\ No newline at end of file diff --git a/primrose/src/main.rs b/primrose/src/main.rs new file mode 100644 index 0000000..911520f --- /dev/null +++ b/primrose/src/main.rs @@ -0,0 +1,24 @@ +use primrose::generator::{run}; +use std::env; +use std::io::{Error, ErrorKind}; + +fn main() -> Result<(), Error> { + let args: Vec<String> = env::args().collect(); + if args.len() == 1 { // skip the first arg + let _ = run("./spec_code/example_unique.rs".to_string(), "default".to_string(), 5); + Ok(()) + } else if args.len() == 4 { // skip the first arg + let model_size_input = match args.get(3).unwrap().parse::<u64>() { + Ok(val) => val, + Err(_) => { + println!("here"); + return Err(Error::new(ErrorKind::Other, "Invalid model size")); + } + }; + let model_size = model_size_input as usize; + let _ = run("./spec_code/".to_string() + args.get(1).unwrap(), args.get(2).unwrap().to_string(), model_size); + Ok(()) + } else { + Err(Error::new(ErrorKind::Other, "Invalid source code paths")) + } +}
\ No newline at end of file diff --git a/primrose/src/parser.rs b/primrose/src/parser.rs new file mode 100644 index 0000000..6d60a62 --- /dev/null +++ b/primrose/src/parser.rs @@ -0,0 +1,234 @@ +extern crate peg; +use peg::parser; + +use std::vec::Vec; +use std::iter::FromIterator; + +use crate::types::{Name, Type, TypeVar, Bounds}; + +pub type Id = String; + +pub type Literal = String; + +#[derive(Clone, Debug)] +pub enum Refinement { + Prop(Box<Term>), + AndProps(Box<Refinement>, Box<Refinement>), +} + +#[derive(Clone, Debug)] +pub enum Term { + LitTerm(Box<Literal>), + VarTerm(Box<Id>), + LambdaTerm((Box<Id>, Box<Bounds>), Box<Term>), + AppTerm(Box<Term>, Box<Term>), +} + +impl Term { + pub fn is_quantifier(&self) -> bool { + match self { + Term::VarTerm(id) => { + if id.to_string().eq("forall") { + true + } else { + false + } + }, + _ => false + } + } + + pub fn require_cdr(&self) -> bool { + match self { + Term::VarTerm(id) => { + if id.to_string().eq("pop") { + true + } else { + false + } + }, + _ => false + } + } +} + +impl ToString for Term { + fn to_string(&self) -> String { + match self { + Term::LitTerm(l) => l.to_string(), + Term::VarTerm(id) => id.to_string(), + Term::LambdaTerm((id, bounds), t) => id.to_string(), + Term::AppTerm(t1, t2) => t1.to_string() + &t2.to_string(), + } + } +} + +#[derive(Clone, Debug)] +pub enum Decl { + PropertyDecl((Box<Id>, Box<Type>), Box<Term>), + ConTypeDecl(Box<Type>, (Box<Id>, Box<Bounds>, Box<Refinement>)) +} + +impl Decl { + pub fn is_prop_decl(&self) -> bool { + match self { + Decl::PropertyDecl(_, _) => true, + _ => false + } + } + + pub fn is_contype_decl(&self) -> bool { + match self { + Decl::ConTypeDecl(_, _) => true, + _ => false + } + } + + pub fn get_name(&self) -> String { + match self { + Decl::ConTypeDecl(con_ty, _) => { + let (con, _) = con_ty.get_con_elem().unwrap(); + con + }, + Decl::PropertyDecl((id, _), _) => id.to_string() + } + } +} + +pub type Spec = Vec<Decl>; +pub type Code = String; + +#[derive(Clone, Debug)] +pub enum Block { + SpecBlock(Box<Spec>, usize), + CodeBlock(Box<Code>, usize) +} + +impl Block { + pub fn is_spec_block(&self) -> bool { + match self { + Block::SpecBlock(_, _) => true, + _ => false + } + } + + pub fn is_code_block(&self) -> bool { + match self { + Block::CodeBlock(_, _) => true, + _ => false + } + } + + pub fn extract_spec(&self) -> Spec { + match self { + Block::SpecBlock(spec, _) => spec.to_vec(), + _ => Vec::new() + } + } + + pub fn extract_code(&self) -> Code { + match self { + Block::CodeBlock(code, _) => code.to_string(), + _ => String::new() + } + } +} + +pub type Prog = Vec<Block>; + +parser!{ +pub grammar spec() for str { + pub rule id() -> Id + = s:$(!keyword() ([ 'a'..='z' | 'A'..='Z' | '_' ]['a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '?' ]*)) + { s.into() } + + pub rule name() -> Name + = s:$(!keyword() ([ 'a'..='z' | 'A'..='Z' | '_' ]['a'..='z' | 'A'..='Z' | '0'..='9' ]*)) + { s.into() } + + pub rule keyword() -> () + = ("crate" / "super" / "self" / "Self" / "const" / "mut" / "true" / "false" / "pub" / "in" / "from" / "with" + / "f32"/ "i32" / "u32" / "bool" / "let" / "if" / "else" / "for" / "while" / "fn" / "do") + ![ 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' | '-' | '?' ] + + pub rule literal() -> Literal + = s:$("true" / "false") + { s.into() } + + pub rule ty() -> Type + = precedence! { + n:name() "<" _ t:ty() _ ">" + { Type::Con(Box::new(n), Box::new(t), Box::new(Bounds::from(["Container".to_string()]))) } + -- + n:name() + { Type::Var(TypeVar::new(n)) } + } + + pub rule term() -> Term + = precedence!{ + lit: literal() { Term::LitTerm(Box::new(lit)) } + -- + v:id() { Term::VarTerm(Box::new(v)) } + -- + "\\" v:id() _ "->" _ t:term() { Term::LambdaTerm((Box::new(v), Box::new(Bounds::new())), Box::new(t)) } + -- + "\\" v:id() _ "<:" _ "(" _ b:bounds() _ ")" _ "->" _ t:term() { Term::LambdaTerm((Box::new(v), Box::new(b)), Box::new(t)) } + -- + "(" _ t1:term() __ t2:term() _ ")" { Term::AppTerm(Box::new(t1), Box::new(t2)) } + } + + pub rule refinement() -> Refinement + = precedence!{ + t:term() { Refinement::Prop(Box::new(t)) } + -- + "(" _ p1:refinement() __ "and" __ p2:refinement() _ ")" { Refinement::AndProps(Box::new(p1), Box::new(p2)) } + } + + pub rule bounds() -> Bounds + = l: ((_ n:name() _ {n}) ++ "," ) { Bounds::from_iter(l.iter().cloned()) } + + pub rule decl() -> Decl + = precedence! { + _ "property" __ p:id() _ "<" _ ty:ty() _ ">" _ "{" _ t:term() _ "}" _ + { + Decl::PropertyDecl((Box::new(p), Box::new(ty)), Box::new(t)) + } + -- + _ "type" __ ty:ty() _ "=" _ "{" _ c:id() _ "impl" __ "(" _ b:bounds() _ ")" _ "|" _ t:refinement() _ "}" _ + { + Decl::ConTypeDecl(Box::new(ty), (Box::new(c), Box::new(b), Box::new(t))) + } + } + + pub rule spec() -> Spec + = _ "/*SPEC*" _ decls: (d:decl() { d }) ** _ _ "*ENDSPEC*/" _ + { + decls + } + + pub rule code() -> Code + = _ "/*CODE*/" c:$((!"/*ENDCODE*/"!"/*SPEC*"!"*ENDSPEC*/"[_])*) "/*ENDCODE*/" _ { c.into() } + + pub rule block() -> Block + = precedence! { + _ p:position!() s:spec() _ + { + Block::SpecBlock(Box::new(s), p) + } + -- + _ p:position!() c:code() _ + { + Block::CodeBlock(Box::new(c), p) + } + } + + pub rule prog() -> Prog + = _ blocks: (b:block() { b }) ** _ _ + { + blocks + } + + rule _ = quiet!{[' ' | '\n' | '\t']*} + rule __ = quiet!{[' ' | '\n' | '\t']+} + +}}
\ No newline at end of file diff --git a/primrose/src/proptest/mod.rs b/primrose/src/proptest/mod.rs new file mode 100644 index 0000000..159a9f6 --- /dev/null +++ b/primrose/src/proptest/mod.rs @@ -0,0 +1,68 @@ +pub mod strategies; + +use im::conslist::{ConsList}; +use im::conslist; +use std::sync::Arc; + +pub fn contains<T: PartialEq>(list: &ConsList<T>, elem: &T) -> bool { + list.iter().find(|x| x.as_ref() == elem).is_some() +} + +pub fn clear<T>(list: &ConsList<T>) -> ConsList<T> { + ConsList::<T>::new() +} + +pub fn remove<T: PartialEq+Clone>(list: &ConsList<T>, a: T) -> (ConsList<T>, Option<T>) { + if contains(list, &a) { + let mut result = ConsList::<T>::new(); + let mut found = false; + for i in list.iter() { + if i.as_ref() == &a && !found { + found = true; + continue; + } else { + result = result.append(conslist![i.clone()]); + } + } + (result, Some(a)) + } else { + (list.clone(), None) + } +} + +pub fn first<T>(list: &ConsList<T>) -> Option<&T> { + list.head().map(|x| unsafe{&*Arc::into_raw(x)}) +} + +pub fn last<T>(list: &ConsList<T>) -> Option<&T> { + list.reverse().head().map(|x| unsafe{&*Arc::into_raw(x)}) +} + +pub fn nth<T>(list: &ConsList<T>, n: usize) -> Option<&T> { + list.iter().nth(n).map(|x| unsafe{&*Arc::into_raw(x)}) +} + +pub fn push<T>(list: &ConsList<T>, a: T) -> ConsList<T> { + list.append(conslist![a]) +} + +pub fn pop<T>(list: &ConsList<T>) -> (ConsList<T>, Option<Arc<T>>) { + if list.is_empty() { + (ConsList::<T>::new(), None) + } else { + let (elem, result) = list.reverse().uncons().unwrap(); + (result.reverse(), Some(elem)) + } +} + +pub fn unique<T: PartialEq>(list: &ConsList<T>) -> ConsList<T> { + let mut result = ConsList::<T>::new(); + for i in list.iter() { + if contains(&result, &i) { + continue; + } else { + result = result.append(conslist![i.clone()]); + } + } + result +}
\ No newline at end of file diff --git a/primrose/src/proptest/strategies.rs b/primrose/src/proptest/strategies.rs new file mode 100644 index 0000000..1f529ca --- /dev/null +++ b/primrose/src/proptest/strategies.rs @@ -0,0 +1,37 @@ +use proptest::strategy::*; +use proptest::prelude::*; +use std::ops::Range; + +use crate::library::eager_unique_vector::{EagerUniqueVec}; +use crate::library::eager_sorted_vector::{EagerSortedVec}; +use crate::library::lazy_unique_vector::{LazyUniqueVec}; +use crate::library::lazy_sorted_vector::{LazySortedVec}; +use proptest::collection::vec; +use proptest::prelude::*; +use im::conslist::{ConsList}; + + +pub fn eager_unique_vec<T: Strategy + 'static>(element: T, size: Range<usize>) -> impl Strategy<Value = EagerUniqueVec<T::Value>> +where <T as Strategy>::Value: PartialEq +{ + vec(element, size.clone()).prop_map(EagerUniqueVec::from_vec) +} + + +pub fn lazy_unique_vec<T: Strategy + 'static>(element: T, size: Range<usize>) -> impl Strategy<Value = LazyUniqueVec<T::Value>> +where <T as Strategy>::Value: Ord +{ + vec(element, size.clone()).prop_map(LazyUniqueVec::from_vec) +} + +pub fn eager_sorted_vec<T: Strategy + 'static>(element: T, size: Range<usize>) -> impl Strategy<Value = EagerSortedVec<T::Value>> +where <T as Strategy>::Value: Ord +{ + vec(element, size.clone()).prop_map(EagerSortedVec::from_vec) +} + +pub fn lazy_sorted_vec<T: Strategy + 'static>(element: T, size: Range<usize>) -> impl Strategy<Value = LazySortedVec<T::Value>> +where <T as Strategy>::Value: Ord +{ + vec(element, size.clone()).prop_map(LazySortedVec::from_vec) +} diff --git a/primrose/src/run_matching.rs b/primrose/src/run_matching.rs new file mode 100644 index 0000000..746c29a --- /dev/null +++ b/primrose/src/run_matching.rs @@ -0,0 +1,86 @@ +use std::process::Command; +use std::env; +use std::fs; +use std::io::{Write, BufReader, BufRead, Error, ErrorKind}; + +use crate::spec_map::{MatchSetup}; + +type ExecutionError = String; + +pub const LANGDECL: &str = "#lang rosette\n"; +const GENNAME: &str = "./racket_specs/gen_match/match-script.rkt"; +const LIBSPECPATH: &str = "../gen_lib_spec/"; +const PROPSPECPATH: &str = "../gen_prop_spec/"; +//const SETUP: &str = "(require \"../match-setup.rkt\")\n"; +const LIBDIR: &str = "./racket_specs/gen_lib_spec/"; +const PROPDIR: &str = "./racket_specs/gen_prop_spec/"; +const MATCHDIR: &str = "./racket_specs/gen_match/"; + +pub fn initialise_match_setup() -> MatchSetup { + let mut match_setup = MatchSetup::new(); + match_setup.insert("Container".to_string(), "../container-setup.rkt".to_string()); + match_setup.insert("Indexable".to_string(), "../indexable-setup.rkt".to_string()); + match_setup.insert("Stack".to_string(), "../stack-setup.rkt".to_string()); + match_setup +} + + +pub fn gen_match_script(prop: String, match_setup: String, prop_spec_file: String, lib_spec_file: String, interface_spec: String, symbolics: &Vec<String>) -> Result<String, Error> { + let mut output = fs::File::create(GENNAME.to_owned())?; + write!(output, "{}", LANGDECL.to_string())?; + let require_prop = "(require \"".to_string() + PROPSPECPATH + &prop_spec_file + "\")\n"; + write!(output, "{}", require_prop)?; + let require_lib = "(require \"".to_string() + LIBSPECPATH + &lib_spec_file + "\")\n"; + write!(output, "{}", require_lib)?; + write!(output, "{}", "(require \"".to_string() + &match_setup + "\")\n")?; + let s = symbolics.join(" "); + let code = "(check ".to_string() + &prop + " (cdr " + &interface_spec +") (car " + &interface_spec + ") ls " + &s + ")\n"; + write!(output, "{}", code)?; + Ok(GENNAME.to_string()) +} + +pub fn run_matching(filename: String) -> Result<bool, ExecutionError> { + let output = Command::new("sh") + .arg("-c") + .arg("racket ".to_owned() + &filename) + .output() + .expect("failed to execute process"); + let raw = output.stdout; + let result_str = String::from_utf8_lossy(&raw).to_string(); + let result = result_str.trim(); + if (result == "#t") { + Ok(true) + } else if (result == "#f"){ + Ok(false) + } else { + Err("Error: Not a valid output.".to_string()) + } +} + +pub fn cleanup_script() { + Command::new("sh") + .arg("-c") + .arg("rm -f ".to_owned() + GENNAME) + .output() + .expect("Fail to clean up"); +} + +pub fn setup_dirs() { + Command::new("sh") + .arg("-c") + .arg("mkdir -p ".to_owned() + PROPDIR) + .output() + .expect("Fail to create the property specification directory"); + + Command::new("sh") + .arg("-c") + .arg("mkdir -p ".to_owned() + LIBDIR) + .output() + .expect("Fail to create the library specification directory"); + + Command::new("sh") + .arg("-c") + .arg("mkdir -p ".to_owned() + MATCHDIR) + .output() + .expect("Fail to create the matching script directory"); +}
\ No newline at end of file diff --git a/primrose/src/spec_map.rs b/primrose/src/spec_map.rs new file mode 100644 index 0000000..c5d7656 --- /dev/null +++ b/primrose/src/spec_map.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; +use std::collections::hash_map::Iter; + +type StructName = String; +type LibSpecDir = String; +type BoundName = String; +type BoundProvide = String; +type MatchSetupDir = String; +pub type Bounds = HashMap<BoundName, BoundProvide>; +pub type ProvidedOps = (Vec<String>, Vec<String>); + +type PropertyName = String; +type PropSpecDir = String; +type PropSymbolics = Vec<String>; + +pub type LibSpecs = HashMap<StructName, (LibSpecDir, Bounds, ProvidedOps)>; +pub type PropSpecs = HashMap<PropertyName, (PropSpecDir, PropSymbolics)>; +pub type MatchSetup = HashMap<BoundName, MatchSetupDir>;
\ No newline at end of file diff --git a/primrose/src/tools/mod.rs b/primrose/src/tools/mod.rs new file mode 100644 index 0000000..3f75b33 --- /dev/null +++ b/primrose/src/tools/mod.rs @@ -0,0 +1,43 @@ +use std::mem::size_of; +use std::collections::{BTreeSet, HashSet}; +use std::vec::Vec; +use rand::{Rng,SeedableRng}; +use rand::rngs::StdRng; +use rand::seq::SliceRandom; +use rand::thread_rng; + +pub fn gen_dataset_1() -> Vec<u32> { + let size = 1024 * 1024; // 1 MB + let amount = size / size_of::<u32>(); + let mut data: Vec<u32> = (1..amount as u32).collect(); + let mut rng = StdRng::seed_from_u64(222); + data.shuffle(&mut rng); + data +} + +pub fn gen_dataset_128() -> Vec<u32> { + let size = 128 * 1024 * 1024; // 128 MB + let amount = size / size_of::<u32>(); + let mut data: Vec<u32> = (1..amount as u32).collect(); + let mut rng = StdRng::seed_from_u64(222); + data.shuffle(&mut rng); + data +} + +pub fn gen_dataset_256() -> Vec<u32> { + let size = 256 * 1024 * 1024; // 256 MB + let amount = size / size_of::<u32>(); + let mut data: Vec<u32> = (1..amount as u32).collect(); + let mut rng = StdRng::seed_from_u64(222); + data.shuffle(&mut rng); + data +} + +pub fn gen_dataset_512() -> Vec<u32> { + let size = 512 * 1024 * 1024; // 512 MB + let amount = size / size_of::<u32>(); + let mut data: Vec<u32> = (1..amount as u32).collect(); + let mut rng = StdRng::seed_from_u64(222); + data.shuffle(&mut rng); + data +}
\ No newline at end of file diff --git a/primrose/src/traits/container_constructor.rs b/primrose/src/traits/container_constructor.rs new file mode 100644 index 0000000..fa4e163 --- /dev/null +++ b/primrose/src/traits/container_constructor.rs @@ -0,0 +1,5 @@ +pub trait ContainerConstructor { + type Impl: ?Sized; + type Bound: ?Sized; + fn new() -> Box<Self::Bound>; +}
\ No newline at end of file diff --git a/primrose/src/traits/mod.rs b/primrose/src/traits/mod.rs new file mode 100644 index 0000000..5d98639 --- /dev/null +++ b/primrose/src/traits/mod.rs @@ -0,0 +1,21 @@ +pub mod container_constructor; + +pub trait Container<T> { + fn len(&mut self) -> usize; + fn contains(&mut self, x: &T) -> bool; + fn is_empty(&mut self) -> bool; + fn insert(&mut self, elt: T); + fn clear(&mut self); + fn remove(&mut self, elt: T) -> Option<T>; // remove first occurance +} + +pub trait Stack<T> { + fn push(&mut self, elt: T); + fn pop(&mut self) -> Option<T>; +} + +pub trait Indexable<T> { + fn first(&mut self) -> Option<&T>; + fn last(&mut self) -> Option<&T>; + fn nth(&mut self, n: usize) -> Option<&T>; +}
\ No newline at end of file diff --git a/primrose/src/type_check.rs b/primrose/src/type_check.rs new file mode 100644 index 0000000..7b5424b --- /dev/null +++ b/primrose/src/type_check.rs @@ -0,0 +1,349 @@ +use crate::parser::{Prog, Block, Spec, Decl, Term, Refinement, Id, spec}; +use crate::inference::{TypeEnv}; +use crate::generator::{readfile}; +use crate::types::{Type, TypeVar, TypeScheme, TypeVarGen, Bounds}; + +use std::ops::Deref; + +type TypeError = String; + +pub struct TypeChecker { + global_ctx : TypeEnv, + tvg: TypeVarGen +} + +impl TypeChecker { + pub fn new() -> TypeChecker { + TypeChecker { + global_ctx: TypeEnv::new(), + tvg: TypeVarGen::new() + } + } + + fn predefined(&mut self) { + // put for_all_unique_pair into context + let binary_fn1 = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("for-all-unique-pairs".to_string(), + TypeScheme { + vars: Vec::new(), + ty: Type::Fun(Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Container".to_string()])))), + Box::new(Type::Fun(Box::new(binary_fn1), Box::new(Type::Bool())))) + } + ); + + // put for_all_unique_pair into context + let binary_fn2 = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("for-all-consecutive-pairs".to_string(), + TypeScheme { + vars: Vec::new(), + ty: Type::Fun(Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Container".to_string()])))), + Box::new(Type::Fun(Box::new(binary_fn2), Box::new(Type::Bool())))) + } + ); + + let unary_fn = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())); + self.global_ctx.insert("for-all-elems".to_string(), + TypeScheme { + vars: Vec::new(), + ty: Type::Fun(Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Container".to_string()])))), + Box::new(Type::Fun(Box::new(unary_fn), Box::new(Type::Bool())))) + } + ); + + // put neq into context + let neq_fn = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("neq".to_string(), + TypeScheme { + vars: Vec::new(), + ty: neq_fn + } + ); + + // put leq into context + let leq_fn = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("leq?".to_string(), + TypeScheme { + vars: Vec::new(), + ty: leq_fn + } + ); + + let geq_fn = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("geq?".to_string(), + TypeScheme { + vars: Vec::new(), + ty: geq_fn + } + ); + + let equal = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool())))); + self.global_ctx.insert("equal?".to_string(), + TypeScheme { + vars: Vec::new(), + ty: equal + } + ); + + let unique_count_fn = Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Type::Fun(Box::new(Type::Con(Box::new("Con".to_string()), + Box::new(Type::Var(TypeVar::new("T".to_string()))), + Box::new(Bounds::from(["Container".to_string()])))), Box::new(Type::Bool())))); + self.global_ctx.insert("unique-count?".to_string(), + TypeScheme { + vars: Vec::new(), + ty: unique_count_fn + } + ); + + // the forall quantifier + let forall = Type::Fun( + Box::new(Type::Fun(Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Bool()))), + Box::new(Type::Bool())); + self.global_ctx.insert("forall".to_string(), + TypeScheme { + vars: Vec::new(), + ty: forall + } + ); + } + + pub fn get_ctx(&self) -> &TypeEnv { + &self.global_ctx + } + + pub fn check_prog(&mut self, prog: Prog) -> Result<(), TypeError> { + let specs: Vec<Spec> = + prog.iter() + .filter(| block | block.is_spec_block()) + .map(| block | block.extract_spec()) + .collect(); + self.predefined(); + self.check_specs(specs) + } + + pub fn check_specs(&mut self, specs: Vec<Spec>) -> Result<(), TypeError> { + let concat_specs = specs.concat(); + let prop_decls: Vec<&Decl> = + concat_specs.iter() + .filter(| decl | decl.is_prop_decl()) + .collect(); + let contype_decls: Vec<&Decl> = + concat_specs.iter() + .filter(| decl | decl.is_contype_decl()) + .collect(); + match self.check_prop_decls(prop_decls) { + Ok(_) => { + match self.check_contype_decls(contype_decls.clone()) { + Ok(_) => self.check_bound_decls(contype_decls), + Err(e) => Err(e) + } + } + Err(e) => Err(e) + } + } + + pub fn check_bound_decls(&mut self, decls: Vec<&Decl>) -> Result<(), TypeError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.check_bound_decl(decl) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } + + pub fn check_bound_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + match decl { + Decl::ConTypeDecl(_, (_, ins, _)) => { + // Duplicate bound name checking + for i in ins.iter() { + match self.global_ctx.get(&i.to_string()) { + Some(_) => { + return Err("Duplicate bound name declaration".to_string()); + }, + None => continue, // TODO: check each bound is a valid rust trait + } + } + Ok(()) + }, + _ => Err("Not a valid bound declaration".to_string()) + } + } + + pub fn check_prop_decls(&mut self, decls: Vec<&Decl>) -> Result<(), TypeError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.check_prop_decl(decl) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } + + pub fn check_prop_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + match decl { + Decl::PropertyDecl((id, ty), term) => { + // Duplicate property decl checking + match self.global_ctx.get(&id.to_string()) { + Some(_) => Err("Duplicate property declaration".to_string()), + None => { + // check well formedness + match self.global_ctx.type_inference(term, &mut self.tvg) { + Ok(ty) => { + // it should have type Con<T> -> Bool + match ty { + Type::Fun(ref t1, ref t2) => { + match (t1.deref(), t2.deref()) { + (Type::Con(n, t, _), Type::Bool()) => { + if n.to_string() == "Con".to_string() { + self.global_ctx.insert( + id.to_string(), + TypeScheme { + vars: Vec::new(), + ty: ty + } + ); + Ok(()) + } else { + Err("Not a valid property decl: input does not have basic container type Con<T>".to_string()) + } + }, + (_, Type::Bool()) => { + self.global_ctx.insert( + id.to_string(), + TypeScheme { + vars: Vec::new(), + ty: ty + } + ); + Ok(()) + }, + _ => Err("Not a valid property decl: return type should be Bool".to_string()) + } + }, + _ => Err("Not a valid property decl: should have type Con<T> -> Bool".to_string()) + } + } + Err(e) => Err(e) + } + }, + } + }, + _ => Err("Not a valid property declaration".to_string()) + } + } + + pub fn check_contype_decls(&mut self, decls: Vec<&Decl>) -> Result<(), TypeError> { + let mut result = Ok(()); + for decl in decls.into_iter() { + match self.check_contype_decl(decl) { + Ok(_) => continue, + Err(e) => { + result = Err(e); + break; + } + } + } + result + } + + pub fn check_contype_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + match decl { + Decl::ConTypeDecl(con_ty, (vid, ins, r)) => { + // Duplicate container type decl checking + match self.global_ctx.get(&con_ty.to_string()) { + Some(_) => Err("Duplicate container type declaration".to_string()), + None => { + let con = Type::Con(Box::new("Con".to_string()), Box::new(Type::Var(TypeVar::new("T".to_string()))), ins.clone()); + let mut local_ctx = self.global_ctx.clone(); + local_ctx.insert(vid.to_string(), + TypeScheme { + vars: Vec::new(), + ty: con + } + ); + match self.check_ref(&mut local_ctx, r) { + Ok(_) => { + self.global_ctx.insert(decl.get_name(), + TypeScheme{ + vars: Vec::new(), + ty: *con_ty.clone() + } + ); + Ok(()) + }, + Err(e) => Err(e) + } + } + } + }, + _ => Err("Not a valid container type declaration".to_string()) + } + } + + pub fn check_ref(&mut self, ctx: &mut TypeEnv, r: &Refinement) -> Result<(), TypeError> { + match r { + Refinement::Prop(t) => { + match ctx.type_inference(t.deref(), &mut self.tvg) { + Ok(t) => { + // t has to be boolean + if t.is_bool() { + Ok(()) + } else { + Err("The refinement has to be evaluated to a Bool type.".to_string()) + } + }, + Err(e) => Err(e) + } + }, + Refinement::AndProps(r1, r2) => { + match self.check_ref(ctx, r1) { + Ok(_) => { + match self.check_ref(ctx, r2) { + Ok(_) => Ok(()), + Err(e) => Err(e) + } + }, + Err(e) => Err(e) + } + } + } + } +} + + +// Helper functions of testing +// TODO; restructure them +pub fn check_prop_decl() -> Result<(), TypeError> { + let mut tc = TypeChecker::new(); + let f = readfile("./spec_code/example.rs".to_string()); + match spec::prog(&f) { + Ok(prog) => tc.check_prog(prog), + Err(e) => Err(e.to_string()) + } +} + +// #[cfg(test)] +// mod tests { +// use crate::type_check::{TypeChecker, check_prop_decl}; + +// #[test] +// fn test_dup_prop_decl() { +// assert!(check_prop_decl().is_ok()); +// } + +// }
\ No newline at end of file diff --git a/primrose/src/types.rs b/primrose/src/types.rs new file mode 100644 index 0000000..8578f5d --- /dev/null +++ b/primrose/src/types.rs @@ -0,0 +1,280 @@ +use std::borrow::Borrow; +use std::collections::{HashSet, HashMap}; +use std::hash::Hash; +use std::ops::{Deref, DerefMut}; +use std::fmt; +use std::result; + +pub type Name = String; + +// traits +pub type Bounds = HashSet<Name>; + +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum Type { + Bool(), + Int(), + Var(TypeVar), + Con(Box<Name>, Box<Type>, Box<Bounds>), + Fun(Box<Type>, Box<Type>) +} + +impl Type { + pub fn is_var(&self) -> bool { + match self { + Type::Var(_) => true, + _ => false + } + } + + pub fn is_bool(&self) -> bool { + match self { + Type::Bool() => true, + _ => false + } + } + + pub fn get_con_elem(&self) -> Option<(String, String)> { + match self { + Type::Con(n, t, _) => Some((n.to_string(), t.to_string())), + _ => None + } + } +} + +impl ToString for Type { + fn to_string(&self) -> String { + match self { + Type::Bool() => "bool".to_string(), + Type::Int() => "int".to_string(), + Type::Var(tv) => tv.to_string(), + Type::Con(n, t, bounds) => n.to_string() + "<" + &t.to_string() + ">" + " <: (" + &bounds.clone().into_iter().collect::<Vec<String>>().join(", ") + ")", + Type::Fun(t1, t2) => t1.to_string() + "->" + &t2.to_string(), + } + } +} + +pub type UnificationError = String; + +trait Union { + fn union(&self, other: &Self) -> Self; +} + +impl<K, V> Union for HashMap<K, V> + where K: Clone + Eq + Hash, + V: Clone +{ + fn union(&self, other: &Self) -> Self { + let mut res = self.clone(); + for (key, value) in other { + res.entry(key.clone()).or_insert(value.clone()); + } + res + } +} + +impl Type { + // Most general unifier + pub fn mgu(&self, other: &Type) -> Result<Subst, UnificationError> { + match (self, other) { + // Unify function type + (&Type::Fun(ref in1, ref out1), &Type::Fun(ref in2, ref out2)) => { + let sub1 = in1.mgu(in2)?; + let sub2 = out1.apply(&sub1).mgu(&out2.apply(&sub1))?; + Ok(sub1.compose(&sub2)) + } + + // Unify con type + (&Type::Con(ref n1, ref t1, _), &Type::Con(ref n2, ref t2, _)) => { + if n1.to_string() != n2.to_string() { + Err("Cannot unify two different container".to_string()) + } else { + t1.mgu(t2) + } + } + + // Type variable biding + (&Type::Var(ref v), t) => v.bind(t), + (t, &Type::Var(ref v)) => v.bind(t), + + // Unify primitives + (&Type::Int(), &Type::Int()) | (&Type::Bool(), &Type::Bool()) => { + Ok(Subst::new()) + } + + // Otherwise, the types cannot be unified. + (t1, t2) => { + println!("{:?}", t1); + println!("{:?}", t2); + Err("types do not unify".to_string()) + } + } + } +} + +// A substitution is a mapping from type variables to types. +#[derive(Clone, Debug)] +pub struct Subst(HashMap<TypeVar, Type>); + +impl Deref for Subst { + type Target = HashMap<TypeVar, Type>; + fn deref(&self) -> &HashMap<TypeVar, Type> { + &self.0 + } +} +impl DerefMut for Subst { + fn deref_mut(&mut self) -> &mut HashMap<TypeVar, Type> { + &mut self.0 + } +} + +impl Subst { + pub fn new() -> Subst { + Subst(HashMap::new()) + } + + // composing substitutions + pub fn compose(&self, other: &Subst) -> Subst { + Subst(self.union(&other.iter() + .map(|(k, v)| (k.clone(), v.apply(self))) + .collect())) + } +} + +// Fresh variable generator +pub struct TypeVarGen { + supply: usize, +} + +impl TypeVarGen { + pub fn new() -> TypeVarGen { + TypeVarGen { supply: 0 } + } + pub fn next(&mut self) -> TypeVar { + let name = "#T".to_owned() + &self.supply.to_string(); + let v = TypeVar::new(name); + self.supply += 1; + v + } +} + +// Type variables/type names +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TypeVar { + name: Name, +} + +impl TypeVar { + pub fn new(s: Name) -> TypeVar { + TypeVar{name: s} + } + /// Attempt to bind a type variable to a type, returning an appropriate substitution. + fn bind(&self, ty: &Type) -> Result<Subst, UnificationError> { + // Binding to itself + if let &Type::Var(ref u) = ty { + if u == self { + return Ok(Subst::new()); + } + } + + // Occurance check + if ty.ftv().contains(self) { + return Err("occur check fails".to_string()); + } + + let mut s = Subst::new(); + s.insert(self.clone(), ty.clone()); + Ok(s) + } +} + + +impl ToString for TypeVar { + fn to_string(&self) -> String { + self.name.to_string() + } +} + +pub trait Types { + fn ftv(&self) -> HashSet<TypeVar>; + fn apply(&self, s: &Subst) -> Self; +} + +impl<'a, T> Types for Vec<T> + where T: Types +{ + // Free type variables + fn ftv(&self) -> HashSet<TypeVar> { + self.iter() + .map(|x| x.ftv()) + .fold(HashSet::new(), |set, x| set.union(&x).cloned().collect()) + } + + // Apply a substitution to a vector of types + fn apply(&self, s: &Subst) -> Vec<T> { + self.iter().map(|x| x.apply(s)).collect() + } +} + +impl Types for Type { + fn ftv(&self) -> HashSet<TypeVar> { + match self { + &Type::Var(ref s) => [s.clone()].iter().cloned().collect(), + &Type::Int() | &Type::Bool() => HashSet::new(), + &Type::Fun(ref i, ref o) => i.ftv().union(&o.ftv()).cloned().collect(), + &Type::Con(_, ref s, _) => s.ftv().union(&HashSet::new()).cloned().collect(), + } + } + + // apply substitution + fn apply(&self, s: &Subst) -> Type { + match self { + &Type::Var(ref n) => s.get(n).cloned().unwrap_or(self.clone()), + &Type::Fun(ref t1, ref t2) => Type::Fun(Box::new(t1.apply(s)), Box::new(t2.apply(s))), + &Type::Con(ref n, ref t, ref bounds) => Type::Con(Box::new(n.to_string()), Box::new(t.apply(s)), bounds.clone()), + _ => self.clone(), + } + } +} + +// A type scheme is a type with an extra piece of information attached, to constraint the inference +#[derive(Clone, Debug)] +pub struct TypeScheme { + pub vars: Vec<TypeVar>, + pub ty: Type, +} + +impl Types for TypeScheme { + fn ftv(&self) -> HashSet<TypeVar> { + self.ty + .ftv() + .difference(&self.vars.iter().cloned().collect()) + .cloned() + .collect() + } + + fn apply(&self, s: &Subst) -> TypeScheme { + TypeScheme { + vars: self.vars.clone(), + ty: { + let mut sub = s.clone(); + for var in &self.vars { + sub.remove(var); + } + self.ty.apply(&sub) + }, + } + } +} + +impl TypeScheme { + /// Instantiates a typescheme into a type. + pub fn instantiate(&self, tvg: &mut TypeVarGen) -> Type { + let newvars = self.vars.iter().map(|_| Type::Var(tvg.next())); + self.ty.apply(&Subst(self.vars + .iter() + .cloned() + .zip(newvars) + .collect())) + } +}
\ No newline at end of file |