diff --git a/build.rs b/build.rs index e432b63..80286bd 100644 --- a/build.rs +++ b/build.rs @@ -5,15 +5,21 @@ use std::path::Path; type StrResult = Result; /// A module of definitions. -struct Module<'a>(Vec<(&'a str, Def<'a>)>); +struct Module<'a>(Vec<(&'a str, Binding<'a>)>); impl<'a> Module<'a> { - fn new(mut list: Vec<(&'a str, Def<'a>)>) -> Self { + fn new(mut list: Vec<(&'a str, Binding<'a>)>) -> Self { list.sort_by_key(|&(name, _)| name); Self(list) } } +/// A definition bound in a module, with metadata. +struct Binding<'a> { + def: Def<'a>, + deprecation: Option<&'a str>, +} + /// A definition in a module. enum Def<'a> { Symbol(Symbol<'a>), @@ -30,6 +36,7 @@ enum Symbol<'a> { #[derive(Debug, Copy, Clone)] enum Line<'a> { Blank, + Deprecated(&'a str), ModuleStart(&'a str), ModuleEnd, Symbol(&'a str, Option), @@ -91,7 +98,9 @@ fn tokenize(line: &str) -> StrResult { None => (line, None), }; - Ok(if tail == Some("{") { + Ok(if head == "@deprecated:" { + Line::Deprecated(tail.ok_or("missing deprecation message")?.trim()) + } else if tail == Some("{") { validate_ident(head)?; Line::ModuleStart(head) } else if head == "}" && tail.is_none() { @@ -137,11 +146,18 @@ fn decode_char(text: &str) -> StrResult { /// Turns a stream of lines into a list of definitions. fn parse<'a>( p: &mut Peekable>>>, -) -> StrResult)>> { +) -> StrResult)>> { let mut defs = vec![]; + let mut deprecation = None; loop { match p.next().transpose()? { - None | Some(Line::ModuleEnd) => break, + None | Some(Line::ModuleEnd) => { + if let Some(message) = deprecation { + return Err(format!("dangling `@deprecated: {}`", message)); + } + break; + } + Some(Line::Deprecated(message)) => deprecation = Some(message), Some(Line::Symbol(name, c)) => { let mut variants = vec![]; while let Some(Line::Variant(name, c)) = p.peek().cloned().transpose()? { @@ -159,11 +175,19 @@ fn parse<'a>( Symbol::Single(c) }; - defs.push((name, Def::Symbol(symbol))); + defs.push((name, Binding { def: Def::Symbol(symbol), deprecation })); + deprecation = None; } Some(Line::ModuleStart(name)) => { let module_defs = parse(p)?; - defs.push((name, Def::Module(Module::new(module_defs)))); + defs.push(( + name, + Binding { + def: Def::Module(Module::new(module_defs)), + deprecation, + }, + )); + deprecation = None; } other => return Err(format!("expected definition, found {other:?}")), } @@ -174,9 +198,9 @@ fn parse<'a>( /// Encodes a `Module` into Rust code. fn encode(buf: &mut String, module: &Module) { buf.push_str("Module(&["); - for (name, def) in &module.0 { - write!(buf, "({name:?},").unwrap(); - match def { + for (name, entry) in &module.0 { + write!(buf, "({name:?}, Binding {{ def: ").unwrap(); + match &entry.def { Def::Module(module) => { buf.push_str("Def::Module("); encode(buf, module); @@ -191,7 +215,7 @@ fn encode(buf: &mut String, module: &Module) { buf.push_str(")"); } } - buf.push_str("),"); + write!(buf, ", deprecation: {:?} }}),", entry.deprecation).unwrap(); } buf.push_str("])"); } diff --git a/src/lib.rs b/src/lib.rs index d541534..ae64ee1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,12 @@ Human-friendly notation for Unicode symbols. */ /// A module of definitions. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct Module(&'static [(&'static str, Def)]); +#[derive(Debug, Copy, Clone)] +pub struct Module(&'static [(&'static str, Binding)]); impl Module { - /// Try to get a definition from the module. - pub fn get(&self, name: &str) -> Option { + /// Try to get a bound definition in the module. + pub fn get(&self, name: &str) -> Option { self.0 .binary_search_by_key(&name, |(k, _)| k) .ok() @@ -16,13 +16,29 @@ impl Module { } /// Iterate over the module's definition. - pub fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter().copied() } } +/// A definition bound in a module, with metadata. +#[derive(Debug, Copy, Clone)] +pub struct Binding { + /// The bound definition. + pub def: Def, + /// A deprecation message for the definition, if it is deprecated. + pub deprecation: Option<&'static str>, +} + +impl Binding { + /// Create a new bound definition. + pub const fn new(definition: Def) -> Self { + Self { def: definition, deprecation: None } + } +} + /// A definition in a module. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone)] pub enum Def { /// A symbol, potentially with modifiers. Symbol(Symbol), @@ -31,7 +47,7 @@ pub enum Def { } /// A symbol, either a leaf or with modifiers. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone)] pub enum Symbol { /// A symbol without modifiers. Single(char), @@ -40,8 +56,10 @@ pub enum Symbol { } /// A module that contains the other top-level modules. -pub const ROOT: Module = - Module(&[("emoji", Def::Module(EMOJI)), ("sym", Def::Module(SYM))]); +pub const ROOT: Module = Module(&[ + ("emoji", Binding::new(Def::Module(EMOJI))), + ("sym", Binding::new(Def::Module(SYM))), +]); include!(concat!(env!("OUT_DIR"), "/out.rs")); @@ -54,8 +72,8 @@ mod test { fn assert_sorted_recursively(root: Module) { assert!(root.0.is_sorted_by_key(|(k, _)| k)); - for (_, def) in root.iter() { - if let Def::Module(module) = def { + for (_, entry) in root.iter() { + if let Def::Module(module) = entry.def { assert_sorted_recursively(module) } }