Automatic rules registration

This commit is contained in:
ef3d0c3e 2024-08-08 14:12:16 +02:00
parent 85fe0425b9
commit 4784921bb8
24 changed files with 157 additions and 74 deletions

View file

@ -296,6 +296,7 @@ impl Element for Code {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct CodeRule {
re: [Regex; 2],
properties: PropertyParser,
@ -330,6 +331,7 @@ impl CodeRule {
impl RegexRule for CodeRule {
fn name(&self) -> &'static str { "Code" }
fn previous(&self) -> Option<&'static str> { Some("List") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -38,6 +38,7 @@ impl Element for Comment {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct CommentRule {
re: [Regex; 1],
}
@ -53,6 +54,8 @@ impl CommentRule {
impl RegexRule for CommentRule {
fn name(&self) -> &'static str { "Comment" }
fn previous(&self) -> Option<&'static str> { None }
fn regexes(&self) -> &[Regex] { &self.re }
fn on_regex_match<'a>(

View file

@ -178,10 +178,16 @@ impl RuleState for CustomStyleState {
static STATE_NAME: &'static str = "elements.custom_style";
#[auto_registry::auto_registry(registry = "rules")]
pub struct CustomStyleRule;
impl CustomStyleRule {
pub fn new() -> Self { Self{} }
}
impl Rule for CustomStyleRule {
fn name(&self) -> &'static str { "Custom Style" }
fn previous(&self) -> Option<&'static str> { Some("Style") }
fn next_match(&self, state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
let content = cursor.source.content();

View file

@ -21,6 +21,7 @@ use crate::parser::rule::Rule;
use crate::parser::source::Cursor;
use crate::parser::source::Source;
#[auto_registry::auto_registry(registry = "rules")]
pub struct ElemStyleRule {
start_re: Regex,
}
@ -58,6 +59,7 @@ impl ElemStyleRule {
impl Rule for ElemStyleRule {
fn name(&self) -> &'static str { "Element Style" }
fn previous(&self) -> Option<&'static str> { Some("Script") }
fn next_match(&self, _state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.start_re

View file

@ -146,6 +146,7 @@ impl Element for Graphviz {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct GraphRule {
re: [Regex; 1],
properties: PropertyParser,
@ -178,6 +179,7 @@ impl GraphRule {
impl RegexRule for GraphRule {
fn name(&self) -> &'static str { "Graph" }
fn previous(&self) -> Option<&'static str> { Some("Tex") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -17,6 +17,7 @@ use std::rc::Rc;
use super::paragraph::Paragraph;
#[auto_registry::auto_registry(registry = "rules")]
pub struct ImportRule {
re: [Regex; 1],
}
@ -40,6 +41,7 @@ impl ImportRule {
impl RegexRule for ImportRule {
fn name(&self) -> &'static str { "Import" }
fn previous(&self) -> Option<&'static str> { Some("Paragraph") }
fn regexes(&self) -> &[Regex] { &self.re }

View file

@ -284,6 +284,7 @@ impl RuleState for LayoutState {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct LayoutRule {
re: [Regex; 3],
}
@ -378,6 +379,7 @@ static STATE_NAME: &'static str = "elements.layout";
impl RegexRule for LayoutRule {
fn name(&self) -> &'static str { "Layout" }
fn previous(&self) -> Option<&'static str> { Some("Media") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -71,6 +71,7 @@ impl ContainerElement for Link {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct LinkRule {
re: [Regex; 1],
}
@ -85,6 +86,7 @@ impl LinkRule {
impl RegexRule for LinkRule {
fn name(&self) -> &'static str { "Link" }
fn previous(&self) -> Option<&'static str> { Some("Link") }
fn regexes(&self) -> &[Regex] { &self.re }

View file

@ -106,6 +106,7 @@ impl ContainerElement for ListEntry {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct ListRule {
start_re: Regex,
continue_re: Regex,
@ -249,6 +250,7 @@ impl ListRule {
impl Rule for ListRule {
fn name(&self) -> &'static str { "List" }
fn previous(&self) -> Option<&'static str> { Some("Raw") }
fn next_match(&self, _state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.start_re

View file

@ -221,6 +221,7 @@ impl ReferenceableElement for Medium {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct MediaRule {
re: [Regex; 1],
properties: PropertyParser,
@ -325,6 +326,7 @@ impl MediaRule {
impl RegexRule for MediaRule {
fn name(&self) -> &'static str { "Media" }
fn previous(&self) -> Option<&'static str> { Some("Graph") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -9,7 +9,6 @@ pub mod media;
pub mod paragraph;
pub mod raw;
pub mod reference;
pub mod registrar;
pub mod script;
pub mod section;
pub mod style;

View file

@ -91,6 +91,7 @@ impl ContainerElement for Paragraph {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct ParagraphRule {
re: Regex,
}
@ -104,7 +105,8 @@ impl ParagraphRule {
}
impl Rule for ParagraphRule {
fn name(&self) -> &'static str { "Paragraphing" }
fn name(&self) -> &'static str { "Paragraph" }
fn previous(&self) -> Option<&'static str> { Some("Comment") }
fn next_match(&self, _state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.re

View file

@ -44,6 +44,7 @@ impl Element for Raw {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct RawRule {
re: [Regex; 1],
properties: PropertyParser,
@ -72,6 +73,7 @@ impl RawRule {
impl RegexRule for RawRule {
fn name(&self) -> &'static str { "Raw" }
fn previous(&self) -> Option<&'static str> { Some("Variable Substitution") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -62,6 +62,7 @@ impl Element for Reference {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct ReferenceRule {
re: [Regex; 1],
properties: PropertyParser,
@ -127,6 +128,7 @@ impl ReferenceRule {
impl RegexRule for ReferenceRule {
fn name(&self) -> &'static str { "Reference" }
fn previous(&self) -> Option<&'static str> { Some("Text") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -1,46 +0,0 @@
use crate::parser::parser::Parser;
use super::code::CodeRule;
use super::comment::CommentRule;
use super::elemstyle::ElemStyleRule;
use super::graphviz::GraphRule;
use super::import::ImportRule;
use super::layout::LayoutRule;
use super::link::LinkRule;
use super::list::ListRule;
use super::media::MediaRule;
use super::paragraph::ParagraphRule;
use super::raw::RawRule;
use super::script::ScriptRule;
use super::section::SectionRule;
use super::style::StyleRule;
use super::customstyle::CustomStyleRule;
use super::tex::TexRule;
use super::text::TextRule;
use super::variable::VariableRule;
use super::variable::VariableSubstitutionRule;
use super::reference::ReferenceRule;
pub fn register<P: Parser>(parser: &mut P) {
parser.add_rule(Box::new(CommentRule::new()), None).unwrap();
parser.add_rule(Box::new(ParagraphRule::new()), None).unwrap();
parser.add_rule(Box::new(ImportRule::new()), None).unwrap();
parser.add_rule(Box::new(ScriptRule::new()), None).unwrap();
parser.add_rule(Box::new(ElemStyleRule::new()), None).unwrap();
parser.add_rule(Box::new(VariableRule::new()), None).unwrap();
parser.add_rule(Box::new(VariableSubstitutionRule::new()), None).unwrap();
parser.add_rule(Box::new(RawRule::new()), None).unwrap();
parser.add_rule(Box::new(ListRule::new()), None).unwrap();
parser.add_rule(Box::new(CodeRule::new()), None).unwrap();
parser.add_rule(Box::new(TexRule::new()), None).unwrap();
parser.add_rule(Box::new(GraphRule::new()), None).unwrap();
parser.add_rule(Box::new(MediaRule::new()), None).unwrap();
parser.add_rule(Box::new(LayoutRule::new()), None).unwrap();
parser.add_rule(Box::new(StyleRule::new()), None).unwrap();
parser.add_rule(Box::new(CustomStyleRule{}), None).unwrap();
parser.add_rule(Box::new(SectionRule::new()), None).unwrap();
parser.add_rule(Box::new(LinkRule::new()), None).unwrap();
parser.add_rule(Box::new(TextRule::default()), None).unwrap();
parser.add_rule(Box::new(ReferenceRule::new()), None).unwrap();
}

View file

@ -20,6 +20,7 @@ use std::rc::Rc;
use super::text::Text;
#[auto_registry::auto_registry(registry = "rules")]
pub struct ScriptRule {
re: [Regex; 2],
eval_kinds: [(&'static str, &'static str); 3],
@ -77,6 +78,7 @@ impl ScriptRule {
impl RegexRule for ScriptRule {
fn name(&self) -> &'static str { "Script" }
fn previous(&self) -> Option<&'static str> { Some("Import") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -135,6 +135,7 @@ impl ReferenceableElement for Section {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct SectionRule {
re: [Regex; 1],
}
@ -155,6 +156,7 @@ pub mod section_kind {
impl RegexRule for SectionRule {
fn name(&self) -> &'static str { "Section" }
fn previous(&self) -> Option<&'static str> { Some("Custom Style") }
fn regexes(&self) -> &[Regex] { &self.re }

View file

@ -132,6 +132,7 @@ impl RuleState for StyleState {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct StyleRule {
re: [Regex; 4],
}
@ -157,6 +158,7 @@ static STATE_NAME: &'static str = "elements.style";
impl RegexRule for StyleRule {
fn name(&self) -> &'static str { "Style" }
fn previous(&self) -> Option<&'static str> { Some("Layout") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -219,6 +219,7 @@ impl Element for Tex {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct TexRule {
re: [Regex; 2],
properties: PropertyParser,
@ -296,6 +297,7 @@ impl TexRule {
impl RegexRule for TexRule {
fn name(&self) -> &'static str { "Tex" }
fn previous(&self) -> Option<&'static str> { Some("Code") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -42,13 +42,20 @@ impl Element for Text {
}
}
#[derive(Default)]
#[auto_registry::auto_registry(registry = "rules")]
pub struct TextRule;
impl TextRule {
pub fn new() -> Self { Self {} }
}
impl Rule for TextRule {
fn name(&self) -> &'static str { "Text" }
fn previous(&self) -> Option<&'static str> { Some("Link") }
fn next_match(&self, _state: &ParserState, _cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { None }
fn next_match(&self, _state: &ParserState, _cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
None
}
fn on_match(
&self,

View file

@ -37,6 +37,7 @@ impl FromStr for VariableKind {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct VariableRule {
re: [Regex; 1],
kinds: Vec<(String, String)>,
@ -117,6 +118,7 @@ impl VariableRule {
impl RegexRule for VariableRule {
fn name(&self) -> &'static str { "Variable" }
fn previous(&self) -> Option<&'static str> { Some("Element Style") }
fn regexes(&self) -> &[Regex] { &self.re }
@ -295,6 +297,7 @@ impl RegexRule for VariableRule {
}
}
#[auto_registry::auto_registry(registry = "rules")]
pub struct VariableSubstitutionRule {
re: [Regex; 1],
}
@ -309,6 +312,7 @@ impl VariableSubstitutionRule {
impl RegexRule for VariableSubstitutionRule {
fn name(&self) -> &'static str { "Variable Substitution" }
fn previous(&self) -> Option<&'static str> { Some("Variable") }
fn regexes(&self) -> &[regex::Regex] { &self.re }

View file

@ -4,7 +4,6 @@ use std::rc::Rc;
use crate::document::document::Document;
use crate::document::element::DocumentEnd;
use crate::document::langdocument::LangDocument;
use crate::elements::registrar::register;
use crate::elements::text::Text;
use super::parser::Parser;
@ -35,8 +34,10 @@ impl LangParser {
};
// Register rules
// TODO: use https://docs.rs/inventory/latest/inventory/
register(&mut s);
for rule in super::rule::get_rule_registry()
{
s.add_rule(rule).unwrap();
}
s
}

View file

@ -19,7 +19,6 @@ use crate::document::document::DocumentAccessors;
use crate::document::element::ContainerElement;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::elements::customstyle::CustomStyleRule;
use crate::elements::paragraph::Paragraph;
use crate::lua::kernel::Kernel;
use crate::lua::kernel::KernelHolder;
@ -186,7 +185,6 @@ impl<'a, 'b> ParserState<'a, 'b> {
.for_each(|(rule, (matched_at, match_data))| {
// Don't upate if not stepped over yet
if *matched_at > cursor.pos {
// TODO: maybe we should expose matches() so it becomes possible to dynamically register a new rule
return;
}
@ -369,7 +367,7 @@ pub trait Parser {
/// # Warning
///
/// This method must not be called if a [`ParserState`] for this parser exists.
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String> {
fn add_rule(&mut self, rule: Box<dyn Rule>) -> Result<(), String> {
if let Some(_) = self
.rules()
.iter()
@ -381,23 +379,7 @@ pub trait Parser {
));
}
// Try to insert after
if let Some(after) = after {
let index = self
.rules()
.iter()
.enumerate()
.find(|(_, rule)| rule.name() == after)
.map(|(idx, _)| idx);
if let Some(index) = index {
self.rules_mut().insert(index, rule);
} else {
return Err(format!("Unable to find rule `{after}` to insert after"));
}
} else {
self.rules_mut().push(rule);
}
self.rules_mut().push(rule);
Ok(())
}

View file

@ -12,12 +12,66 @@ use mlua::Function;
use mlua::Lua;
use std::any::Any;
use std::collections::HashMap;
use std::ops::Range;
use std::rc::Rc;
macro_rules! create_registry {
( $($construct:expr),+ $(,)? ) => {{
let mut map = HashMap::new();
$(
let boxed = Box::new($construct) as Box<dyn Rule>;
map.insert(boxed.name(), boxed);
)+
map
}};
}
/// Gets the list of all rules exported with the [`auto_registry`] proc macro.
/// Rules are sorted according to topological order using the [`Rule::previous`] method.
#[auto_registry::generate_registry(registry = "rules", target = make_rules, return_type = HashMap<&'static str, Box<dyn Rule>>, maker = create_registry)]
pub fn get_rule_registry() -> Vec<Box<dyn Rule>> {
fn cmp(
map: &HashMap<&'static str, Box<dyn Rule>>,
lname: &'static str,
rname: &'static str,
) -> std::cmp::Ordering {
let l = map.get(lname).unwrap();
let r = map.get(rname).unwrap();
if l.previous() == Some(r.name()) {
std::cmp::Ordering::Greater
} else if r.previous() == Some(l.name()) {
std::cmp::Ordering::Less
} else if l.previous().is_some() && r.previous().is_none() {
std::cmp::Ordering::Greater
} else if r.previous().is_some() && l.previous().is_none() {
std::cmp::Ordering::Less
} else if let (Some(pl), Some(pr)) = (l.previous(), r.previous()) {
cmp(map, pl, pr)
} else {
std::cmp::Ordering::Equal
}
}
let mut map = make_rules();
let mut sorted_keys = map.iter().map(|(key, _)| *key).collect::<Vec<_>>();
sorted_keys.sort_by(|l, r| cmp(&map, l, r));
let mut owned = Vec::with_capacity(sorted_keys.len());
for key in sorted_keys {
let rule = map.remove(key).unwrap();
owned.push(rule);
}
owned
}
pub trait Rule: Downcast {
/// Returns rule's name
/// The rule name
fn name(&self) -> &'static str;
/// The name of the rule that should come before this one
fn previous(&self) -> Option<&'static str>;
/// Finds the next match starting from [`cursor`]
fn next_match(&self, state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)>;
/// Callback when rule matches
@ -47,8 +101,12 @@ impl core::fmt::Debug for dyn Rule {
}
pub trait RegexRule {
/// The rule name
fn name(&self) -> &'static str;
/// The name of the rule that should come before this one
fn previous(&self) -> Option<&'static str>;
/// Returns the rule's regexes
fn regexes(&self) -> &[regex::Regex];
@ -69,6 +127,7 @@ pub trait RegexRule {
impl<T: RegexRule + 'static> Rule for T {
fn name(&self) -> &'static str { RegexRule::name(self) }
fn previous(&self) -> Option<&'static str> { RegexRule::previous(self) }
/// Finds the next match starting from [`cursor`]
fn next_match(&self, _state: &ParserState, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
@ -120,3 +179,41 @@ impl<T: RegexRule + 'static> Rule for T {
fn register_layouts(&self, holder: &mut LayoutHolder) { self.register_layouts(holder); }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry() {
let rules = get_rule_registry();
let names: Vec<&'static str> = rules.iter().map(|rule| rule.name()).collect();
assert_eq!(
names,
vec![
"Comment",
"Paragraph",
"Import",
"Script",
"Element Style",
"Variable",
"Variable Substitution",
"Raw",
"List",
"Code",
"Tex",
"Graph",
"Media",
"Layout",
"Style",
"Custom Style",
"Section",
"Link",
"Text",
"Reference",
]
);
}
}