diff --git a/.travis.yml b/.travis.yml index 485a91f..f89f6b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,5 +15,7 @@ script: - cargo test --features heapsize - "cd examples/event-log/ && cargo build && cd ../.." - "cd examples/summarize-events/ && cargo build && cd ../.." + - "cd string-cache-codegen/ && cargo build && cd .." + - "cd string-cache-codegen/test/ && cargo test && cd ../.." notifications: webhooks: http://build.servo.org:54856/travis diff --git a/Cargo.toml b/Cargo.toml index dc0d94e..b5a5dcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT / Apache-2.0" repository = "https://github.com/servo/string-cache" documentation = "http://doc.servo.org/string_cache/" build = "build.rs" +exclude = ["string-cache-codegen/**/*"] [lib] name = "string_cache" @@ -27,7 +28,8 @@ heap_size = ["heapsize"] [dependencies] lazy_static = "0.2" serde = ">=0.6, <0.9" -phf_shared = "0.7.4" +phf = "0.7.15" +phf_shared = "0.7.15" debug_unreachable = "0.1.1" [dev-dependencies] @@ -41,6 +43,6 @@ optional = true version = ">=0.1.1, <0.4" optional = true -[build-dependencies] -phf_generator = "0.7.4" -phf_shared = "0.7.4" +[build-dependencies.string_cache_codegen] +version = "0.2.27" +path = "string-cache-codegen" diff --git a/build.rs b/build.rs index 65358f6..eefbfc7 100644 --- a/build.rs +++ b/build.rs @@ -1,73 +1,19 @@ -extern crate phf_shared; -extern crate phf_generator; +extern crate string_cache_codegen; -#[path = "src/shared.rs"] #[allow(dead_code)] mod shared; #[path = "src/static_atom_list.rs"] mod static_atom_list; use std::env; use std::fs::File; -use std::io::{BufWriter, Write}; -use std::mem; +use std::io::BufWriter; use std::path::Path; -use std::slice; fn main() { - let hash_state = generate(); - write_static_atom_set(&hash_state); - write_atom_macro(&hash_state); -} - -fn generate() -> phf_generator::HashState { - let mut set = std::collections::HashSet::new(); - for atom in static_atom_list::ATOMS { - if !set.insert(atom) { - panic!("duplicate static atom `{:?}`", atom); - } - } - phf_generator::generate_hash(static_atom_list::ATOMS) -} - -fn write_static_atom_set(hash_state: &phf_generator::HashState) { - let path = Path::new(&std::env::var("OUT_DIR").unwrap()).join("static_atom_set.rs"); + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("static_atoms.rs"); let mut file = BufWriter::new(File::create(&path).unwrap()); - macro_rules! w { - ($($arg: expr),+) => { (writeln!(&mut file, $($arg),+).unwrap()) } - } - w!("pub static STATIC_ATOM_SET: StaticAtomSet = StaticAtomSet {{"); - w!(" key: {},", hash_state.key); - w!(" disps: &["); - for &(d1, d2) in &hash_state.disps { - w!(" ({}, {}),", d1, d2); - } - w!(" ],"); - w!(" atoms: &["); - for &idx in &hash_state.map { - w!(" {:?},", static_atom_list::ATOMS[idx]); - } - w!(" ],"); - w!("}};"); -} -fn write_atom_macro(hash_state: &phf_generator::HashState) { - let set = shared::StaticAtomSet { - key: hash_state.key, - disps: leak(hash_state.disps.clone()), - atoms: leak(hash_state.map.iter().map(|&idx| static_atom_list::ATOMS[idx]).collect()), - }; - - let path = Path::new(&env::var("OUT_DIR").unwrap()).join("atom_macro.rs"); - let mut file = BufWriter::new(File::create(&path).unwrap()); - writeln!(file, r"#[macro_export]").unwrap(); - writeln!(file, r"macro_rules! atom {{").unwrap(); - for &s in set.iter() { - let data = shared::pack_static(set.get_index_or_hash(s).unwrap() as u32); - writeln!(file, r"({:?}) => {{ $crate::Atom {{ unsafe_data: 0x{:x} }} }};", s, data).unwrap(); + let mut builder = string_cache_codegen::AtomSetBuilder::new(); + for atom in static_atom_list::ATOMS { + builder.atom(atom); } - writeln!(file, r"}}").unwrap(); -} - -fn leak(v: Vec) -> &'static [T] { - let slice = unsafe { slice::from_raw_parts(v.as_ptr(), v.len()) }; - mem::forget(v); - slice + builder.build(&mut file, "ServoAtom", "STATIC_ATOM_SET", "atom"); } diff --git a/examples/summarize-events/Cargo.toml b/examples/summarize-events/Cargo.toml index 7d2e7ba..e0505a6 100644 --- a/examples/summarize-events/Cargo.toml +++ b/examples/summarize-events/Cargo.toml @@ -7,7 +7,8 @@ authors = [ "The Servo Project Developers" ] [dependencies] csv = "0" rustc-serialize = "0" -phf_shared = "0.7.4" +phf = "0.7.15" +phf_shared = "0.7.15" [dependencies.string_cache] path = "../.." diff --git a/examples/summarize-events/src/main.rs b/examples/summarize-events/src/main.rs index 8a44389..c35613d 100644 --- a/examples/summarize-events/src/main.rs +++ b/examples/summarize-events/src/main.rs @@ -10,6 +10,7 @@ extern crate csv; extern crate string_cache; extern crate rustc_serialize; +pub extern crate phf; extern crate phf_shared; #[path = "../../../src/shared.rs"] @@ -20,6 +21,7 @@ use string_cache::Atom; use std::{env, cmp}; use std::collections::hash_map::{HashMap, Entry}; +use std::marker::PhantomData; use std::path::Path; #[derive(RustcDecodable, Debug)] @@ -88,7 +90,7 @@ fn main() { // FIXME: We really shouldn't be allowed to do this. It's a memory-safety // hazard; the field is only public for the atom!() macro. - _ => Atom { unsafe_data: ev.id }.to_string(), + _ => Atom { unsafe_data: ev.id, kind: PhantomData }.to_string(), }; match summary.entry(string) { diff --git a/src/atom/mod.rs b/src/atom/mod.rs index e3f6b8b..06464c9 100644 --- a/src/atom/mod.rs +++ b/src/atom/mod.rs @@ -23,19 +23,19 @@ use std::ops; use std::ptr; use std::slice; use std::str; +use std::hash::Hash; +use std::marker::PhantomData; use std::sync::Mutex; use std::sync::atomic::AtomicIsize; use std::sync::atomic::Ordering::SeqCst; use shared::{STATIC_TAG, INLINE_TAG, DYNAMIC_TAG, TAG_MASK, MAX_INLINE_LEN, STATIC_SHIFT_BITS, - ENTRY_ALIGNMENT, pack_static, StaticAtomSet}; + ENTRY_ALIGNMENT, pack_static, dynamic_hash}; use self::UnpackedAtom::{Dynamic, Inline, Static}; #[cfg(feature = "log-events")] use event::Event; -include!(concat!(env!("OUT_DIR"), "/static_atom_set.rs")); - #[cfg(not(feature = "log-events"))] macro_rules! log (($e:expr) => (())); @@ -167,35 +167,99 @@ impl StringCache { } } +pub trait Kind: Eq + Hash + Ord + PartialEq + PartialOrd { + #[inline] + fn get_index_or_hash(s: &str) -> Result; + + #[inline] + fn index(i: u32) -> Option<&'static str>; +} + +// Although DefaultKind isn't used right now, it will be when ServoAtomKind is +// removed. Note it will only be used for dynamic atoms. +#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct DefaultKind; +impl Kind for DefaultKind { + // There are no static atoms for DefaultKind so there's never an index + #[inline] + fn get_index_or_hash(s: &str) -> Result { + Err(dynamic_hash(s)) + } + + #[inline] + fn index(_: u32) -> Option<&'static str> { + None + } +} + +pub type Atom = BaseAtom; + // NOTE: Deriving Eq here implies that a given string must always -// be interned the same way. +// be interned the same way for the same kind. #[derive(Eq, Hash, PartialEq)] -pub struct Atom { +// After RFC 213 (tracking issue https://github.com/rust-lang/rust/issues/27336), +// rename `BaseAtom` to `Atom` and use `K=super::ServoAtomKind`. +pub struct BaseAtom where K: Kind { /// This field is public so that the `atom!()` macro can use it. /// You should not otherwise access this field. #[doc(hidden)] pub unsafe_data: u64, + /// This field exists to allow different sets of static atoms to exist + /// while ensuring they aren't ever compared for equality. + #[doc(hidden)] + pub kind: PhantomData, +} + +// #[derive] incorrectly attempts to impose HeapSizeOf bounds to generic types +// https://github.com/rust-lang/rust/issues/26925 +#[cfg(feature = "heap_size")] +impl HeapSizeOf for BaseAtom where K: Kind { + fn heap_size_of_children(&self) -> usize { + 0 + } } +// Ideally codegen would just set up type aliases and all dependees would get +// Borrowed*. Unfortunately, it's not possible to construct a struct from a +// type alias (#26264, #31659), so we need to duplicate the whole definition +// to let Servo work. Other crates are out of luck and must either use the +// BorrowedAtomBase constructor, wait until the issue is fixed or get codegen +// to recreate the whole Borrowed* type (as done manually for Servo below). +pub struct BorrowedBaseAtom<'a, K: 'a>(pub &'a BaseAtom) where K: Kind; + +impl<'a, K> ops::Deref for BorrowedBaseAtom<'a, K> where K: Kind { + type Target = BaseAtom; + fn deref(&self) -> &BaseAtom { + self.0 + } +} + +impl<'a, K> PartialEq> for BorrowedBaseAtom<'a, K> where K: Kind { + fn eq(&self, other: &BaseAtom) -> bool { + self.0 == other + } +} + +// Change to BaseAtom when heapsize 0.3.7 is released #[cfg(feature = "heapsize")] known_heap_size!(0, Atom); -pub struct BorrowedAtom<'a>(pub &'a Atom); +pub struct BorrowedAtom<'a>(pub &'a BaseAtom); impl<'a> ops::Deref for BorrowedAtom<'a> { - type Target = Atom; - fn deref(&self) -> &Atom { + type Target = BaseAtom; + fn deref(&self) -> &BaseAtom { self.0 } } -impl<'a> PartialEq for BorrowedAtom<'a> { - fn eq(&self, other: &Atom) -> bool { +impl<'a> PartialEq> for BorrowedAtom<'a> { + fn eq(&self, other: &BaseAtom) -> bool { self.0 == other } } -impl Atom { +impl BaseAtom where K: Kind { #[inline(always)] unsafe fn unpack(&self) -> UnpackedAtom { UnpackedAtom::from_packed(self.unsafe_data) @@ -235,11 +299,11 @@ impl PartialEq for Atom { } } -impl<'a> From> for Atom { +impl<'a, K> From> for BaseAtom where K: Kind { #[inline] - fn from(string_to_add: Cow<'a, str>) -> Atom { - let unpacked = match STATIC_ATOM_SET.get_index_or_hash(&*string_to_add) { - Ok(id) => Static(id as u32), + fn from(string_to_add: Cow<'a, str>) -> BaseAtom { + let unpacked = match K::get_index_or_hash(&*string_to_add) { + Ok(id) => Static(id), Err(hash) => { let len = string_to_add.len(); if len <= MAX_INLINE_LEN { @@ -254,27 +318,27 @@ impl<'a> From> for Atom { let data = unsafe { unpacked.pack() }; log!(Event::Intern(data)); - Atom { unsafe_data: data } + BaseAtom { unsafe_data: data, kind: PhantomData } } } -impl<'a> From<&'a str> for Atom { +impl<'a, K> From<&'a str> for BaseAtom where K: Kind { #[inline] - fn from(string_to_add: &str) -> Atom { - Atom::from(Cow::Borrowed(string_to_add)) + fn from(string_to_add: &str) -> BaseAtom { + BaseAtom::from(Cow::Borrowed(string_to_add)) } } -impl From for Atom { +impl From for BaseAtom where K: Kind { #[inline] - fn from(string_to_add: String) -> Atom { - Atom::from(Cow::Owned(string_to_add)) + fn from(string_to_add: String) -> BaseAtom { + BaseAtom::from(Cow::Owned(string_to_add)) } } -impl Clone for Atom { +impl Clone for BaseAtom where K: Kind { #[inline(always)] - fn clone(&self) -> Atom { + fn clone(&self) -> BaseAtom { unsafe { match from_packed_dynamic(self.unsafe_data) { Some(entry) => { @@ -284,17 +348,18 @@ impl Clone for Atom { None => (), } } - Atom { - unsafe_data: self.unsafe_data + BaseAtom { + unsafe_data: self.unsafe_data, + kind: PhantomData, } } } -impl Drop for Atom { +impl Drop for BaseAtom where K: Kind { #[inline] fn drop(&mut self) { // Out of line to guide inlining. - fn drop_slow(this: &mut Atom) { + fn drop_slow(this: &mut BaseAtom) where K: Kind { STRING_CACHE.lock().unwrap().remove(this.unsafe_data); } @@ -313,7 +378,7 @@ impl Drop for Atom { } -impl ops::Deref for Atom { +impl ops::Deref for BaseAtom where K: Kind { type Target = str; #[inline] @@ -324,7 +389,7 @@ impl ops::Deref for Atom { let buf = inline_orig_bytes(&self.unsafe_data); str::from_utf8(buf).unwrap() }, - Static(idx) => STATIC_ATOM_SET.index(idx).expect("bad static atom"), + Static(idx) => K::index(idx).expect("bad static atom"), Dynamic(entry) => { let entry = entry as *mut StringCacheEntry; &(*entry).string @@ -334,14 +399,14 @@ impl ops::Deref for Atom { } } -impl fmt::Display for Atom { +impl fmt::Display for BaseAtom where K: Kind { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { ::fmt(self, f) } } -impl fmt::Debug for Atom { +impl fmt::Debug for BaseAtom where K: Kind { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let ty_str = unsafe { @@ -356,9 +421,9 @@ impl fmt::Debug for Atom { } } -impl PartialOrd for Atom { +impl PartialOrd for BaseAtom where K: Kind { #[inline] - fn partial_cmp(&self, other: &Atom) -> Option { + fn partial_cmp(&self, other: &BaseAtom) -> Option { if self.unsafe_data == other.unsafe_data { return Some(Equal); } @@ -366,9 +431,9 @@ impl PartialOrd for Atom { } } -impl Ord for Atom { +impl Ord for BaseAtom where K: Kind { #[inline] - fn cmp(&self, other: &Atom) -> Ordering { + fn cmp(&self, other: &BaseAtom) -> Ordering { if self.unsafe_data == other.unsafe_data { return Equal; } @@ -376,43 +441,43 @@ impl Ord for Atom { } } -impl AsRef for Atom { +impl AsRef for BaseAtom where K: Kind { fn as_ref(&self) -> &str { &self } } -impl Serialize for Atom { +impl Serialize for BaseAtom where K: Kind { fn serialize(&self, serializer: &mut S) -> Result<(),S::Error> where S: Serializer { let string: &str = self.as_ref(); string.serialize(serializer) } } -impl Deserialize for Atom { - fn deserialize(deserializer: &mut D) -> Result where D: Deserializer { +impl Deserialize for BaseAtom where K: Kind { + fn deserialize(deserializer: &mut D) -> Result,D::Error> where D: Deserializer { let string: String = try!(Deserialize::deserialize(deserializer)); - Ok(Atom::from(&*string)) + Ok(BaseAtom::from(&*string)) } } // AsciiExt requires mutating methods, so we just implement the non-mutating ones. // We don't need to implement is_ascii because there's no performance improvement // over the one from &str. -impl Atom { - pub fn to_ascii_uppercase(&self) -> Atom { +impl BaseAtom where K: Kind { + pub fn to_ascii_uppercase(&self) -> BaseAtom { if self.chars().all(char::is_uppercase) { self.clone() } else { - Atom::from(&*((&**self).to_ascii_uppercase())) + BaseAtom::from(&*((&**self).to_ascii_uppercase())) } } - pub fn to_ascii_lowercase(&self) -> Atom { + pub fn to_ascii_lowercase(&self) -> BaseAtom { if self.chars().all(char::is_lowercase) { self.clone() } else { - Atom::from(&*((&**self).to_ascii_lowercase())) + BaseAtom::from(&*((&**self).to_ascii_lowercase())) } } @@ -547,7 +612,8 @@ mod bench; mod tests { use std::mem; use std::thread; - use super::{Atom, StringCacheEntry, STATIC_ATOM_SET}; + use super::{Atom, StringCacheEntry}; + use super::super::STATIC_ATOM_SET; use super::UnpackedAtom::{Dynamic, Inline, Static}; use shared::ENTRY_ALIGNMENT; diff --git a/src/lib.rs b/src/lib.rs index b466a77..7520fa7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ #[macro_use] extern crate lazy_static; #[macro_use] extern crate debug_unreachable; extern crate serde; +pub extern crate phf; extern crate phf_shared; pub use atom::{Atom, BorrowedAtom}; @@ -52,7 +53,7 @@ macro_rules! ns { (mathml) => { $crate::Namespace(atom!("http://www.w3.org/1998/Math/MathML")) }; } -include!(concat!(env!("OUT_DIR"), "/atom_macro.rs")); +include!(concat!(env!("OUT_DIR"), "/static_atoms.rs")); #[cfg(feature = "log-events")] #[macro_use] @@ -70,4 +71,5 @@ pub mod shared; mod string_cache { pub use atom; pub use namespace; + pub use shared; } diff --git a/src/shared.rs b/src/shared.rs index a653872..3070118 100644 --- a/src/shared.rs +++ b/src/shared.rs @@ -7,6 +7,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +pub use phf; // Expose to users of string_cache_codegen use phf_shared; // FIXME(rust-lang/rust#18153): generate these from an enum @@ -24,31 +25,10 @@ pub fn pack_static(n: u32) -> u64 { (STATIC_TAG as u64) | ((n as u64) << STATIC_SHIFT_BITS) } -pub struct StaticAtomSet { - pub key: u64, - pub disps: &'static [(u32, u32)], - pub atoms: &'static [&'static str], +#[inline(always)] +pub fn dynamic_hash(s: &str) -> u64 { + // First return value of the RNG in phf_generator 0.7.11 + let key = 1897749892740154578u64; + phf_shared::hash(s, key) } -impl StaticAtomSet { - #[inline] - pub fn get_index_or_hash(&self, s: &str) -> Result { - let hash = phf_shared::hash(s, self.key); - let index = phf_shared::get_index(hash, self.disps, self.atoms.len()); - if self.atoms[index as usize] == s { - Ok(index) - } else { - Err(hash) - } - } - - #[inline] - pub fn index(&self, i: u32) -> Option<&'static str> { - self.atoms.get(i as usize).map(|&s| s) - } - - #[inline] - pub fn iter(&self) -> ::std::slice::Iter<&'static str> { - self.atoms.iter() - } -} diff --git a/string-cache-codegen/.gitignore b/string-cache-codegen/.gitignore new file mode 100644 index 0000000..d4f917d --- /dev/null +++ b/string-cache-codegen/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +*.swp diff --git a/string-cache-codegen/Cargo.toml b/string-cache-codegen/Cargo.toml new file mode 100644 index 0000000..9339963 --- /dev/null +++ b/string-cache-codegen/Cargo.toml @@ -0,0 +1,15 @@ +[package] + +name = "string_cache_codegen" +version = "0.2.27" +authors = [ "The Servo Project Developers" ] +description = "A codegen library for string-cache, developed as part of the Servo project." +license = "MIT / Apache-2.0" +repository = "https://github.com/servo/string-cache" +documentation = "http://doc.servo.org/string_cache_codegen/" + +[lib] +name = "string_cache_codegen" + +[dependencies] +phf_codegen = "0.7.15" diff --git a/string-cache-codegen/README.md b/string-cache-codegen/README.md new file mode 100644 index 0000000..cdb7c57 --- /dev/null +++ b/string-cache-codegen/README.md @@ -0,0 +1,26 @@ +# string-cache-codegen + +Example usage: + +``` +extern crate string_cache_codegen; + +use std::env; +use std::fs::File; +use std::io::BufWriter; +use std::path::Path; + +fn main() { + let file = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); + let mut file = BufWriter::new(File::create(&file).unwrap()); + + string_cache_codegen::AtomSetBuilder::new() + .atom("a") + .atom("b") + .atom("c") + .build(&mut file, "Alphabet", "ALPHABET_ATOMS", "alphabet"); +} +``` + +Then just add `include!(concat!(env!("OUT_DIR"), "/codegen.rs"));` to the root +of your crate. diff --git a/string-cache-codegen/src/lib.rs b/string-cache-codegen/src/lib.rs new file mode 100644 index 0000000..512ca7a --- /dev/null +++ b/string-cache-codegen/src/lib.rs @@ -0,0 +1,96 @@ +extern crate phf_codegen; + +use std::io::Write; + +/// A builder for a static atom set and relevant macros +pub struct AtomSetBuilder { + atoms: Vec<&'static str>, +} + +impl AtomSetBuilder { + /// Constructs a new static atom set builder + pub fn new() -> AtomSetBuilder { + AtomSetBuilder { + atoms: vec![], + } + } + + /// Adds an atom to the builder + pub fn atom(&mut self, s: &'static str) -> &mut AtomSetBuilder { + self.atoms.push(s); + self + } + + /// Adds multiple atoms to the builder + pub fn atoms(&mut self, ss: &[&'static str]) -> &mut AtomSetBuilder { + // `self.atoms.extend_from_slice(ss);` in newer rust + for s in ss { + self.atoms.push(s); + } + self + } + + /// Constructs a new atom type with the name `atom_type_name`, a static atom + /// set with the name `static_set_name` and a macro with the name + /// `macro_name` for converting strings to static atoms at compile time. + /// Using the macro requires you to include the generated file in the root + /// of your crate, likely with the `include!` macro. + pub fn build(&self, w: &mut W, atom_type_name: &str, static_set_name: &str, macro_name: &str) where W: Write { + if self.atoms.is_empty() { + panic!("must have more than one atom of a kind"); + } + self.build_kind_definition(w, static_set_name, atom_type_name); + self.build_static_atom_set(w, static_set_name); + self.build_atom_macro(w, macro_name, atom_type_name); + } + + fn build_kind_definition(&self, w: &mut W, static_set_name: &str, atom_type_name: &str) where W: Write { + writeln!(w, "#[derive(Eq, Hash, Ord, PartialEq, PartialOrd)]").unwrap(); + writeln!(w, "pub struct {}Kind;", atom_type_name).unwrap(); + writeln!(w, " +impl ::string_cache::atom::Kind for {atom_type_name}Kind {{ + #[inline] + fn get_index_or_hash(s: &str) -> Result {{ + match {static_set_name}.get_index(s) {{ + Some(i) => Ok(i as u32), + None => Err(::string_cache::shared::dynamic_hash(s)), + }} + }} + + #[inline] + fn index(i: u32) -> Option<&'static str> {{ + {static_set_name}.index(i as usize).map(|&s| s) + }} +}} +", atom_type_name=atom_type_name, static_set_name=static_set_name).unwrap(); + writeln!(w, "pub type {} = ::string_cache::atom::BaseAtom<{}Kind>;", atom_type_name, atom_type_name).unwrap(); + writeln!(w, "pub type Borrowed{}<'a> = ::string_cache::atom::BorrowedBaseAtom<'a, {}Kind>;", atom_type_name, atom_type_name).unwrap(); + } + + fn build_static_atom_set(&self, w: &mut W, static_set_name: &str) where W: Write { + writeln!(w, "pub static {}: ::string_cache::shared::phf::OrderedSet<&'static str> = ", static_set_name).unwrap(); + let mut builder = phf_codegen::OrderedSet::new(); + for &atom in &self.atoms { + builder.entry(atom); + } + builder.phf_path("::string_cache::shared::phf").build(w).unwrap(); + writeln!(w, ";").unwrap(); + } + + fn build_atom_macro(&self, w: &mut W, macro_name: &str, atom_type_name: &str) where W: Write { + writeln!(w, r"#[macro_export]").unwrap(); + writeln!(w, r"macro_rules! {} {{", macro_name).unwrap(); + for (i, s) in self.atoms.iter().enumerate() { + let data = pack_static(i as u32); + writeln!(w, r"({:?}) => {{ $crate::{} {{ unsafe_data: 0x{:x}, kind: ::std::marker::PhantomData }} }};", s, atom_type_name, data).unwrap(); + } + writeln!(w, r"}}").unwrap(); + } +} + +// Duplicated from string_cache::shared to lift dependency on string_cache +const STATIC_TAG: u8 = 0b_10; +const STATIC_SHIFT_BITS: usize = 32; +fn pack_static(n: u32) -> u64 { + (STATIC_TAG as u64) | ((n as u64) << STATIC_SHIFT_BITS) +} diff --git a/string-cache-codegen/test/Cargo.toml b/string-cache-codegen/test/Cargo.toml new file mode 100644 index 0000000..738f73f --- /dev/null +++ b/string-cache-codegen/test/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "string_cache_codegen_test" +authors = [ "The Servo Project Developers" ] +version = "0.0.0" +build = "build.rs" + +[build-dependencies.string_cache_codegen] +path = ".." + +[dependencies.string_cache] +path = "../.." diff --git a/string-cache-codegen/test/build.rs b/string-cache-codegen/test/build.rs new file mode 100644 index 0000000..1e655c4 --- /dev/null +++ b/string-cache-codegen/test/build.rs @@ -0,0 +1,17 @@ +extern crate string_cache_codegen; + +use std::env; +use std::fs::File; +use std::io::BufWriter; +use std::path::Path; + +fn main() { + let file = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); + let mut file = BufWriter::new(File::create(&file).unwrap()); + + string_cache_codegen::AtomSetBuilder::new() + .atom("a") + .atom("b") + .atom("c") + .build(&mut file, "Alphabet", "ALPHABET_ATOMS", "alphabet"); +} diff --git a/string-cache-codegen/test/src/lib.rs b/string-cache-codegen/test/src/lib.rs new file mode 100644 index 0000000..dfae179 --- /dev/null +++ b/string-cache-codegen/test/src/lib.rs @@ -0,0 +1,20 @@ +extern crate string_cache; + +include!(concat!(env!("OUT_DIR"), "/codegen.rs")); + +#[cfg(test)] +mod test { + use super::{ALPHABET_ATOMS, Alphabet, BorrowedAlphabet}; + use string_cache::atom::BorrowedBaseAtom; + #[test] + fn static_atom_set() { + let a: Alphabet = alphabet!("a"); + assert!(&*a == "a"); + assert!(&*alphabet!("b") == "b"); + assert!(ALPHABET_ATOMS.contains("c")); + assert!(!ALPHABET_ATOMS.contains("d")); + assert!(ALPHABET_ATOMS.len() == 3); + let ba: BorrowedAlphabet = BorrowedBaseAtom(&a); + assert!(ba == alphabet!("a")); + } +}