Improved ‘compiler’ example showing parsing of an actual language and use of actual interning

This commit is contained in:
Vincent Esche 2019-10-11 16:49:14 +02:00
parent e1f6572a00
commit 09d9592f14
6 changed files with 146 additions and 69 deletions

View file

@ -1,38 +0,0 @@
use crate::compiler;
use std::sync::Arc;
#[salsa::query_group(ClassTable)]
pub trait ClassTableDatabase: compiler::CompilerDatabase {
/// Get the fields.
fn fields(&self, class: DefId) -> Arc<Vec<DefId>>;
/// Get the list of all classes
fn all_classes(&self) -> Arc<Vec<DefId>>;
/// Get the list of all fields
fn all_fields(&self) -> Arc<Vec<DefId>>;
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct DefId(usize);
fn all_classes(_: &impl ClassTableDatabase) -> Arc<Vec<DefId>> {
Arc::new(vec![DefId(0), DefId(10)]) // dummy impl
}
fn fields(_: &impl ClassTableDatabase, class: DefId) -> Arc<Vec<DefId>> {
Arc::new(vec![DefId(class.0 + 1), DefId(class.0 + 2)]) // dummy impl
}
fn all_fields(db: &impl ClassTableDatabase) -> Arc<Vec<DefId>> {
Arc::new(
db.all_classes()
.iter()
.cloned()
.flat_map(|def_id| {
let fields = db.fields(def_id);
(0..fields.len()).map(move |i| fields[i])
})
.collect(),
)
}

View file

@ -1,6 +1,74 @@
pub trait CompilerDatabase: salsa::Database {
fn interner(&self) -> &Interner;
use std::sync::Arc;
use crate::{interner::Interner, values::*};
#[salsa::query_group(CompilerDatabase)]
#[salsa::requires(Interner)]
pub trait Compiler: Interner {
#[salsa::input]
fn input_string(&self) -> Arc<String>;
/// Get the fields.
fn fields(&self, class: Class) -> Arc<Vec<Field>>;
/// Get the list of all classes
fn all_classes(&self) -> Arc<Vec<Class>>;
/// Get the list of all fields
fn all_fields(&self) -> Arc<Vec<Field>>;
}
#[derive(Clone, Default)]
pub struct Interner;
/// This function parses a dummy language with the following structure:
///
/// Classes are defined one per line, consisting of a comma-separated list of fields.
///
/// Example:
///
/// ```
/// lorem,ipsum
/// dolor,sit,amet,
/// consectetur,adipiscing,elit
/// ```
fn all_classes(db: &impl Compiler) -> Arc<Vec<Class>> {
let string = db.input_string();
let rows = string.split('\n');
let classes: Vec<_> = rows
.filter(|string| !string.is_empty())
.map(|string| {
let fields = string
.trim()
.split(',')
.filter(|string| !string.is_empty())
.map(|name_str| {
let name = name_str.to_owned();
let field_data = FieldData { name };
db.intern_field(field_data)
})
.collect();
let class_data = ClassData { fields };
db.intern_class(class_data)
})
.collect();
Arc::new(classes)
}
fn fields(db: &impl Compiler, class: Class) -> Arc<Vec<Field>> {
let class = db.lookup_intern_class(class);
let fields = class.fields.clone();
Arc::new(fields)
}
fn all_fields(db: &impl Compiler) -> Arc<Vec<Field>> {
Arc::new(
db.all_classes()
.iter()
.cloned()
.flat_map(|class| {
let fields = db.fields(class);
(0..fields.len()).map(move |i| fields[i])
})
.collect(),
)
}

View file

@ -1,5 +1,5 @@
use crate::class_table;
use crate::compiler::{CompilerDatabase, Interner};
use crate::compiler::CompilerDatabase;
use crate::interner::InternerDatabase;
/// Our "database" will be threaded through our application (though
/// 99% of the application only interacts with it through traits and
@ -13,16 +13,10 @@ use crate::compiler::{CompilerDatabase, Interner};
/// to your context (e.g., a shared counter or some such thing). If
/// mutations to that shared state affect the results of your queries,
/// that's going to mess up the incremental results.
#[salsa::database(class_table::ClassTable)]
#[salsa::database(InternerDatabase, CompilerDatabase)]
#[derive(Default)]
pub struct DatabaseImpl {
runtime: salsa::Runtime<DatabaseImpl>,
/// An interner is an example of shared mutable state that would
/// be ok: although the interner allocates internally when you
/// intern something new, this never affects any previously
/// interned values, so it's not going to affect query results.
interner: Interner,
}
/// This impl tells salsa where to find the salsa runtime.
@ -35,12 +29,3 @@ impl salsa::Database for DatabaseImpl {
&mut self.runtime
}
}
/// In addition to the "query provider" traits, you may have other
/// trait requirements that your application needs -- you can
/// implement those yourself (in this case, an `interner`).
impl CompilerDatabase for DatabaseImpl {
fn interner(&self) -> &Interner {
&self.interner
}
}

View file

@ -0,0 +1,10 @@
use crate::values::*;
#[salsa::query_group(InternerDatabase)]
pub trait Interner: salsa::Database {
#[salsa::interned]
fn intern_field(&self, field: FieldData) -> Field;
#[salsa::interned]
fn intern_class(&self, class: ClassData) -> Class;
}

View file

@ -1,23 +1,40 @@
mod class_table;
use std::sync::Arc;
mod compiler;
mod implementation;
mod interner;
mod values;
use self::class_table::ClassTableDatabase;
use self::compiler::Compiler;
use self::implementation::DatabaseImpl;
use self::interner::Interner;
static INPUT_STR: &'static str = r#"
lorem,ipsum
dolor,sit,amet,
consectetur,adipiscing,elit
"#;
#[test]
fn test() {
let query = DatabaseImpl::default();
let all_def_ids = query.all_fields();
let mut db = DatabaseImpl::default();
db.set_input_string(Arc::new(INPUT_STR.to_owned()));
let all_fields = db.all_fields();
assert_eq!(
format!("{:?}", all_def_ids),
"[DefId(1), DefId(2), DefId(11), DefId(12)]"
format!("{:?}", all_fields),
"[Field(0), Field(1), Field(2), Field(3), Field(4), Field(5), Field(6), Field(7)]"
);
}
fn main() {
let query = DatabaseImpl::default();
for f in query.all_fields().iter() {
println!("{:?}", f);
let mut db = DatabaseImpl::default();
db.set_input_string(Arc::new(INPUT_STR.to_owned()));
for field in db.all_fields().iter() {
let field_data = db.lookup_intern_field(*field);
println!("{:?} => {:?}", field, field_data);
}
}

View file

@ -0,0 +1,35 @@
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ClassData {
pub fields: Vec<Field>,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Class(salsa::InternId);
impl salsa::InternKey for Class {
fn from_intern_id(id: salsa::InternId) -> Self {
Self(id)
}
fn as_intern_id(&self) -> salsa::InternId {
self.0
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct FieldData {
pub name: String,
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
pub struct Field(salsa::InternId);
impl salsa::InternKey for Field {
fn from_intern_id(id: salsa::InternId) -> Self {
Self(id)
}
fn as_intern_id(&self) -> salsa::InternId {
self.0
}
}