diff options
author | Aria <me@aria.rip> | 2023-10-06 18:07:14 +0100 |
---|---|---|
committer | Aria <me@aria.rip> | 2023-10-06 18:07:14 +0100 |
commit | cf1cc487a741d38a58a2bd12ddd4fb36867b102f (patch) | |
tree | 49888e348766c865d7258ff984ed74756c4946e9 | |
parent | bd89f1c478c5e620f092e93e8415ec5de1918191 (diff) |
more cleanup and docs
-rw-r--r-- | primrose/Cargo.toml | 7 | ||||
-rw-r--r-- | primrose/primrose/Cargo.toml | 3 | ||||
-rw-r--r-- | primrose/primrose/src/analysis.rs | 59 | ||||
-rw-r--r-- | primrose/primrose/src/bounded_ops.rs | 12 | ||||
-rw-r--r-- | primrose/primrose/src/description.rs | 58 | ||||
-rw-r--r-- | primrose/primrose/src/generator.rs | 156 | ||||
-rw-r--r-- | primrose/primrose/src/inference.rs | 2 | ||||
-rw-r--r-- | primrose/primrose/src/main.rs | 38 | ||||
-rw-r--r-- | primrose/primrose/src/parser.rs | 42 | ||||
-rw-r--r-- | primrose/primrose/src/type_check.rs | 363 | ||||
-rw-r--r-- | primrose/primrose/src/types.rs | 13 |
11 files changed, 373 insertions, 380 deletions
diff --git a/primrose/Cargo.toml b/primrose/Cargo.toml index 8022dda..893dfa9 100644 --- a/primrose/Cargo.toml +++ b/primrose/Cargo.toml @@ -2,4 +2,9 @@ resolver = "2" members = [ "primrose/" -]
\ No newline at end of file +] + +[workspace.dependencies] +log = { version = "0.4.20" } +env_logger = "0.10.0" +thiserror = "1.0.49"
\ No newline at end of file diff --git a/primrose/primrose/Cargo.toml b/primrose/primrose/Cargo.toml index 3ac3b71..451a128 100644 --- a/primrose/primrose/Cargo.toml +++ b/primrose/primrose/Cargo.toml @@ -11,6 +11,9 @@ indicatif = "0.16.2" rand = "0.8.5" im = "10.2.0" proptest = "1.0.0" +thiserror = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } [dev-dependencies] criterion = "0.3.5" diff --git a/primrose/primrose/src/analysis.rs b/primrose/primrose/src/analysis.rs index c76ec29..e1a1e94 100644 --- a/primrose/primrose/src/analysis.rs +++ b/primrose/primrose/src/analysis.rs @@ -6,8 +6,12 @@ use std::env; use std::fs; use std::io::{BufRead, BufReader, Error, ErrorKind, Write}; use std::ops::Deref; +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("{0}")] +pub struct AnalyserError(String); -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"; @@ -58,6 +62,7 @@ fn gen_symbolics(symbolics: &[String]) -> String { code } +#[derive(Debug)] pub struct Analyser { ctx: InforMap, prop_specs: PropSpecs, @@ -88,11 +93,7 @@ impl Analyser { self.analyse_specs(specs, model_size) } - pub fn analyse_specs( - &mut self, - specs: Vec<Spec>, - model_size: usize, - ) -> Result<(), AnalyserError> { + 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() @@ -111,7 +112,7 @@ impl Analyser { } } - pub fn analyse_prop_decls( + fn analyse_prop_decls( &mut self, decls: Vec<&Decl>, model_size: usize, @@ -126,11 +127,7 @@ impl Analyser { result } - pub fn analyse_prop_decl( - &mut self, - decl: &Decl, - model_size: usize, - ) -> Result<(), AnalyserError> { + fn analyse_prop_decl(&mut self, decl: &Decl, model_size: usize) -> Result<(), AnalyserError> { match decl { Decl::PropertyDecl((id, _), term) => { let mut mterm = term.clone(); @@ -150,8 +147,8 @@ impl Analyser { 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); + let prop_tag = Tag::Prop(id.to_string()); + self.ctx.entry(id.to_string()).or_insert(prop_tag); if (symbolics.is_empty()) { self.prop_specs .insert(id.to_string(), (filename, vec!["n".to_string()])); @@ -165,7 +162,7 @@ impl Analyser { } } - pub fn analyse_bound_decls(&mut self, decls: Vec<&Decl>) -> Result<(), AnalyserError> { + 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) { @@ -176,7 +173,7 @@ impl Analyser { result } - pub fn analyse_bound_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> { + 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(); @@ -188,30 +185,30 @@ impl Analyser { let immut_ctx = self.ctx.clone(); // prevent generating existing name let mut i: usize = 0; - while immut_ctx.contains(&name) { + while immut_ctx.contains_key(&name) { name = name + &i.to_string(); i += 1; } - let con_tag = immut_ctx.get_id(c.clone()).unwrap(); + let con_tag = immut_ctx.get(&c).unwrap(); match con_tag { Tag::Con(elem_ty, _, tags) => { - self.ctx.update( + self.ctx.insert( c.clone(), - Tag::Con(elem_ty.to_string(), name.clone(), Box::new(tags.to_vec())), + Tag::Con(elem_ty.to_string(), name.clone(), tags.to_vec()), ); } _ => { return Err("Not a valid container declaration.".to_string()); } } - self.ctx.put(name, bound_tag); + self.ctx.entry(name).or_insert(bound_tag); Ok(()) } _ => Err("Not a valid bound declaration".to_string()), } } - pub fn analyse_contype_decls(&mut self, decls: Vec<&Decl>) -> Result<(), AnalyserError> { + 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) { @@ -222,7 +219,7 @@ impl Analyser { result } - pub fn analyse_contype_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> { + 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)) => { @@ -232,12 +229,12 @@ impl Analyser { Box::new(ins.clone().into_iter().collect::<Vec<String>>()), ); tags.push(i_tag); - match self.analyse_ref(r.deref()) { + match self.analyse_ref(r) { 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); + let con_tag = Tag::Con(t, String::new(), tags); + self.ctx.entry(c).or_insert(con_tag); Ok(()) } Err(e) => Err(e), @@ -249,7 +246,7 @@ impl Analyser { fn analyse_ref(&self, r: &Refinement) -> Result<Vec<Tag>, AnalyserError> { match r { - Refinement::Prop(term) => match term.deref() { + Refinement::Prop(term) => match term { Term::AppTerm(term1, term2) => match self.retrive_ref_term(term1) { Ok(t) => { let tags = vec![t.clone()]; @@ -271,7 +268,7 @@ impl Analyser { fn retrive_ref_term(&self, term: &Term) -> Result<&Tag, AnalyserError> { match term { - Term::VarTerm(id) => match self.ctx.get_id(id.to_string()) { + Term::VarTerm(id) => match self.ctx.get(&id.to_string()) { Some(t) => match t { Tag::Prop(_) => Ok(t), _ => Err(id.to_string() + " does not have a valid property"), @@ -282,7 +279,7 @@ impl Analyser { } } - pub fn analyse_term( + fn analyse_term( term: &mut Term, is_outter_app: bool, is_quantifier: bool, @@ -291,7 +288,7 @@ impl Analyser { ) -> String { match term { Term::LitTerm(lit) => { - if (lit.to_string() == *"true") { + if lit == "true" { "#t".to_string() } else { "#f".to_string() @@ -318,7 +315,7 @@ impl Analyser { 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::VarTerm("cdr".to_string())), Box::new(term.clone()), ); Self::analyse_term(term, is_outter_app, is_quantifier, cdr_added, symbolics) diff --git a/primrose/primrose/src/bounded_ops.rs b/primrose/primrose/src/bounded_ops.rs index 2d16b2a..7c0f485 100644 --- a/primrose/primrose/src/bounded_ops.rs +++ b/primrose/primrose/src/bounded_ops.rs @@ -13,16 +13,16 @@ pub fn generate_bounded_ops() -> BoundedOps { "push".to_string(), Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Stack".to_string()])), + 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()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Stack".to_string()])), + Bounds::from(["Stack".to_string()]), )), )), ), @@ -31,9 +31,9 @@ pub fn generate_bounded_ops() -> BoundedOps { "pop".to_string(), Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Stack".to_string()])), + Bounds::from(["Stack".to_string()]), )), Box::new(Type::Var(TypeVar::new("T".to_string()))), ), diff --git a/primrose/primrose/src/description.rs b/primrose/primrose/src/description.rs index 2a6c74c..b1bdd5d 100644 --- a/primrose/primrose/src/description.rs +++ b/primrose/primrose/src/description.rs @@ -3,6 +3,8 @@ use std::collections::HashMap; use crate::parser::Id; +pub type InforMap = HashMap<Id, Tag>; + pub type Description = String; type ElemTypeName = String; type ConName = String; @@ -10,9 +12,19 @@ type BoundName = String; #[derive(Eq, PartialEq, Clone, Debug)] pub enum Tag { - Prop(Box<Description>), // analysis of a property + /// Links to a property by name + Prop(Description), + /// TODO Bound((ConName, ElemTypeName), Box<Vec<Description>>), - Con(ElemTypeName, BoundName, Box<Vec<Tag>>), // analysis of a container type with refinements + /// Places bounds on a container type defined in the type context + Con( + /// The name of the type variable used for the element type + ElemTypeName, + /// The name of the bound + BoundName, + /// Bounds placed on the type + Vec<Tag>, + ), } impl Tag { @@ -42,45 +54,3 @@ impl Tag { } } } - -#[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 let std::collections::hash_map::Entry::Vacant(e) = self.infor_map.entry(id) { - e.insert(tag); - true - } else { - false - } - } - - 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() - } -} diff --git a/primrose/primrose/src/generator.rs b/primrose/primrose/src/generator.rs index b76a947..76c18b4 100644 --- a/primrose/primrose/src/generator.rs +++ b/primrose/primrose/src/generator.rs @@ -1,8 +1,13 @@ +//! Main driving code for generation + use indicatif::{ProgressBar, ProgressStyle}; +use log::debug; +use log::trace; use std::env; use std::fs; -use std::io::{BufRead, BufReader, Error, ErrorKind, Write}; +use std::io::{self, BufRead, BufReader, Write}; use std::process::Command; +use thiserror::Error; use crate::parser::{spec, Block, Spec}; use crate::type_check::TypeChecker; @@ -14,6 +19,7 @@ use crate::run_matching::{ cleanup_script, gen_match_script, initialise_match_setup, run_matching, setup_dirs, LANGDECL, }; use crate::spec_map::{MatchSetup, PropSpecs, ProvidedOps}; +use crate::type_check::TypeError; const CODEGEN: &str = "/*CODEGEN*/\n"; const CODEGENEND: &str = "/*ENDCODEGEN*/\n"; @@ -24,7 +30,7 @@ const CODEEND: &str = "/*ENDCODE*/"; const SPEC: &str = "/*SPEC*"; const SPECEND: &str = "*ENDSPEC*/"; -const LIB: &str = "./src/library/"; +const LIB: &str = "./primrose/src/library/"; const MATCHSCRIPT: &str = "./racket_specs/gen_match/match-script.rkt"; const IMPORT: &str = "use primrose::traits::container_constructor::ContainerConstructor;\n"; @@ -32,22 +38,42 @@ const TRAITCRATE: &str = "primrose::traits::"; const OPS: &str = "./racket_specs/gen_lib_spec/ops.rkt"; -type ErrorMessage = String; +#[derive(Error, Debug)] +pub enum Error { + #[error("error reading input: {0}")] + InputRead(io::Error), + + #[error("error parsing input: {0}")] + ParseError(#[from] peg::error::ParseError<peg::str::LineCol>), + + #[error("type error: {0}")] + TypeError(#[from] TypeError), + + #[error("analyser error: {0}")] + AnalyserError(String), + + #[error("error generating match script: {0}")] + GenMatchScript(io::Error), + + #[error("error executing solver: {0}")] + ExecutionError(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) + #[error("Unable to find a struct which matches the specification in the library")] + NoMatchingStructInLibrary, + + #[error("Error, cannot obtain provided operations from the library specifiction")] + ProvidedOperationsNotInLibrarySpec, } -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"); +pub fn readfile(filename: String) -> Result<String, io::Error> { + let contents = fs::read_to_string(filename)?; + Ok(mark_src_blocks(contents)) +} +pub fn writefile(pathname: String, filename: String, contents: String) -> Result<(), io::Error> { + // Create the directory if it does not exist let path = "./gen_code/".to_string() + &pathname + "/"; + fs::create_dir_all(&path)?; let mut output = fs::File::create(path.to_owned() + &filename)?; write!(output, "{}", contents)?; @@ -66,7 +92,7 @@ fn process_bound_elem_ty(t: &str, elem_ty: &str) -> String { TRAITCRATE.to_string() + t + "<" + elem_ty + ">" } -pub fn process_bound_decl(ctx: &InforMap) -> Result<String, ErrorMessage> { +pub fn process_bound_decl(ctx: &InforMap) -> String { let mut code = String::new(); let match_setup = initialise_match_setup(); for (id, tag) in ctx.iter() { @@ -82,16 +108,13 @@ pub fn process_bound_decl(ctx: &InforMap) -> Result<String, ErrorMessage> { _ => continue, } } - Ok(code) + + code } /// Generate the code to replace the container declaration in property specification -pub fn process_con_decl( - ctx: &InforMap, - prop_specs: &PropSpecs, -) -> Result<Vec<String>, ErrorMessage> { +pub fn process_con_decl(ctx: &InforMap, prop_specs: &PropSpecs) -> Result<Vec<String>, Error> { 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 @@ -122,7 +145,7 @@ pub fn process_con_decl( 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()); + return Err(Error::NoMatchingStructInLibrary); } else { let opt = struct_choices.join(", "); code = code @@ -169,7 +192,7 @@ pub fn process_con_decl( 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()); + return Err(Error::NoMatchingStructInLibrary); } else { let opt = struct_choices.join(", "); for struct_choice in struct_choices { @@ -195,7 +218,7 @@ pub fn process_con_decl( } } -fn write_provided_ops(provided_ops: &ProvidedOps) -> Result<(), Error> { +fn write_provided_ops(provided_ops: &ProvidedOps) -> Result<(), io::Error> { let ops_path = OPS; let (code, ops) = provided_ops; let mut output = fs::File::create(ops_path)?; @@ -215,7 +238,7 @@ fn library_spec_lookup( bounds: Vec<Description>, prop_specs: &PropSpecs, match_setup: &MatchSetup, -) -> Result<Vec<String>, ErrorMessage> { +) -> Result<Vec<String>, Error> { let pb = ProgressBar::new_spinner(); pb.enable_steady_tick(200); pb.set_style( @@ -244,10 +267,7 @@ fn library_spec_lookup( match write_provided_ops(provided_ops) { Ok(_) => {} Err(_) => { - return Err( - "Error, cannot obtain provided operations from the library specifiction" - .to_string(), - ); + return Err(Error::ProvidedOperationsNotInLibrarySpec); } } let mut is_match = false; @@ -257,34 +277,22 @@ fn library_spec_lookup( let (prop_file, symbolics) = prop_specs .get(p) .expect(&("Error: No property specification found for: ".to_string() + &p)); - match gen_match_script( + 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()); - } + ) + .map_err(Error::GenMatchScript)?; + let r = run_matching(MATCHSCRIPT.to_string()).map_err(Error::ExecutionError)?; + // true - match; false - not match + if (r) { + is_partial_match = true; + } else { + is_partial_match = false; + break; } } is_match = is_partial_match; @@ -308,24 +316,34 @@ fn library_spec_lookup( } /// Process the given file, returning code for all possible types -pub fn gen_outputs(filename: String, model_size: usize) -> Result<Vec<String>, ErrorMessage> { +pub fn gen_outputs(filename: String, model_size: usize) -> Result<Vec<String>, Error> { + debug!("Setting up directories"); setup_dirs(); - println!("Ready..."); - let f = readfile(filename); - let blocks = spec::prog(&f).map_err(|_| "Error, invalid source code.".to_string())?; + debug!("Reading and pre-processing input"); + let f = readfile(filename).map_err(Error::InputRead)?; + + debug!("Parsing into blocks"); + let blocks = spec::prog(&f)?; // Type Checking + debug!("Running type checker"); let mut tc = TypeChecker::new(); tc.check_prog(blocks.clone())?; + trace!("Results of type checking: {:#?}", &tc); // Run Analysis + debug!("Running analysis"); let mut analyser = Analyser::new(); - analyser.analyse_prog(blocks.clone(), model_size)?; + analyser + .analyse_prog(blocks.clone(), model_size) + .map_err(Error::AnalyserError)?; + trace!("Results of analysis: {:#?}", &analyser); - let code = process_bound_decl(analyser.get_ctx())?; + debug!("Processing bound declarations"); + let code = process_bound_decl(analyser.get_ctx()); - // generate con types according to the information in con decl + debug!("Generating con types according to the information in con decl"); let mut gen_code = Vec::new(); let gen_con_code = process_con_decl(analyser.get_ctx(), analyser.get_prop_specs())?; for generated_code in gen_con_code.iter() { @@ -347,19 +365,19 @@ pub fn gen_outputs(filename: String, model_size: usize) -> Result<Vec<String>, E /// Read input from the filename, calculate all valid implementations, and output them to seperate files in output_path pub fn run(input: String, output_path: String, model_size: usize) -> Result<(), Error> { - match gen_outputs(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 += 1; - } - Ok(()) - } - Err(e) => Err(Error::new(ErrorKind::Other, e.to_string())), + debug!("Generating candidate code outputs"); + let gen_code = gen_outputs(input, model_size)?; + + debug!("Writing {} different outputs", gen_code.len()); + 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 += 1; } + + Ok(()) } /// Mark all parts of the code so everything is between either CODE and CODEEND diff --git a/primrose/primrose/src/inference.rs b/primrose/primrose/src/inference.rs index 0164755..67f710d 100644 --- a/primrose/primrose/src/inference.rs +++ b/primrose/primrose/src/inference.rs @@ -63,7 +63,7 @@ impl TypeEnv { let mut tv = Type::Var(tvg.gen()); let mut env = self.clone(); if (!bounds.is_empty()) { - tv = Type::Con(Box::new("Con".to_string()), Box::new(tv), bounds.clone()); + tv = Type::Con("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(); diff --git a/primrose/primrose/src/main.rs b/primrose/primrose/src/main.rs index f39444b..b6de2c4 100644 --- a/primrose/primrose/src/main.rs +++ b/primrose/primrose/src/main.rs @@ -1,34 +1,46 @@ +use log::info; use primrose::generator::run; use std::env; -use std::io::{Error, ErrorKind}; +use std::error::Error; -fn main() -> Result<(), Error> { +fn main() -> Result<(), Box<dyn Error>> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + let (file_name, output_name, model_size) = + parse_args().map_err(Into::<Box<dyn Error>>::into)?; + + info!( + "Running on {}, outputting to {}, with model size {}", + file_name, output_name, model_size + ); + run(file_name, output_name, model_size)?; + + Ok(()) +} + +fn parse_args() -> Result<(String, String, usize), &'static str> { let args: Vec<String> = env::args().collect(); if args.len() == 1 { - // skip the first arg - let _ = run( + Ok(( "./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")); + return Err("Invalid model size"); } }; let model_size = model_size_input as usize; - let _ = run( + + Ok(( "./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")) + return Err("Usage: <file_name> [output_path] [model_size]"); } } diff --git a/primrose/primrose/src/parser.rs b/primrose/primrose/src/parser.rs index 580c6b5..f4fc809 100644 --- a/primrose/primrose/src/parser.rs +++ b/primrose/primrose/src/parser.rs @@ -12,15 +12,15 @@ pub type Literal = String; #[derive(Clone, Debug)] pub enum Refinement { - Prop(Box<Term>), + Prop(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>), + LitTerm(Literal), + VarTerm(Id), + LambdaTerm((Id, Bounds), Box<Term>), AppTerm(Box<Term>, Box<Term>), } @@ -53,8 +53,8 @@ impl ToString for Term { #[derive(Clone, Debug)] pub enum Decl { - PropertyDecl((Box<Id>, Box<Type>), Box<Term>), - ConTypeDecl(Box<Type>, (Box<Id>, Box<Bounds>, Box<Refinement>)), + PropertyDecl((Id, Type), Box<Term>), + ConTypeDecl(Type, (Id, Bounds, Refinement)), } impl Decl { @@ -95,6 +95,20 @@ impl Block { matches!(self, Block::CodeBlock(_, _)) } + pub fn spec(&self) -> &[Decl] { + match self { + Block::SpecBlock(spec, _) => spec, + _ => &[], + } + } + + pub fn code(&self) -> &str { + match self { + Block::CodeBlock(code, _) => code, + _ => "", + } + } + pub fn extract_spec(&self) -> Spec { match self { Block::SpecBlock(spec, _) => spec.to_vec(), @@ -134,7 +148,7 @@ pub grammar spec() for str { pub rule ty() -> Type = precedence! { n:name() "<" _ t:ty() _ ">" - { Type::Con(Box::new(n), Box::new(t), Box::new(Bounds::from(["Container".to_string()]))) } + { Type::Con(n, Box::new(t), Bounds::from(["Container".to_string()])) } -- n:name() { Type::Var(TypeVar::new(n)) } @@ -142,20 +156,20 @@ pub grammar spec() for str { pub rule term() -> Term = precedence!{ - lit: literal() { Term::LitTerm(Box::new(lit)) } + lit: literal() { Term::LitTerm(lit) } -- - v:id() { Term::VarTerm(Box::new(v)) } + v:id() { Term::VarTerm(v) } -- - "\\" v:id() _ "->" _ t:term() { Term::LambdaTerm((Box::new(v), Box::default()), Box::new(t)) } + "\\" v:id() _ "->" _ t:term() { Term::LambdaTerm((v, std::collections::HashSet::default()), Box::new(t)) } -- - "\\" v:id() _ "<:" _ "(" _ b:bounds() _ ")" _ "->" _ t:term() { Term::LambdaTerm((Box::new(v), Box::new(b)), Box::new(t)) } + "\\" v:id() _ "<:" _ "(" _ b:bounds() _ ")" _ "->" _ t:term() { Term::LambdaTerm((v, 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)) } + t:term() { Refinement::Prop(t) } -- "(" _ p1:refinement() __ "and" __ p2:refinement() _ ")" { Refinement::AndProps(Box::new(p1), Box::new(p2)) } } @@ -167,12 +181,12 @@ pub grammar spec() for str { = precedence! { _ "property" __ p:id() _ "<" _ ty:ty() _ ">" _ "{" _ t:term() _ "}" _ { - Decl::PropertyDecl((Box::new(p), Box::new(ty)), Box::new(t)) + Decl::PropertyDecl((p, 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))) + Decl::ConTypeDecl(ty, (c, b, t)) } } diff --git a/primrose/primrose/src/type_check.rs b/primrose/primrose/src/type_check.rs index fa9091f..4314a5c 100644 --- a/primrose/primrose/src/type_check.rs +++ b/primrose/primrose/src/type_check.rs @@ -1,3 +1,6 @@ +//! Performs type checking for specifications +use thiserror::Error; + use crate::generator::readfile; use crate::inference::TypeEnv; use crate::parser::{spec, Block, Decl, Id, Prog, Refinement, Spec, Term}; @@ -5,21 +8,29 @@ use crate::types::{Bounds, Type, TypeScheme, TypeVar, TypeVarGen}; use std::ops::Deref; -type TypeError = String; +#[derive(Error, Debug)] +#[error("{0}")] +pub struct TypeError(String); +#[derive(Debug)] pub struct TypeChecker { global_ctx: TypeEnv, tvg: TypeVarGen, } impl TypeChecker { + /// Create a new type checking context. pub fn new() -> TypeChecker { - TypeChecker { + let mut tc = TypeChecker { global_ctx: TypeEnv::new(), tvg: TypeVarGen::new(), - } + }; + tc.predefined(); + + tc } + /// Add types for pre-defined functions fn predefined(&mut self) { // put for_all_unique_pair into context let binary_fn1 = Type::Fun( @@ -35,9 +46,9 @@ impl TypeChecker { vars: Vec::new(), ty: Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Container".to_string()])), + Bounds::from(["Container".to_string()]), )), Box::new(Type::Fun(Box::new(binary_fn1), Box::new(Type::Bool()))), ), @@ -58,9 +69,9 @@ impl TypeChecker { vars: Vec::new(), ty: Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Container".to_string()])), + Bounds::from(["Container".to_string()]), )), Box::new(Type::Fun(Box::new(binary_fn2), Box::new(Type::Bool()))), ), @@ -77,9 +88,9 @@ impl TypeChecker { vars: Vec::new(), ty: Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Container".to_string()])), + Bounds::from(["Container".to_string()]), )), Box::new(Type::Fun(Box::new(unary_fn), Box::new(Type::Bool()))), ), @@ -152,9 +163,9 @@ impl TypeChecker { Box::new(Type::Var(TypeVar::new("T".to_string()))), Box::new(Type::Fun( Box::new(Type::Con( - Box::new("Con".to_string()), + "Con".to_string(), Box::new(Type::Var(TypeVar::new("T".to_string()))), - Box::new(Bounds::from(["Container".to_string()])), + Bounds::from(["Container".to_string()]), )), Box::new(Type::Bool()), )), @@ -184,21 +195,24 @@ impl TypeChecker { ); } - pub fn get_ctx(&self) -> &TypeEnv { + /// Get the type context, including predefined types. + pub fn global_ctx(&self) -> &TypeEnv { &self.global_ctx } + /// Check an entire program 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) + + self.check_specs(&specs) } - pub fn check_specs(&mut self, specs: Vec<Spec>) -> Result<(), TypeError> { + /// Check a list of specifications + fn check_specs(&mut self, specs: &[Spec]) -> Result<(), TypeError> { let concat_specs = specs.concat(); let prop_decls: Vec<&Decl> = concat_specs .iter() @@ -208,212 +222,173 @@ impl TypeChecker { .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), - } + + self.check_prop_decls(&prop_decls)?; + self.check_contype_decls(&contype_decls)?; + self.check_bound_decls(&contype_decls) } - 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; - } - } + /// Check all bound declarations + fn check_bound_decls(&mut self, decls: &[&Decl]) -> Result<(), TypeError> { + for decl in decls.iter() { + self.check_bound_decl(decl)?; } - result + + Ok(()) } - 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(()) + /// Check a single bound declaration + fn check_bound_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + let Decl::ConTypeDecl(_, (_, ins, _)) = decl else { + return Err(TypeError("Not a valid bound declaration".to_string())); + }; + + // Duplicate bound name checking + for i in ins.iter() { + if self.global_ctx.get(&i.to_string()).is_some() { + return Err(TypeError("Duplicate bound name declaration".to_string())); } - _ => Err("Not a valid bound declaration".to_string()), + + // TODO: check each bound is a valid rust trait } + Ok(()) } - 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; - } - } + /// Check a list of property declarations + fn check_prop_decls(&mut self, decls: &[&Decl]) -> Result<(), TypeError> { + for decl in decls.iter() { + self.check_prop_decl(decl)?; } - result + + Ok(()) } - 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" { - self.global_ctx.insert( - id.to_string(), - TypeScheme { - vars: Vec::new(), - 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 - } - ); - 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), - } - } + /// Check a single property declaration + fn check_prop_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + let Decl::PropertyDecl((id, ty), term) = decl else { + return Err(TypeError("Not a valid property declaration".to_string())); + }; + + // Duplicate property decl checking + if self.global_ctx.get(&id.to_string()).is_some() { + return Err(TypeError("Duplicate property declaration".to_string())); + } + + // check well formedness + let ty = self + .global_ctx + .type_inference(term, &mut self.tvg) + .map_err(TypeError)?; + + // it should have type Con<T> -> Bool + let Type::Fun(ref t1, ref t2) = ty else { + return Err(TypeError( + "Not a valid property decl: should have type Con<T> -> Bool".to_string(), + )); + }; + + match (t1.deref(), t2.deref()) { + (Type::Con(n, t, _), Type::Bool()) => { + if n == "Con" { + self.global_ctx.insert( + id.to_string(), + TypeScheme { + vars: Vec::new(), + ty, + }, + ); + Ok(()) + } else { + Err(TypeError("Not a valid property decl: input does not have basic container type Con<T>".to_string())) } } - _ => Err("Not a valid property declaration".to_string()), + (_, Type::Bool()) => { + self.global_ctx.insert( + id.to_string(), + TypeScheme { + vars: Vec::new(), + ty, + }, + ); + Ok(()) + } + _ => Err(TypeError( + "Not a valid property decl: should have type Con<T> -> Bool".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; - } - } + /// Check all container type declarations + fn check_contype_decls(&mut self, decls: &[&Decl]) -> Result<(), TypeError> { + for decl in decls.iter() { + self.check_contype_decl(decl)?; } - result + + Ok(()) } - 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()), + /// Check a single container type declaration + fn check_contype_decl(&mut self, decl: &Decl) -> Result<(), TypeError> { + let Decl::ConTypeDecl(con_ty, (vid, ins, r)) = decl else { + return Err(TypeError( + "Not a valid container type declaration".to_string(), + )); + }; + + // Duplicate container type decl checking + if self.global_ctx.get(&con_ty.to_string()).is_some() { + return Err(TypeError( + "Duplicate container type declaration".to_string(), + )); } + + // Insert into local context + let con = Type::Con( + "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, + }, + ); + + // Check that it makes sense in the local context + self.check_ref(&mut local_ctx, r)?; + + // If so, insert into global context + self.global_ctx.insert( + decl.get_name(), + TypeScheme { + vars: Vec::new(), + ty: con_ty.clone(), + }, + ); + Ok(()) } - pub fn check_ref(&mut self, ctx: &mut TypeEnv, r: &Refinement) -> Result<(), TypeError> { + 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), + let t = ctx.type_inference(t, &mut self.tvg).map_err(TypeError)?; + + // t has to be boolean + if t.is_bool() { + Ok(()) + } else { + Err(TypeError( + "The refinement has to be evaluated to a Bool type.".to_string(), + )) } } - 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), - }, + Refinement::AndProps(r1, r2) => { + self.check_ref(ctx, r1)?; + self.check_ref(ctx, r2) + } } } } - -// 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()); -// } - -// } diff --git a/primrose/primrose/src/types.rs b/primrose/primrose/src/types.rs index 608912b..667601e 100644 --- a/primrose/primrose/src/types.rs +++ b/primrose/primrose/src/types.rs @@ -15,7 +15,7 @@ pub enum Type { Bool(), Int(), Var(TypeVar), - Con(Box<Name>, Box<Type>, Box<Bounds>), + Con(Name, Box<Type>, Bounds), Fun(Box<Type>, Box<Type>), } @@ -93,7 +93,7 @@ impl Type { // Unify con type (Type::Con(n1, t1, _), Type::Con(n2, t2, _)) => { - if n1.to_string() != n2.to_string() { + if n1 != n2 { Err("Cannot unify two different container".to_string()) } else { t1.mgu(t2) @@ -152,6 +152,7 @@ impl Subst { } // Fresh variable generator +#[derive(Debug)] pub struct TypeVarGen { supply: usize, } @@ -241,11 +242,9 @@ impl Types for Type { match self { Type::Var(n) => s.get(n).cloned().unwrap_or(self.clone()), Type::Fun(t1, t2) => Type::Fun(Box::new(t1.apply(s)), Box::new(t2.apply(s))), - Type::Con(n, t, bounds) => Type::Con( - Box::new(n.to_string()), - Box::new(t.apply(s)), - bounds.clone(), - ), + Type::Con(n, t, bounds) => { + Type::Con(n.to_string(), Box::new(t.apply(s)), bounds.clone()) + } _ => self.clone(), } } |