aboutsummaryrefslogtreecommitdiff
path: root/src/crates/primrose
diff options
context:
space:
mode:
Diffstat (limited to 'src/crates/primrose')
-rw-r--r--src/crates/primrose/Cargo.toml22
-rw-r--r--src/crates/primrose/benches/criterion_benchmark.rs235
-rw-r--r--src/crates/primrose/src/analysis.rs372
-rw-r--r--src/crates/primrose/src/bounded_ops.rs43
-rw-r--r--src/crates/primrose/src/codegen.rs108
-rw-r--r--src/crates/primrose/src/description.rs54
-rw-r--r--src/crates/primrose/src/error.rs36
-rw-r--r--src/crates/primrose/src/inference.rs111
-rw-r--r--src/crates/primrose/src/lib.rs23
-rw-r--r--src/crates/primrose/src/library_specs.rs340
-rw-r--r--src/crates/primrose/src/main.rs91
-rw-r--r--src/crates/primrose/src/parser.rs203
-rw-r--r--src/crates/primrose/src/run_matching.rs109
-rw-r--r--src/crates/primrose/src/selector.rs206
-rw-r--r--src/crates/primrose/src/source_file.rs47
-rw-r--r--src/crates/primrose/src/spec_map.rs18
-rw-r--r--src/crates/primrose/src/tools/mod.rs89
-rw-r--r--src/crates/primrose/src/type_check.rs388
-rw-r--r--src/crates/primrose/src/types.rs282
19 files changed, 2777 insertions, 0 deletions
diff --git a/src/crates/primrose/Cargo.toml b/src/crates/primrose/Cargo.toml
new file mode 100644
index 0000000..80dfdab
--- /dev/null
+++ b/src/crates/primrose/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "primrose"
+version = "0.1.0"
+authors = ["Xueying Qin <qinxy1995@gmail.com>"]
+edition = "2021"
+
+[dependencies]
+log = { workspace = true }
+env_logger = { workspace = true }
+
+rand = { workspace = true }
+thiserror = { workspace = true }
+peg = "0.8.0"
+
+primrose-library = { path = "../library" }
+
+[dev-dependencies]
+criterion = "0.3.5"
+
+[[bench]]
+name = "criterion_benchmark"
+harness = false
diff --git a/src/crates/primrose/benches/criterion_benchmark.rs b/src/crates/primrose/benches/criterion_benchmark.rs
new file mode 100644
index 0000000..6bdc214
--- /dev/null
+++ b/src/crates/primrose/benches/criterion_benchmark.rs
@@ -0,0 +1,235 @@
+use criterion::{criterion_group, criterion_main, Criterion};
+
+use primrose_library::traits::Container;
+use primrose_library::LazyUniqueVec;
+use rand::rngs::StdRng;
+use rand::seq::SliceRandom;
+
+use rand::SeedableRng;
+use std::collections::{BTreeSet, HashSet};
+use std::mem::size_of;
+
+use std::vec::Vec;
+
+// one search at the
+fn gen_dataset_1() -> Vec<u32> {
+ // avoid duplication
+ let size = 1024 * 1024; // 1 MB
+ let amount = size / size_of::<u32>();
+ let mut data: Vec<u32> = (1..amount as u32).collect(); //ensure no duplication
+ let mut rng = StdRng::seed_from_u64(222);
+ data.shuffle(&mut rng);
+ data
+}
+
+fn gen_dataset_128() -> Vec<u32> {
+ // avoid duplication
+ let size = 128 * 1024 * 1024; // 128 MB
+ let amount = size / size_of::<u32>();
+ let mut data: Vec<u32> = (1..amount as u32).collect(); //ensure no duplication
+ let mut rng = StdRng::seed_from_u64(222);
+ data.shuffle(&mut rng);
+ data
+}
+
+fn gen_dataset_256() -> Vec<u32> {
+ // avoid duplication
+ let size = 256 * 1024 * 1024; // 256 MB
+ let amount = size / size_of::<u32>();
+ let mut data: Vec<u32> = (1..amount as u32).collect(); //ensure no duplication
+ let mut rng = StdRng::seed_from_u64(222);
+ data.shuffle(&mut rng);
+ data
+}
+
+fn gen_dataset_512() -> Vec<u32> {
+ // avoid duplication
+ let size = 512 * 1024 * 1024; // 512 MB
+ let amount = size / size_of::<u32>();
+ let mut data: Vec<u32> = (1..amount as u32).collect(); //ensure no duplication
+ let mut rng = StdRng::seed_from_u64(222);
+ data.shuffle(&mut rng);
+ data
+}
+
+fn btreeset_insertion_1m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut BTreeSet::new();
+ let data = gen_dataset_1();
+ c.bench_function("btreeset insertion 1MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn btreeset_insertion_128m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut BTreeSet::new();
+ let data = gen_dataset_128();
+ c.bench_function("btreeset insertion 128MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn btreeset_insertion_256m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut BTreeSet::new();
+ let data = gen_dataset_256();
+ c.bench_function("btreeset insertion 256MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn btreeset_insertion_512m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut BTreeSet::new();
+ let data = gen_dataset_512();
+ c.bench_function("btreeset insertion 512MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn hashset_insertion_1m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut HashSet::new();
+ let data = gen_dataset_1();
+ c.bench_function("hashset insertion 1MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn hashset_insertion_128m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut HashSet::new();
+ let data = gen_dataset_128();
+ c.bench_function("hashset insertion 128MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn hashset_insertion_256m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut HashSet::new();
+ let data = gen_dataset_256();
+ c.bench_function("hashset insertion 256MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn hashset_insertion_512m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut HashSet::new();
+ let data = gen_dataset_512();
+ c.bench_function("hashset insertion 512MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn lazy_uniuqe_vec_insertion_1m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut LazyUniqueVec::new();
+ let data = gen_dataset_1();
+ c.bench_function("lazy unique vector insertion 1MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn lazy_uniuqe_vec_insertion_128m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut LazyUniqueVec::new();
+ let data = gen_dataset_128();
+ c.bench_function("lazy unique vector insertion 128MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn lazy_uniuqe_vec_insertion_256m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut LazyUniqueVec::new();
+ let data = gen_dataset_256();
+ c.bench_function("lazy unique vector insertion 256MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+fn lazy_uniuqe_vec_insertion_512m(c: &mut Criterion) {
+ let s: &mut dyn Container<u32> = &mut LazyUniqueVec::new();
+ let data = gen_dataset_512();
+ c.bench_function("lazy unique vector insertion 512MB", |b| {
+ b.iter(|| {
+ for val in data.iter() {
+ s.insert(*val);
+ }
+ s.contains(&1024);
+ })
+ });
+}
+
+criterion_group! {
+ name = insertion_1m;
+ config = Criterion::default().sample_size(10);
+ targets = btreeset_insertion_1m, hashset_insertion_1m, lazy_uniuqe_vec_insertion_1m
+}
+
+criterion_group! {
+ name = insertion_128m;
+ config = Criterion::default().sample_size(10);
+ targets = btreeset_insertion_128m, hashset_insertion_128m, lazy_uniuqe_vec_insertion_128m
+}
+
+criterion_group! {
+ name = insertion_256m;
+ config = Criterion::default().sample_size(10);
+ targets = btreeset_insertion_256m, hashset_insertion_256m, lazy_uniuqe_vec_insertion_256m
+}
+
+criterion_group! {
+ name = insertion_512m;
+ config = Criterion::default().sample_size(10);
+ targets = btreeset_insertion_512m, hashset_insertion_512m, lazy_uniuqe_vec_insertion_512m
+}
+
+criterion_main!(insertion_1m, insertion_128m, insertion_256m, insertion_512m);
diff --git a/src/crates/primrose/src/analysis.rs b/src/crates/primrose/src/analysis.rs
new file mode 100644
index 0000000..ec2f803
--- /dev/null
+++ b/src/crates/primrose/src/analysis.rs
@@ -0,0 +1,372 @@
+use crate::description::{InforMap, Tag};
+use crate::parser::{Decl, Prog, Refinement, Spec, Term};
+use crate::spec_map::PropSpecs;
+
+use std::fs;
+use std::io::{Error, Write};
+
+pub 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/";
+
+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: &[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
+}
+
+#[derive(Debug)]
+pub struct Analyser {
+ ctx: InforMap,
+ prop_specs: PropSpecs,
+}
+
+impl Analyser {
+ pub fn new() -> Analyser {
+ Analyser {
+ ctx: InforMap::new(),
+ prop_specs: PropSpecs::new(),
+ }
+ }
+
+ pub fn ctx(&self) -> &InforMap {
+ &self.ctx
+ }
+
+ pub fn 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)
+ }
+
+ 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),
+ }
+ }
+
+ 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
+ }
+
+ 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(&["n".to_string()]);
+ if !symbolics.is_empty() {
+ symbolics_provided = gen_symbolics(&symbolics);
+ }
+ self.write_prop_spec_file(filename.clone(), code, symbolics_provided, model_size)
+ .map_err(|e| format!("{}", e))?;
+ 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()]));
+ } else {
+ self.prop_specs
+ .insert(id.to_string(), (filename, symbolics));
+ }
+ Ok(())
+ }
+ _ => Err("Not a valid property declaration".to_string()),
+ }
+ }
+
+ 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
+ }
+
+ fn analyse_bound_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> {
+ match decl {
+ Decl::ConTypeDecl(con_ty, (_, ins, _)) => {
+ let (c, t) = con_ty.get_con_elem().unwrap();
+ let mut name = c.clone() + "Trait";
+ let bound_tag = Tag::Bound(
+ (c.clone(), t),
+ 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_key(&name) {
+ name = name + &i.to_string();
+ i += 1;
+ }
+ let con_tag = immut_ctx.get(&c).unwrap();
+ match con_tag {
+ Tag::Con(elem_ty, _, tags) => {
+ self.ctx.insert(
+ c.clone(),
+ Tag::Con(elem_ty.to_string(), name.clone(), tags.to_vec()),
+ );
+ }
+ _ => {
+ return Err("Not a valid container declaration.".to_string());
+ }
+ }
+ self.ctx.entry(name).or_insert(bound_tag);
+ Ok(())
+ }
+ _ => Err("Not a valid bound declaration".to_string()),
+ }
+ }
+
+ 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
+ }
+
+ fn analyse_contype_decl(&mut self, decl: &Decl) -> Result<(), AnalyserError> {
+ let mut tags = Vec::<Tag>::new();
+ match decl {
+ Decl::ConTypeDecl(con_ty, (_, ins, r)) => {
+ let (c, t) = con_ty.get_con_elem().unwrap();
+ let i_tag = Tag::Bound(
+ (c.clone(), t.clone()),
+ ins.clone().into_iter().collect::<Vec<String>>(),
+ );
+ tags.push(i_tag);
+ 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(), tags);
+ self.ctx.entry(c).or_insert(con_tag);
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
+ _ => Err("Not a valid container type declaration".to_string()),
+ }
+ }
+
+ fn analyse_ref(&self, r: &Refinement) -> Result<Vec<Tag>, AnalyserError> {
+ match r {
+ Refinement::Prop(term) => match term {
+ Term::App(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) {
+ Ok(tags1) => match self.analyse_ref(r2) {
+ 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::Var(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"),
+ },
+ _ => Err("Undefined variable: ".to_string() + id),
+ },
+ _ => Err("Should be a varible term".to_string()),
+ }
+ }
+
+ fn analyse_term(
+ term: &mut Term,
+ is_outter_app: bool,
+ is_quantifier: bool,
+ cdr_added: &mut Vec<String>,
+ symbolics: &mut Vec<String>,
+ ) -> String {
+ match term {
+ Term::Lit(lit) => {
+ if lit == "true" {
+ "#t".to_string()
+ } else {
+ "#f".to_string()
+ }
+ }
+ Term::Var(id) => id.to_string(),
+ Term::Lambda((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::App(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::App(
+ Box::new(Term::Var("cdr".to_string())),
+ Box::new(term.clone()),
+ );
+ Self::analyse_term(term, is_outter_app, is_quantifier, cdr_added, symbolics)
+ } else {
+ match ((*t1.clone()).is_quantifier(), *t2.clone()) {
+ (_, Term::App(_, _)) => {
+ 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)?;
+ write!(output, "{}", REQUIRE)?;
+ write!(output, "{}", EXTRAREQUIRE)?;
+ let list_model = gen_list_model(model_size);
+ write!(output, "{}", list_model)?;
+ write!(output, "{}", contents)?;
+ write!(output, "{}", symbolics)?;
+ Ok(())
+ }
+}
diff --git a/src/crates/primrose/src/bounded_ops.rs b/src/crates/primrose/src/bounded_ops.rs
new file mode 100644
index 0000000..0ca598f
--- /dev/null
+++ b/src/crates/primrose/src/bounded_ops.rs
@@ -0,0 +1,43 @@
+use crate::types::{Bounds, Type, TypeVar};
+
+use std::collections::HashMap;
+
+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(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ Bounds::from(["Stack".to_string()]),
+ )),
+ Box::new(Type::Fun(
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ Box::new(Type::Con(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ Bounds::from(["Stack".to_string()]),
+ )),
+ )),
+ ),
+ );
+ let pop = (
+ "pop".to_string(),
+ Type::Fun(
+ Box::new(Type::Con(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ Bounds::from(["Stack".to_string()]),
+ )),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ ),
+ );
+ ops.insert("Stack".to_string(), vec![push, pop]);
+ ops
+}
diff --git a/src/crates/primrose/src/codegen.rs b/src/crates/primrose/src/codegen.rs
new file mode 100644
index 0000000..d56c2b3
--- /dev/null
+++ b/src/crates/primrose/src/codegen.rs
@@ -0,0 +1,108 @@
+//! Generating rust code from results of
+
+use crate::{
+ description::{InforMap, Tag},
+ parser::Block,
+ selector::ContainerSelector,
+};
+
+const CODEGEN: &str = "/*CODEGEN*/\n";
+const CODEGENEND: &str = "/*ENDCODEGEN*/\n";
+const TRAITCRATE: &str = "primrose_library::traits::";
+const IMPORT: &str = "use primrose_library::traits::ContainerConstructor;\n";
+
+impl ContainerSelector {
+ /// Generate replacement code for the whole file, with the given `(tag_id, selection)` pairs.
+ /// This will generate invalid code if any selection is invalid, or panic if any `tag_id` is invalid.
+ /// Returns the original file with the generated code in place of the original specification.
+ pub fn gen_replacement_file<'a, T: Iterator<Item = (&'a String, &'a str)>>(
+ &self,
+ selections: T,
+ ) -> String {
+ let mut result = String::new();
+ for (tag_id, selection) in selections {
+ result += &self.gen_replacement_code(tag_id, selection);
+ }
+
+ // rest of the rust code, minus the spec stuff
+ for block in self.blocks.iter().filter(|block| block.is_code_block()) {
+ match block {
+ Block::CodeBlock(code, _) => result += code,
+ _ => unreachable!(),
+ };
+ }
+
+ result
+ }
+
+ /// Generate replacement code for the given tag, choosing the given library spec.
+ /// This will generate invalid code if selection is invalid, or panic if `tag_id` is invalid.
+ /// Returns only the required code, and not the rest of the code originally in the file.
+ pub fn gen_replacement_code(&self, tag_id: &String, selection: &str) -> String {
+ let tag = self.analyser.ctx().get(tag_id).expect("invalid tag_id");
+
+ let Tag::Con(elem_ty, i_name, _tags) = tag else {
+ panic!("tag_id was not Tag::Con");
+ };
+
+ // generated code at top
+ let mut result = String::new();
+ result += CODEGEN;
+ result += IMPORT;
+ result += &self.bounds_decl;
+ result += &gen_output_code(tag_id, elem_ty, selection, i_name);
+ result += CODEGENEND;
+
+ result
+ }
+}
+
+// TODO: Constructing a box like this is inefficient, and might affect performance of some programs
+fn gen_output_code(s: &str, elem_type: &str, chosen: &str, trait_name: &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}>;
+ type Bound = dyn {trait_name}<{elem_type}>;
+ fn new() -> Box<Self::Bound> {{
+ Box::new(Self::Impl::new())
+ }}
+}}
+"#
+ )
+}
+
+pub fn process_bound_decl(ctx: &InforMap) -> String {
+ let mut code = String::new();
+ 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,
+ }
+ }
+
+ code
+}
+
+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 {{}}
+"#
+ )
+}
+
+fn process_bound_elem_ty(t: &str, elem_ty: &str) -> String {
+ TRAITCRATE.to_string() + t + "<" + elem_ty + ">"
+}
diff --git a/src/crates/primrose/src/description.rs b/src/crates/primrose/src/description.rs
new file mode 100644
index 0000000..29eeb44
--- /dev/null
+++ b/src/crates/primrose/src/description.rs
@@ -0,0 +1,54 @@
+use std::collections::HashMap;
+
+pub type InforMap = HashMap<TagId, Tag>;
+pub type TagId = String;
+
+pub type Description = String;
+type ElemTypeName = String;
+type ConName = String;
+type BoundName = String;
+
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub enum Tag {
+ /// Links to a property by name
+ Prop(Description),
+ /// TODO
+ Bound((ConName, ElemTypeName), Vec<Description>),
+ /// 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 {
+ pub fn is_prop_tag(&self) -> bool {
+ matches!(self, Tag::Prop(..))
+ }
+
+ pub fn is_bound_tag(&self) -> bool {
+ matches!(self, Tag::Bound(..))
+ }
+
+ pub fn is_con_tag(&self) -> bool {
+ matches!(self, Tag::Con(..))
+ }
+
+ 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(),
+ }
+ }
+}
diff --git a/src/crates/primrose/src/error.rs b/src/crates/primrose/src/error.rs
new file mode 100644
index 0000000..990f80e
--- /dev/null
+++ b/src/crates/primrose/src/error.rs
@@ -0,0 +1,36 @@
+use std::io;
+
+use thiserror::Error;
+
+use crate::type_check::TypeError;
+
+/// The main error type for primrose
+#[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("library specification error: {0}")]
+ LibraryError(String),
+
+ #[error("analyser error: {0}")]
+ AnalyserError(String),
+
+ #[error("error generating match script: {0}")]
+ GenMatchScript(io::Error),
+
+ #[error("error executing solver: {0}")]
+ ExecutionError(String),
+
+ #[error("Unable to find a struct which matches the specification in the library")]
+ NoMatchingStructInLibrary,
+
+ #[error("Error, cannot obtain provided operations from the library specification")]
+ ProvidedOperationsNotInLibrarySpec,
+}
diff --git a/src/crates/primrose/src/inference.rs b/src/crates/primrose/src/inference.rs
new file mode 100644
index 0000000..9cf4783
--- /dev/null
+++ b/src/crates/primrose/src/inference.rs
@@ -0,0 +1,111 @@
+use std::collections::{HashMap, HashSet};
+
+use std::ops::{Deref, DerefMut};
+
+use crate::bounded_ops::generate_bounded_ops;
+use crate::parser::{Id, Term};
+use crate::types::{Subst, Type, TypeScheme, TypeVar, TypeVarGen, Types};
+
+/// 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().cloned().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())
+ }
+
+ // 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::Lit(_) => Ok((Subst::new(), Type::Bool())),
+ // Infer variable
+ Term::Var(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::Lambda((n, bounds), ref e) => {
+ let mut tv = Type::Var(tvg.gen());
+ let mut env = self.clone();
+ if !bounds.is_empty() {
+ 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();
+ 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::App(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.gen());
+ 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;
diff --git a/src/crates/primrose/src/lib.rs b/src/crates/primrose/src/lib.rs
new file mode 100644
index 0000000..a283f28
--- /dev/null
+++ b/src/crates/primrose/src/lib.rs
@@ -0,0 +1,23 @@
+mod analysis;
+mod bounded_ops;
+mod description;
+mod inference;
+mod parser;
+mod run_matching;
+mod source_file;
+mod spec_map;
+mod type_check;
+mod types;
+
+pub mod tools;
+
+mod codegen;
+mod selector;
+pub use selector::ContainerSelector;
+
+mod library_specs;
+pub use library_specs::LibSpec;
+pub use spec_map::LibSpecs;
+
+mod error;
+pub use error::Error;
diff --git a/src/crates/primrose/src/library_specs.rs b/src/crates/primrose/src/library_specs.rs
new file mode 100644
index 0000000..6b30ae6
--- /dev/null
+++ b/src/crates/primrose/src/library_specs.rs
@@ -0,0 +1,340 @@
+//! Process library files and extracts library specifications
+
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+
+use std::fs;
+
+use std::io::{Error, Write};
+use std::path::Path;
+
+use log::debug;
+
+use crate::spec_map::{Bounds, LibSpecs, 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;
+
+/// Specifications extracted from a library file
+#[derive(Debug, Clone)]
+pub struct LibSpec {
+ /// Name of the specification
+ pub spec_name: String,
+
+ /// Name of the specified structs
+ pub struct_name: String,
+
+ /// All specification code defined
+ pub specs: Vec<String>,
+
+ /// The provided rosette module name
+ pub provide: String,
+
+ /// The bounds of each operation
+ pub interface_provide_map: Bounds,
+
+ /// The provided operations
+ pub provided_ops: ProvidedOps,
+}
+
+impl LibSpec {
+ /// Process all library specifications in the given directory.
+ /// This will also write the required racket file for that spec (see [`Self::process`]).
+ pub fn process_all(dirname: &Path) -> Result<LibSpecs, ErrorMessage> {
+ let paths = fs::read_dir(dirname).unwrap();
+ let files: Vec<String> = paths
+ .into_iter()
+ .map(|p| p.unwrap())
+ .filter(|p| p.file_type().unwrap().is_file())
+ .map(|path| path.path().into_os_string().into_string().unwrap())
+ .filter(|path| !path.contains("/mod.rs") && !path.contains("/lib.rs"))
+ .collect();
+ let mut lib_specs = LibSpecs::new();
+ for path in files {
+ match Self::process(&path) {
+ Ok(spec) => {
+ lib_specs.insert(spec.struct_name.clone(), spec);
+ }
+ Err(e) => {
+ debug!(
+ "Failed to process library module {}: {}. Continuing anyway.",
+ &path, e
+ );
+ }
+ }
+ }
+ Ok(lib_specs)
+ }
+
+ /// Process a single library specification file.
+ /// This will also write the required racket file for that spec.
+ pub fn process(filename: &str) -> Result<Self, ErrorMessage> {
+ let result = Self::read(filename);
+ match result {
+ Ok(spec) => {
+ let _spec_name = format!("{}.rkt", &spec.spec_name);
+ let state = write_lib_file(&spec.spec_name, &spec.specs, &spec.provide);
+ if state.is_err() {
+ return Err("Unable to create lib specification file".to_string());
+ }
+ Ok(spec)
+ }
+ Err(e) => Err(e),
+ }
+ }
+
+ /// Read all library specification files in the given directory.
+ pub fn read_all(dirname: &Path) -> Result<LibSpecs, ErrorMessage> {
+ let paths = fs::read_dir(dirname).map_err(|_e| "Library spec directory does not exist")?;
+
+ let _lib_specs = LibSpecs::new();
+ Ok(paths
+ .into_iter()
+ .flatten()
+ .filter(|p| p.file_type().unwrap().is_file())
+ .flat_map(|path| path.path().into_os_string().into_string())
+ .filter(|path| !path.contains("/mod.rs") && !path.contains("/lib.rs"))
+ .flat_map(|path| match Self::read(&path) {
+ Ok(spec) => Some((spec.struct_name.clone(), spec)),
+ Err(e) => {
+ debug!(
+ "Failed to process library module {}: {}. Continuing anyway.",
+ &path, e
+ );
+ None
+ }
+ })
+ .collect())
+ }
+
+ /// Read and parse a library specification file
+ pub fn read(filename: &str) -> Result<Self, 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) {
+ 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 _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.first().unwrap().trim().to_string();
+ let v3: Vec<&str> = s3.split(' ').collect();
+ let spec_name = v3.first().unwrap().trim().to_string();
+ let struct_name = v3.get(1).unwrap().trim().to_string();
+ let s1 = v1.first().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 interface_name = v5.first().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(RawLibSpec {
+ contents,
+ code: mut result,
+ op_infos,
+ provided_ops: mut ops,
+ }) => {
+ code.append(&mut result);
+ interface_info.insert(interface_name.clone(), op_infos);
+ interfaces.push(interface_name.clone());
+ provided_ops.append(&mut ops);
+ trimed_contents = contents;
+ }
+ Err(e) => {
+ return Err(e);
+ }
+ }
+ }
+ let (provide, interface_provide_map) = generate_provide(interface_info);
+ Ok(LibSpec {
+ spec_name,
+ struct_name,
+ specs: code.clone(),
+ provide,
+ interface_provide_map,
+ provided_ops: (code, provided_ops),
+ })
+ }
+ }
+ }
+}
+
+/// Extracted, but not yet parsed directives from a library spec
+struct RawLibSpec {
+ /// The contents of the LIBSPEC block
+ contents: String,
+ /// The code define by the block
+ code: Vec<String>,
+ /// Hoare triples for each operation
+ op_infos: BTreeMap<String, (String, String, String)>,
+ /// Provided operation names
+ provided_ops: Vec<String>,
+}
+
+fn is_next_pragma_impl(src: &str) -> 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: &str) -> bool {
+ src.contains(IMPL)
+}
+
+fn has_pragma_spec(src: &str) -> bool {
+ src.contains(LIBSPEC)
+}
+
+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)
+}
+
+/// Extract the relevant LIBSPEC blocks
+fn extract_lib_specs(src: String) -> Result<RawLibSpec, 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.is_empty() && !is_next_pragma_impl(contents) {
+ 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.first().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(RawLibSpec {
+ contents: contents.to_string(),
+ code: result,
+ op_infos,
+ provided_ops,
+ })
+}
+
+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.starts_with('/') && 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
+ .first()
+ .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())
+ }
+}
+
+fn write_lib_file(filename: &str, contents: &[String], provide: &str) -> Result<(), Error> {
+ let path = GENPATH;
+
+ let mut output = fs::File::create(path.to_owned() + filename)?;
+ write!(output, "{}", LANGDECL)?;
+ for item in contents.iter() {
+ write!(output, "{}", item)?;
+ }
+ write!(output, "{}", provide)?;
+ Ok(())
+}
diff --git a/src/crates/primrose/src/main.rs b/src/crates/primrose/src/main.rs
new file mode 100644
index 0000000..d307942
--- /dev/null
+++ b/src/crates/primrose/src/main.rs
@@ -0,0 +1,91 @@
+use log::info;
+use primrose::tools::nary_cartesian_product;
+use primrose::{ContainerSelector, Error};
+use std::collections::HashMap;
+use std::error::Error as StdError;
+use std::path::Path;
+use std::{env, fs, io::Write};
+
+const LIB: &str = "./crates/primrose-library/src/";
+
+fn main() -> Result<(), Box<dyn StdError>> {
+ 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 StdError>>::into)?;
+
+ info!(
+ "Running on {}, outputting to {}, with model size {}",
+ file_name, output_name, model_size
+ );
+ run(file_name, output_name, model_size)?;
+
+ info!("Yippeeeeee!!");
+ Ok(())
+}
+
+fn parse_args() -> Result<(String, String, usize), &'static str> {
+ let args: Vec<String> = env::args().collect();
+ if args.len() == 1 {
+ Ok((
+ "./spec_code/example_unique.rs".to_string(),
+ "default".to_string(),
+ 5,
+ ))
+ } else if args.len() == 4 {
+ let model_size_input = match args.get(3).unwrap().parse::<u64>() {
+ Ok(val) => val,
+ Err(_) => {
+ return Err("Invalid model size");
+ }
+ };
+ let model_size = model_size_input as usize;
+
+ Ok((
+ "./spec_code/".to_string() + args.get(1).unwrap(),
+ args.get(2).unwrap().to_string(),
+ model_size,
+ ))
+ } else {
+ return Err("Usage: <file_name> [output_path] [model_size]");
+ }
+}
+
+/// Read input from the filename, calculate all valid implementations, and output them to seperate files in output_path
+fn run(input: String, output_name: String, model_size: usize) -> Result<(), Error> {
+ info!("Generating candidate code outputs");
+ let gen_code = gen_outputs(input, model_size)?;
+
+ let output_path = format!("./gen_code/{}/", output_name);
+ fs::create_dir_all(&output_path).expect("error creating output directory");
+
+ info!("Writing {} different outputs", gen_code.len());
+ for (i, code) in gen_code.iter().enumerate() {
+ let mut output = fs::File::create(format!("{}/{}{}.rs", output_path, output_name, i))
+ .expect("error creating output file");
+ write!(output, "{}", &code).expect("error writing output file");
+ }
+
+ Ok(())
+}
+
+/// Process the given file, returning code for all possible types
+fn gen_outputs(filename: String, model_size: usize) -> Result<Vec<String>, Error> {
+ let selector = ContainerSelector::from_path(Path::new(&filename), Path::new(LIB), model_size)?;
+
+ let mut candidates = HashMap::new();
+ for tag in selector.container_tags() {
+ let found = selector.find_candidates(tag)?;
+ candidates.insert(tag, found);
+ }
+
+ Ok(nary_cartesian_product(&candidates)
+ .into_iter()
+ .map(|selections| {
+ selections
+ .into_iter()
+ .map(|(tag_name, typ_name)| selector.gen_replacement_code(tag_name, typ_name))
+ .collect::<String>()
+ })
+ .collect())
+}
diff --git a/src/crates/primrose/src/parser.rs b/src/crates/primrose/src/parser.rs
new file mode 100644
index 0000000..47ae8e3
--- /dev/null
+++ b/src/crates/primrose/src/parser.rs
@@ -0,0 +1,203 @@
+extern crate peg;
+use peg::parser;
+
+use std::iter::FromIterator;
+use std::vec::Vec;
+
+use crate::types::{Bounds, Name, Type, TypeVar};
+
+pub type Id = String;
+
+pub type Literal = String;
+
+#[derive(Clone, Debug)]
+pub enum Refinement {
+ Prop(Term),
+ AndProps(Box<Refinement>, Box<Refinement>),
+}
+
+#[derive(Clone, Debug)]
+pub enum Term {
+ Lit(Literal),
+ Var(Id),
+ Lambda((Id, Bounds), Box<Term>),
+ App(Box<Term>, Box<Term>),
+}
+
+impl Term {
+ pub fn is_quantifier(&self) -> bool {
+ match self {
+ Term::Var(id) => id.to_string().eq("forall"),
+ _ => false,
+ }
+ }
+
+ pub fn require_cdr(&self) -> bool {
+ match self {
+ Term::Var(id) => id.to_string().eq("pop"),
+ _ => false,
+ }
+ }
+}
+
+impl ToString for Term {
+ fn to_string(&self) -> String {
+ match self {
+ Term::Lit(l) => l.to_string(),
+ Term::Var(id) => id.to_string(),
+ Term::Lambda((id, _bounds), _t) => id.to_string(),
+ Term::App(t1, t2) => t1.to_string() + &t2.to_string(),
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum Decl {
+ PropertyDecl((Id, Type), Box<Term>),
+ ConTypeDecl(Type, (Id, Bounds, Refinement)),
+}
+
+impl Decl {
+ pub fn is_prop_decl(&self) -> bool {
+ matches!(self, Decl::PropertyDecl(_, _))
+ }
+
+ pub fn is_contype_decl(&self) -> bool {
+ matches!(self, Decl::ConTypeDecl(_, _))
+ }
+
+ 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 {
+ matches!(self, Block::SpecBlock(_, _))
+ }
+
+ pub fn is_code_block(&self) -> bool {
+ matches!(self, Block::CodeBlock(_, _))
+ }
+
+ pub fn extract_spec(&self) -> Spec {
+ match self {
+ Block::SpecBlock(spec, _) => spec.to_vec(),
+ _ => Vec::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(n, Box::new(t), Bounds::from(["Container".to_string()])) }
+ --
+ n:name()
+ { Type::Var(TypeVar::new(n)) }
+ }
+
+ pub rule term() -> Term
+ = precedence!{
+ lit: literal() { Term::Lit(lit) }
+ --
+ v:id() { Term::Var(v) }
+ --
+ "\\" v:id() _ "->" _ t:term() { Term::Lambda((v, std::collections::HashSet::default()), Box::new(t)) }
+ --
+ "\\" v:id() _ "<:" _ "(" _ b:bounds() _ ")" _ "->" _ t:term() { Term::Lambda((v, b), Box::new(t)) }
+ --
+ "(" _ t1:term() __ t2:term() _ ")" { Term::App(Box::new(t1), Box::new(t2)) }
+ }
+
+ pub rule refinement() -> Refinement
+ = precedence!{
+ t:term() { Refinement::Prop(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((p, ty), Box::new(t))
+ }
+ --
+ _ "type" __ ty:ty() _ "=" _ "{" _ c:id() _ "impl" __ "(" _ b:bounds() _ ")" _ "|" _ t:refinement() _ "}" _
+ {
+ Decl::ConTypeDecl(ty, (c, b, 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']+}
+
+}}
diff --git a/src/crates/primrose/src/run_matching.rs b/src/crates/primrose/src/run_matching.rs
new file mode 100644
index 0000000..c494f50
--- /dev/null
+++ b/src/crates/primrose/src/run_matching.rs
@@ -0,0 +1,109 @@
+use std::fs;
+use std::io::{Error, Write};
+use std::process::Command;
+
+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: &str,
+ match_setup: &str,
+ prop_spec_file: &str,
+ lib_spec_file: &str,
+ interface_spec: &str,
+ symbolics: &[String],
+) -> Result<String, Error> {
+ let mut output = fs::File::create(GENNAME)?;
+ write!(output, "{}", LANGDECL)?;
+ 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");
+}
diff --git a/src/crates/primrose/src/selector.rs b/src/crates/primrose/src/selector.rs
new file mode 100644
index 0000000..fcfd0c6
--- /dev/null
+++ b/src/crates/primrose/src/selector.rs
@@ -0,0 +1,206 @@
+use std::{
+ collections::HashMap,
+ fs,
+ io::{self, Write},
+ path::Path,
+};
+
+use log::{debug, trace};
+
+const MATCHSCRIPT: &str = "./racket_specs/gen_match/match-script.rkt";
+const OPS: &str = "./racket_specs/gen_lib_spec/ops.rkt";
+
+use crate::{
+ analysis::Analyser,
+ codegen::process_bound_decl,
+ description::{Description, Tag, TagId},
+ error::Error,
+ library_specs::LibSpec,
+ parser::{spec, Block},
+ run_matching::{
+ cleanup_script, gen_match_script, initialise_match_setup, run_matching, setup_dirs,
+ LANGDECL,
+ },
+ source_file::read_src,
+ spec_map::{LibSpecs, MatchSetup, ProvidedOps},
+ type_check::TypeChecker,
+};
+
+/// Selects containers for a specific file.
+/// Creating this requires doing some analysis on the file, so is relatively costly.
+pub struct ContainerSelector {
+ /// Analysis of the source file
+ pub(crate) analyser: Analyser,
+
+ pub(crate) blocks: Vec<Block>,
+ pub(crate) bounds_decl: String,
+ pub(crate) match_setup: MatchSetup,
+ pub(crate) lib_specs: LibSpecs,
+}
+
+impl ContainerSelector {
+ /// Load the file at the given path, perform analysis, and return a selector.
+ pub fn from_path(path: &Path, lib_path: &Path, model_size: usize) -> Result<Self, Error> {
+ Self::from_src(
+ &read_src(path).map_err(Error::InputRead)?,
+ lib_path,
+ model_size,
+ )
+ }
+
+ /// Analyse the given source and return a selector for it.
+ pub fn from_src(src: &str, lib_path: &Path, model_size: usize) -> Result<Self, Error> {
+ debug!("Setting up directories");
+ setup_dirs();
+
+ debug!("Parsing into blocks");
+ let blocks = spec::prog(src)?;
+
+ debug!("Running type checker");
+ let mut tc = TypeChecker::new();
+ tc.check_prog(blocks.clone())?;
+ trace!("Results of type checking: {:#?}", &tc);
+
+ debug!("Running analysis");
+ let mut analyser = Analyser::new();
+ analyser
+ .analyse_prog(blocks.clone(), model_size)
+ .map_err(Error::AnalyserError)?;
+ trace!("Results of analysis: {:#?}", &analyser);
+
+ Self::new(analyser, blocks, lib_path)
+ }
+
+ /// Create a new selector using the given analysis
+ fn new(analyser: Analyser, blocks: Vec<Block>, lib_path: &Path) -> Result<Self, Error> {
+ Ok(Self {
+ blocks,
+ bounds_decl: process_bound_decl(analyser.ctx()),
+ match_setup: initialise_match_setup(),
+ lib_specs: LibSpec::process_all(lib_path).map_err(Error::LibraryError)?,
+ analyser,
+ })
+ }
+
+ /// Get all container tags in this context
+ pub fn container_tags(&self) -> impl Iterator<Item = &String> {
+ self.analyser
+ .ctx()
+ .iter()
+ .filter(|(_k, v)| v.is_con_tag())
+ .map(|(k, _)| k)
+ }
+
+ /// Find candidates for all container tags in this context.
+ /// Returns a map from tag name to list of candidates.
+ pub fn find_all_candidates(&self) -> Result<HashMap<&TagId, Vec<String>>, Error> {
+ let mut candidates = HashMap::new();
+ for tag in self.container_tags() {
+ debug!("Finding candidates for tag {}", tag);
+ let found = self.find_candidates(tag)?;
+
+ debug!("Found {} candidates for tag {}", found.len(), tag);
+ candidates.insert(tag, found);
+ }
+
+ Ok(candidates)
+ }
+
+ /// Find candidate container types for a given Tag in this context.
+ /// Panics if tag_id is not a key, or is not the correct type of tag
+ pub fn find_candidates(&self, tag_id: &String) -> Result<Vec<String>, Error> {
+ let tag = self.analyser.ctx().get(tag_id).expect("invalid tag_id");
+
+ let Tag::Con(_elem_ty, _i_name, tags) = tag else {
+ panic!("tag_id was not Tag::Con");
+ };
+
+ debug!("Finding container types for tag {}", tag_id);
+ 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 mut structs = Vec::new();
+
+ // select library structs implement bounds decl in contype
+ let lib_spec_impls = self.lib_specs.iter().filter(|(_name, spec)| {
+ bounds.iter().all(|i| {
+ spec.interface_provide_map
+ .keys()
+ .cloned()
+ .collect::<String>()
+ .contains(i)
+ })
+ });
+
+ for (name, spec) in lib_spec_impls {
+ debug!("{} - ...", name);
+ match write_provided_ops(&spec.provided_ops) {
+ Ok(_) => {}
+ Err(_) => {
+ return Err(Error::ProvidedOperationsNotInLibrarySpec);
+ }
+ }
+ let mut is_match = true;
+ for p in prop_descs.iter() {
+ for i in bounds.iter() {
+ let (prop_file, symbolics) =
+ self.analyser.prop_specs().get(p).expect(
+ &("Error: No property specification found for: ".to_string() + &p),
+ );
+ gen_match_script(
+ p,
+ self.match_setup.get(i).unwrap(),
+ prop_file,
+ &spec.spec_name,
+ spec.interface_provide_map.get(i).unwrap(),
+ symbolics,
+ )
+ .map_err(Error::GenMatchScript)?;
+
+ // true - match; false - not match
+ is_match &=
+ run_matching(MATCHSCRIPT.to_string()).map_err(Error::ExecutionError)?;
+ if !is_match {
+ break;
+ }
+ }
+ if !is_match {
+ break;
+ }
+ }
+ if is_match {
+ debug!("{} - YAY", name);
+ structs.push(name.to_string());
+ } else {
+ debug!("{} - NAY", name);
+ }
+ }
+
+ cleanup_script();
+ Ok(structs)
+ }
+}
+
+/// Write the provided operation specifications from a library spec to the correct path ([`self::OPS`]).
+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)?;
+ write!(output, "{}", LANGDECL)?;
+ for item in code.iter() {
+ write!(output, "{}", item)?;
+ }
+ let ops_string = ops.join(" ");
+ let provide = "\n(provide ".to_string() + &ops_string + ")";
+ write!(output, "{}", provide)?;
+ Ok(())
+}
diff --git a/src/crates/primrose/src/source_file.rs b/src/crates/primrose/src/source_file.rs
new file mode 100644
index 0000000..0884015
--- /dev/null
+++ b/src/crates/primrose/src/source_file.rs
@@ -0,0 +1,47 @@
+use std::{
+ fs,
+ io::{self},
+ path::Path,
+};
+
+pub const CODE: &str = "/*CODE*/";
+pub const CODEEND: &str = "/*ENDCODE*/";
+
+pub const SPEC: &str = "/*SPEC*";
+pub const SPECEND: &str = "*ENDSPEC*/";
+
+pub fn read_src(filename: &Path) -> Result<String, io::Error> {
+ let contents = fs::read_to_string(filename)?;
+ Ok(preprocess_src(contents))
+}
+
+/// Mark all parts of the code so everything is between either CODE and CODEEND
+/// or SPEC and SPECEND
+pub fn preprocess_src(src: String) -> String {
+ let mut trimed_src = src.trim();
+ let mut result = String::new();
+ while !trimed_src.is_empty() {
+ 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 += spec;
+ }
+ None => {
+ result = result + CODE + trimed_src + CODEEND;
+ break;
+ }
+ },
+ None => {
+ result = result + CODE + trimed_src + CODEEND;
+ break;
+ }
+ }
+ }
+ result
+}
diff --git a/src/crates/primrose/src/spec_map.rs b/src/crates/primrose/src/spec_map.rs
new file mode 100644
index 0000000..22b84c8
--- /dev/null
+++ b/src/crates/primrose/src/spec_map.rs
@@ -0,0 +1,18 @@
+use std::collections::HashMap;
+
+use crate::library_specs::LibSpec;
+
+type StructName = 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, LibSpec>;
+pub type PropSpecs = HashMap<PropertyName, (PropSpecDir, PropSymbolics)>;
+pub type MatchSetup = HashMap<BoundName, MatchSetupDir>;
diff --git a/src/crates/primrose/src/tools/mod.rs b/src/crates/primrose/src/tools/mod.rs
new file mode 100644
index 0000000..ebee9b5
--- /dev/null
+++ b/src/crates/primrose/src/tools/mod.rs
@@ -0,0 +1,89 @@
+//! Useful tools for benchmarking & selection
+
+use rand::rngs::StdRng;
+use rand::seq::SliceRandom;
+
+use rand::SeedableRng;
+
+use std::collections::HashMap;
+use std::mem::size_of;
+use std::{hash::Hash, vec::Vec};
+
+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
+}
+
+/// Get the cartesian product of all of the values in set.
+/// Returns a list of every possible combination created by picking a value from the matching key in `bins`.
+pub fn nary_cartesian_product<K: Hash + Eq, V>(bins: &HashMap<K, Vec<V>>) -> Vec<HashMap<&K, &V>> {
+ let bins = bins.iter().collect::<Vec<_>>();
+ let mut indices = vec![0; bins.len()];
+ let lengths = bins.iter().map(|(_, v)| v.len()).collect::<Vec<_>>();
+ let mut outputs = Vec::with_capacity(lengths.iter().product());
+ loop {
+ let code = indices
+ .iter()
+ .enumerate()
+ // get candidate we're using for each type
+ .map(|(key_idx, val_idx)| (bins[key_idx].0, &bins[key_idx].1[*val_idx]))
+ .collect::<HashMap<_, _>>();
+
+ outputs.push(code);
+
+ if inc_carrying(&mut indices, &lengths) {
+ break;
+ }
+ }
+
+ outputs
+}
+
+/// Increment the left hand side of indices, carrying as needed if any entries reach the
+/// the value defined in `bounds`.
+/// `max` and `indices` must be the same length.
+/// Returns true if carried outside the list, otherwise false.
+pub fn inc_carrying(indices: &mut [usize], bounds: &[usize]) -> bool {
+ for i in 0..indices.len() {
+ if indices[i] < bounds[i] - 1 {
+ indices[i] += 1;
+ return false;
+ } else {
+ indices[i] = 0;
+ // carry to next bit
+ }
+ }
+
+ true
+}
diff --git a/src/crates/primrose/src/type_check.rs b/src/crates/primrose/src/type_check.rs
new file mode 100644
index 0000000..29a8d2a
--- /dev/null
+++ b/src/crates/primrose/src/type_check.rs
@@ -0,0 +1,388 @@
+//! Performs type checking for specifications
+use thiserror::Error;
+
+use crate::inference::TypeEnv;
+use crate::parser::{Decl, Prog, Refinement, Spec};
+use crate::types::{Bounds, Type, TypeScheme, TypeVar, TypeVarGen};
+
+use std::ops::Deref;
+
+#[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 {
+ 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(
+ 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(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ 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(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ 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(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ 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(
+ "Con".to_string(),
+ Box::new(Type::Var(TypeVar::new("T".to_string()))),
+ 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,
+ },
+ );
+ }
+
+ /// 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.check_specs(&specs)
+ }
+
+ /// 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()
+ .filter(|decl| decl.is_prop_decl())
+ .collect();
+ let contype_decls: Vec<&Decl> = concat_specs
+ .iter()
+ .filter(|decl| decl.is_contype_decl())
+ .collect();
+
+ self.check_prop_decls(&prop_decls)?;
+ self.check_contype_decls(&contype_decls)?;
+ self.check_bound_decls(&contype_decls)
+ }
+
+ /// Check all bound declarations
+ fn check_bound_decls(&mut self, decls: &[&Decl]) -> Result<(), TypeError> {
+ for decl in decls.iter() {
+ self.check_bound_decl(decl)?;
+ }
+
+ 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()));
+ }
+
+ // TODO: check each bound is a valid rust trait
+ }
+ Ok(())
+ }
+
+ /// 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)?;
+ }
+
+ Ok(())
+ }
+
+ /// 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()))
+ }
+ }
+ (_, 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(),
+ )),
+ }
+ }
+
+ /// 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)?;
+ }
+
+ Ok(())
+ }
+
+ /// 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(())
+ }
+
+ fn check_ref(&mut self, ctx: &mut TypeEnv, r: &Refinement) -> Result<(), TypeError> {
+ match r {
+ Refinement::Prop(t) => {
+ 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) => {
+ self.check_ref(ctx, r1)?;
+ self.check_ref(ctx, r2)
+ }
+ }
+ }
+}
diff --git a/src/crates/primrose/src/types.rs b/src/crates/primrose/src/types.rs
new file mode 100644
index 0000000..6c459fc
--- /dev/null
+++ b/src/crates/primrose/src/types.rs
@@ -0,0 +1,282 @@
+use std::collections::{HashMap, HashSet};
+
+use std::hash::Hash;
+use std::ops::{Deref, DerefMut};
+
+pub type Name = String;
+
+// traits
+pub type Bounds = HashSet<Name>;
+
+#[derive(Eq, PartialEq, Clone, Debug)]
+pub enum Type {
+ Bool(),
+ Var(TypeVar),
+ Con(Name, Box<Type>, Bounds),
+ Fun(Box<Type>, Box<Type>),
+}
+
+impl Type {
+ pub fn is_bool(&self) -> bool {
+ matches!(self, Type::Bool())
+ }
+
+ 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::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(in1, out1), Type::Fun(in2, out2)) => {
+ let sub1 = in1.mgu(in2)?;
+ let sub2 = out1.apply(&sub1).mgu(&out2.apply(&sub1))?;
+ Ok(sub1.compose(&sub2))
+ }
+
+ // Unify con type
+ (Type::Con(n1, t1, _), Type::Con(n2, t2, _)) => {
+ if n1 != n2 {
+ Err("Cannot unify two different container".to_string())
+ } else {
+ t1.mgu(t2)
+ }
+ }
+
+ // Type variable biding
+ (Type::Var(v), t) => v.bind(t),
+ (t, Type::Var(v)) => v.bind(t),
+
+ // Unify primitives
+ (&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
+#[derive(Debug)]
+pub struct TypeVarGen {
+ supply: usize,
+}
+
+impl TypeVarGen {
+ pub fn new() -> TypeVarGen {
+ TypeVarGen { supply: 0 }
+ }
+ pub fn gen(&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(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<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(s) => [s.clone()].iter().cloned().collect(),
+ &Type::Bool() => HashSet::new(),
+ Type::Fun(i, o) => i.ftv().union(&o.ftv()).cloned().collect(),
+ Type::Con(_, s, _) => s.ftv().union(&HashSet::new()).cloned().collect(),
+ }
+ }
+
+ // apply substitution
+ fn apply(&self, s: &Subst) -> 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(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.gen()));
+ self.ty
+ .apply(&Subst(self.vars.iter().cloned().zip(newvars).collect()))
+ }
+}