aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--primrose/Cargo.toml7
-rw-r--r--primrose/primrose/Cargo.toml3
-rw-r--r--primrose/primrose/src/analysis.rs59
-rw-r--r--primrose/primrose/src/bounded_ops.rs12
-rw-r--r--primrose/primrose/src/description.rs58
-rw-r--r--primrose/primrose/src/generator.rs156
-rw-r--r--primrose/primrose/src/inference.rs2
-rw-r--r--primrose/primrose/src/main.rs38
-rw-r--r--primrose/primrose/src/parser.rs42
-rw-r--r--primrose/primrose/src/type_check.rs363
-rw-r--r--primrose/primrose/src/types.rs13
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(),
}
}