diff --git a/Cargo.lock b/Cargo.lock index bfc1544..2e0f9b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -741,6 +741,7 @@ dependencies = [ "lsp-server", "lsp-types 0.97.0", "mlua", + "rand 0.8.5", "regex", "rusqlite", "rust-crypto", diff --git a/Cargo.toml b/Cargo.toml index 6838393..660725f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,6 @@ tokio = { version = "1.38.1", features = ["macros", "rt-multi-thread", "io-std"] tower-lsp = "0.20.0" unicode-segmentation = "1.11.0" walkdir = "2.5.0" + +[dev-dependencies] +rand = "0.8.5" diff --git a/src/document/customstyle.rs b/src/document/customstyle.rs new file mode 100644 index 0000000..9cff95a --- /dev/null +++ b/src/document/customstyle.rs @@ -0,0 +1,57 @@ +use std::cell::Ref; +use std::cell::RefMut; +use std::collections::HashMap; +use std::ops::Range; +use std::rc::Rc; + +use ariadne::Report; + +use crate::parser::parser::Parser; +use crate::parser::source::Source; +use crate::parser::source::Token; + +use super::document::Document; + +#[derive(Debug, PartialEq, Eq)] +pub enum CustomStyleToken { + Toggle(String), + Pair(String, String), +} + +pub trait CustomStyle: core::fmt::Debug { + /// Name for the custom style + fn name(&self) -> &str; + /// Gets the begin and end token for a custom style + fn tokens(&self) -> &CustomStyleToken; + + fn on_start<'a>( + &self, + location: Token, + parser: &dyn Parser, + document: &'a (dyn Document<'a> + 'a), + ) -> Result<(), Report<(Rc, Range)>>; + fn on_end<'a>( + &self, + location: Token, + parser: &dyn Parser, + document: &'a (dyn Document<'a> + 'a), + ) -> Result<(), Report<(Rc, Range)>>; +} + +pub trait CustomStyleHolder { + /// gets a reference to all defined custom styles + fn custom_styles(&self) -> Ref<'_, HashMap>>; + + /// gets a (mutable) reference to all defined custom styles + fn custom_styles_mut(&self) -> RefMut<'_, HashMap>>; + + fn get_custom_style(&self, style_name: &str) -> Option> { + self.custom_styles() + .get(style_name) + .map(|style| style.clone()) + } + + fn insert_custom_style(&self, style: Rc) { + self.custom_styles_mut().insert(style.name().into(), style); + } +} diff --git a/src/document/element.rs b/src/document/element.rs index 59ac9a6..049519a 100644 --- a/src/document/element.rs +++ b/src/document/element.rs @@ -3,7 +3,6 @@ use std::str::FromStr; use crate::compiler::compiler::Compiler; use crate::elements::reference::Reference; use crate::parser::source::Token; -use crate::parser::util::PropertyParser; use downcast_rs::impl_downcast; use downcast_rs::Downcast; diff --git a/src/document/mod.rs b/src/document/mod.rs index 58fc559..c538a99 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -5,3 +5,4 @@ pub mod element; pub mod variable; pub mod style; pub mod layout; +pub mod customstyle; diff --git a/src/document/style.rs b/src/document/style.rs index 584f851..1ace157 100644 --- a/src/document/style.rs +++ b/src/document/style.rs @@ -18,9 +18,6 @@ pub trait ElementStyle: Downcast + core::fmt::Debug { /// Will fail if deserialization fails fn from_json(&self, json: &str) -> Result, String>; - /// Serializes sytle into json string - fn to_json(&self) -> String; - /// Attempts to deserialize lua table into a new style fn from_lua( &self, @@ -32,24 +29,24 @@ impl_downcast!(ElementStyle); pub trait StyleHolder { /// gets a reference to all defined styles - fn styles(&self) -> Ref<'_, HashMap>>; + fn element_styles(&self) -> Ref<'_, HashMap>>; /// gets a (mutable) reference to all defined styles - fn styles_mut(&self) -> RefMut<'_, HashMap>>; + fn element_styles_mut(&self) -> RefMut<'_, HashMap>>; /// Checks if a given style key is registered - fn is_style_registered(&self, style_key: &str) -> bool { self.styles().contains_key(style_key) } + fn is_style_registered(&self, style_key: &str) -> bool { self.element_styles().contains_key(style_key) } /// Gets the current active style for an element /// NOTE: Will panic if a style is not defined for a given element /// If you need to process user input, use [`is_registered`] fn current_style(&self, style_key: &str) -> Rc { - self.styles().get(style_key).map(|rc| rc.clone()).unwrap() + self.element_styles().get(style_key).map(|rc| rc.clone()).unwrap() } /// Sets the [`style`] fn set_current_style(&self, style: Rc) { - self.styles_mut().insert(style.key().to_string(), style); + self.element_styles_mut().insert(style.key().to_string(), style); } } @@ -65,8 +62,6 @@ macro_rules! impl_elementstyle { .map(|obj| std::rc::Rc::new(obj) as std::rc::Rc) } - fn to_json(&self) -> String { serde_json::to_string(self).unwrap() } - fn from_lua( &self, lua: &mlua::Lua, diff --git a/src/elements/customstyle.rs b/src/elements/customstyle.rs new file mode 100644 index 0000000..6a256a6 --- /dev/null +++ b/src/elements/customstyle.rs @@ -0,0 +1,557 @@ +use std::any::Any; +use std::cell::RefCell; +use std::collections::HashMap; +use std::ops::Range; +use std::rc::Rc; +use std::sync::Arc; + +use ariadne::Fmt; +use ariadne::Label; +use ariadne::Report; +use ariadne::ReportKind; +use mlua::Error::BadArgument; +use mlua::Function; +use mlua::Lua; + +use crate::document::customstyle::CustomStyle; +use crate::document::customstyle::CustomStyleToken; +use crate::document::document::Document; +use crate::document::document::DocumentAccessors; +use crate::lua::kernel::function_with_context; +use crate::lua::kernel::KernelContext; +use crate::lua::kernel::CTX; +use crate::parser::parser::Parser; +use crate::parser::rule::Rule; +use crate::parser::source::Cursor; +use crate::parser::source::Source; +use crate::parser::source::Token; +use crate::parser::state::Scope; +use crate::parser::state::State; + +use lazy_static::lazy_static; + +use super::paragraph::Paragraph; + +#[derive(Debug)] +struct LuaCustomStyle { + pub(self) name: String, + pub(self) tokens: CustomStyleToken, + pub(self) start: String, + pub(self) end: String, +} + +impl CustomStyle for LuaCustomStyle { + fn name(&self) -> &str { self.name.as_str() } + + fn tokens(&self) -> &CustomStyleToken { &self.tokens } + + fn on_start<'a>( + &self, + location: Token, + parser: &dyn Parser, + document: &'a dyn Document<'a>, + ) -> Result<(), Report<(Rc, Range)>> { + let kernel = parser.get_kernel("main").unwrap(); + let ctx = KernelContext { + location: location.clone(), + parser, + document, + }; + + let mut result = Ok(()); + kernel.run_with_context(ctx, |lua| { + let chunk = lua.load(self.start.as_str()); + if let Err(err) = chunk.eval::<()>() { + result = Err( + Report::build(ReportKind::Error, location.source(), location.start()) + .with_message("Lua execution failed") + .with_label( + Label::new((location.source(), location.range.clone())) + .with_message(err.to_string()) + .with_color(parser.colors().error), + ) + .with_note(format!( + "When trying to start custom style {}", + self.name().fg(parser.colors().info) + )) + .finish(), + ); + } + }); + + result + } + + fn on_end<'a>( + &self, + location: Token, + parser: &dyn Parser, + document: &'a dyn Document<'a>, + ) -> Result<(), Report<(Rc, Range)>> { + let kernel = parser.get_kernel("main").unwrap(); + let ctx = KernelContext { + location: location.clone(), + parser, + document, + }; + + let mut result = Ok(()); + kernel.run_with_context(ctx, |lua| { + let chunk = lua.load(self.end.as_str()); + if let Err(err) = chunk.eval::<()>() { + result = Err( + Report::build(ReportKind::Error, location.source(), location.start()) + .with_message("Lua execution failed") + .with_label( + Label::new((location.source(), location.range.clone())) + .with_message(err.to_string()) + .with_color(parser.colors().error), + ) + .with_note(format!( + "When trying to end custom style {}", + self.name().fg(parser.colors().info) + )) + .finish(), + ); + } + }); + + result + } +} + +struct CustomStyleState { + toggled: HashMap, +} + +impl State for CustomStyleState { + fn scope(&self) -> Scope { Scope::PARAGRAPH } + + fn on_remove<'a>( + &self, + parser: &dyn Parser, + document: &dyn Document, + ) -> Vec, Range)>> { + let mut reports = vec![]; + + self.toggled.iter().for_each(|(style, token)| { + let paragraph = document.last_element::().unwrap(); + let paragraph_end = paragraph + .content + .last() + .and_then(|last| { + Some(( + last.location().source(), + last.location().end() - 1..last.location().end(), + )) + }) + .unwrap(); + + reports.push( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Unterminated Custom Style") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_order(1) + .with_message(format!( + "Style {} starts here", + style.fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .with_label( + Label::new(paragraph_end) + .with_order(1) + .with_message(format!("Paragraph ends here")) + .with_color(parser.colors().error), + ) + .with_note("Styles cannot span multiple documents (i.e @import)") + .finish(), + ); + }); + + return reports; + } +} + +pub struct CustomStyleRule; + +lazy_static! { + static ref STATE_NAME: String = "elements.custom_style".to_string(); +} + +impl Rule for CustomStyleRule { + fn name(&self) -> &'static str { "Custom Style" } + + fn next_match(&self, parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)> { + let content = cursor.source.content(); + + let mut closest_match = usize::MAX; + let mut matched_style = (None, false); + parser + .custom_styles() + .iter() + .for_each(|(_name, style)| match style.tokens() { + CustomStyleToken::Toggle(s) => { + if let Some(pos) = &content[cursor.pos..].find(s) { + if *pos < closest_match { + closest_match = *pos; + matched_style = (Some(style.clone()), false); + } + } + } + CustomStyleToken::Pair(begin, end) => { + if let Some(pos) = &content[cursor.pos..].find(begin) { + if *pos < closest_match { + closest_match = *pos; + matched_style = (Some(style.clone()), false); + } + } + + if let Some(pos) = &content[cursor.pos..].find(end) { + if *pos < closest_match { + closest_match = *pos; + matched_style = (Some(style.clone()), true); + } + } + } + }); + + if closest_match == usize::MAX { + None + } else { + Some(( + closest_match + cursor.pos, + Box::new((matched_style.0.unwrap().clone(), matched_style.1)) as Box, + )) + } + } + + fn on_match<'a>( + &self, + parser: &dyn Parser, + document: &'a dyn Document<'a>, + cursor: Cursor, + match_data: Option>, + ) -> (Cursor, Vec, Range)>>) { + let (style, end) = match_data + .as_ref() + .unwrap() + .downcast_ref::<(Rc, bool)>() + .unwrap(); + + let query = parser.state().query(&STATE_NAME); + let state = match query { + Some(state) => state, + None => { + // Insert as a new state + match parser.state_mut().insert( + STATE_NAME.clone(), + Rc::new(RefCell::new(CustomStyleState { + toggled: HashMap::new(), + })), + ) { + Err(_) => panic!("Unknown error"), + Ok(state) => state, + } + } + }; + + let (close, token) = match style.tokens() { + CustomStyleToken::Toggle(s) => { + let mut borrow = state.borrow_mut(); + let state = borrow.downcast_mut::().unwrap(); + + match state.toggled.get(style.name()) { + Some(_) => { + // Terminate style + let token = + Token::new(cursor.pos..cursor.pos + s.len(), cursor.source.clone()); + + state.toggled.remove(style.name()); + (true, token) + } + None => { + // Start style + let token = + Token::new(cursor.pos..cursor.pos + s.len(), cursor.source.clone()); + + state.toggled.insert(style.name().into(), token.clone()); + (false, token) + } + } + } + CustomStyleToken::Pair(s_begin, s_end) => { + let mut borrow = state.borrow_mut(); + let state = borrow.downcast_mut::().unwrap(); + + if *end { + // Terminate style + let token = + Token::new(cursor.pos..cursor.pos + s_end.len(), cursor.source.clone()); + if state.toggled.get(style.name()).is_none() { + return ( + cursor.at(cursor.pos + s_end.len()), + vec![ + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Invalid End of Style") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_order(1) + .with_message(format!( + "Cannot end style {} here, is it not started anywhere", + style.name().fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .finish(), + ], + ); + } + + state.toggled.remove(style.name()); + (true, token) + } else { + // Start style + let token = Token::new( + cursor.pos..cursor.pos + s_begin.len(), + cursor.source.clone(), + ); + if let Some(start_token) = state.toggled.get(style.name()) { + return ( + cursor.at(cursor.pos + s_end.len()), + vec![Report::build( + ReportKind::Error, + start_token.source(), + start_token.start(), + ) + .with_message("Invalid Start of Style") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_order(1) + .with_message(format!( + "Style cannot {} starts here", + style.name().fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .with_label( + Label::new((start_token.source(), start_token.range.clone())) + .with_order(2) + .with_message(format!( + "Style {} starts previously here", + style.name().fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .finish()], + ); + } + + state.toggled.insert(style.name().into(), token.clone()); + (false, token) + } + } + }; + + if let Err(rep) = if close { + style.on_end(token.clone(), parser, document) + } else { + style.on_start(token.clone(), parser, document) + } { + return ( + cursor.at(token.end()), + vec![unsafe { + // TODO + std::mem::transmute(rep) + }], + ); + } else { + (cursor.at(token.end()), vec![]) + } + } + + fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option)>> { + let mut bindings = vec![]; + + bindings.push(( + "define_toggled".into(), + lua.create_function( + |_, (name, token, on_start, on_end): (String, String, String, String)| { + let mut result = Ok(()); + + let style = LuaCustomStyle { + tokens: CustomStyleToken::Toggle(token), + name: name.clone(), + start: on_start, + end: on_end, + }; + + CTX.with_borrow(|ctx| { + ctx.as_ref().map(|ctx| { + if let Some(_) = ctx.parser.get_custom_style(name.as_str()) { + result = Err(BadArgument { + to: Some("define_toggled".to_string()), + pos: 1, + name: Some("name".to_string()), + cause: Arc::new(mlua::Error::external(format!( + "Custom style with name `{name}` already exists" + ))), + }); + return; + } + ctx.parser.insert_custom_style(Rc::new(style)); + }); + }); + + result + }, + ) + .unwrap(), + )); + + bindings.push(( + "define_paired".into(), + lua.create_function( + |_, + (name, token_start, token_end, on_start, on_end): ( + String, + String, + String, + String, + String, + )| { + let mut result = Ok(()); + + let style = LuaCustomStyle { + tokens: CustomStyleToken::Pair(token_start, token_end), + name: name.clone(), + start: on_start, + end: on_end, + }; + + CTX.with_borrow(|ctx| { + ctx.as_ref().map(|ctx| { + if let Some(_) = ctx.parser.get_custom_style(name.as_str()) { + result = Err(BadArgument { + to: Some("define_toggled".to_string()), + pos: 1, + name: Some("name".to_string()), + cause: Arc::new(mlua::Error::external(format!( + "Custom style with name `{name}` already exists" + ))), + }); + return; + } + ctx.parser.insert_custom_style(Rc::new(style)); + }); + }); + + result + }, + ) + .unwrap(), + )); + + Some(bindings) + } +} + +#[cfg(test)] +mod tests { + use crate::elements::raw::Raw; + use crate::elements::text::Text; + use crate::parser::langparser::LangParser; + use crate::parser::source::SourceFile; + use crate::validate_document; + + use super::*; + + #[test] + fn toggle() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +%<[main] +function my_style_start() + nml.raw.push("inline", "start") +end +function my_style_end() + nml.raw.push("inline", "end") +end +function red_style_start() + nml.raw.push("inline", "") +end +function red_style_end() + nml.raw.push("inline", "") +end +nml.custom_style.define_toggled("My Style", "|", "my_style_start()", "my_style_end()") +nml.custom_style.define_toggled("My Style2", "°", "red_style_start()", "red_style_end()") +>% +pre |styled| post °Hello°. +"# + .to_string(), + None, + )); + let parser = LangParser::default(); + let doc = parser.parse(source, None); + + validate_document!(doc.content().borrow(), 0, + Paragraph { + Text { content == "pre " }; + Raw { content == "start" }; + Text { content == "styled" }; + Raw { content == "end" }; + Text { content == " post " }; + Raw { content == "" }; + Text { content == "Hello" }; + Raw { content == "" }; + Text { content == "." }; + }; + ); + } + + #[test] + fn paired() { + let source = Rc::new(SourceFile::with_content( + "".to_string(), + r#" +%<[main] +function my_style_start() + nml.raw.push("inline", "start") +end +function my_style_end() + nml.raw.push("inline", "end") +end +function red_style_start() + nml.raw.push("inline", "") +end +function red_style_end() + nml.raw.push("inline", "") +end +nml.custom_style.define_paired("My Style", "[", "]", "my_style_start()", "my_style_end()") +nml.custom_style.define_paired("My Style2", "(", ")", "red_style_start()", "red_style_end()") +>% +pre [styled] post (Hello). +"# + .to_string(), + None, + )); + let parser = LangParser::default(); + let doc = parser.parse(source, None); + + validate_document!(doc.content().borrow(), 0, + Paragraph { + Text { content == "pre " }; + Raw { content == "start" }; + Text { content == "styled" }; + Raw { content == "end" }; + Text { content == " post " }; + Raw { content == "" }; + Text { content == "Hello" }; + Raw { content == "" }; + Text { content == "." }; + }; + ); + } +} diff --git a/src/elements/elemstyle.rs b/src/elements/elemstyle.rs index 5386839..393ec91 100644 --- a/src/elements/elemstyle.rs +++ b/src/elements/elemstyle.rs @@ -10,23 +10,15 @@ use ariadne::ReportKind; use mlua::Error::BadArgument; use mlua::Function; use mlua::Lua; -use mlua::LuaSerdeExt; -use mlua::Table; use mlua::Value; -use regex::Captures; use regex::Regex; use crate::document::document::Document; -use crate::document::{self}; use crate::lua::kernel::CTX; use crate::parser::parser::Parser; -use crate::parser::rule::RegexRule; use crate::parser::rule::Rule; use crate::parser::source::Cursor; use crate::parser::source::Source; -use crate::parser::source::Token; - -use super::variable::VariableRule; pub struct ElemStyleRule { start_re: Regex, @@ -66,7 +58,7 @@ impl ElemStyleRule { impl Rule for ElemStyleRule { fn name(&self) -> &'static str { "Element Style" } - fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box)> { + fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)> { self.start_re .find_at(cursor.source.content(), cursor.pos) .map_or(None, |m| { diff --git a/src/elements/list.rs b/src/elements/list.rs index cb648f6..889b16d 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -252,7 +252,7 @@ impl ListRule { impl Rule for ListRule { fn name(&self) -> &'static str { "List" } - fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box)> { + fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)> { self.start_re .find_at(cursor.source.content(), cursor.pos) .map_or(None, |m| { @@ -440,7 +440,6 @@ impl Rule for ListRule { #[cfg(test)] mod tests { use super::*; - use crate::compiler::compiler::Target; use crate::elements::paragraph::Paragraph; use crate::elements::text::Text; use crate::parser::langparser::LangParser; diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 066b14c..abfd1a5 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -17,3 +17,4 @@ pub mod tex; pub mod text; pub mod variable; pub mod elemstyle; +pub mod customstyle; diff --git a/src/elements/paragraph.rs b/src/elements/paragraph.rs index c8d3313..86bdaa1 100644 --- a/src/elements/paragraph.rs +++ b/src/elements/paragraph.rs @@ -108,7 +108,7 @@ impl ParagraphRule { impl Rule for ParagraphRule { fn name(&self) -> &'static str { "Paragraphing" } - fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box)> { + fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)> { self.re .find_at(cursor.source.content(), cursor.pos) .and_then(|m| Some((m.start(), Box::new([false; 0]) as Box))) diff --git a/src/elements/raw.rs b/src/elements/raw.rs index 7370219..d842a1d 100644 --- a/src/elements/raw.rs +++ b/src/elements/raw.rs @@ -27,10 +27,10 @@ use std::str::FromStr; use std::sync::Arc; #[derive(Debug)] -struct Raw { - pub(self) location: Token, - pub(self) kind: ElemKind, - pub(self) content: String, +pub struct Raw { + pub location: Token, + pub kind: ElemKind, + pub content: String, } impl Element for Raw { @@ -265,7 +265,6 @@ impl RegexRule for RawRule { #[cfg(test)] mod tests { use super::*; - use crate::compiler::compiler::Target; use crate::elements::paragraph::Paragraph; use crate::elements::text::Text; use crate::parser::langparser::LangParser; diff --git a/src/elements/registrar.rs b/src/elements/registrar.rs index 13ed156..4d5eea5 100644 --- a/src/elements/registrar.rs +++ b/src/elements/registrar.rs @@ -1,4 +1,5 @@ use crate::parser::parser::Parser; +use crate::parser::parser::ParserStrategy; use super::code::CodeRule; use super::comment::CommentRule; @@ -14,6 +15,7 @@ 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; @@ -37,6 +39,7 @@ pub fn register(parser: &mut P) { 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(); diff --git a/src/elements/style.rs b/src/elements/style.rs index c398420..892e104 100644 --- a/src/elements/style.rs +++ b/src/elements/style.rs @@ -96,10 +96,6 @@ impl State for StyleState { } // Style not enabled let token = token.as_ref().unwrap(); - //let range = range.as_ref().unwrap(); - - //let active_range = range.start .. paragraph.location().end()-1; - let paragraph = document.last_element::().unwrap(); let paragraph_end = paragraph .content @@ -112,16 +108,9 @@ impl State for StyleState { }) .unwrap(); - // TODO: Allow style to span multiple documents if they don't break paragraph. reports.push( Report::build(ReportKind::Error, token.source(), token.start()) - .with_message("Unterminated style") - //.with_label( - // Label::new((document.source(), active_range.clone())) - // .with_order(0) - // .with_message(format!("Style {} is not terminated before the end of paragraph", - // name.fg(parser.colors().info))) - // .with_color(parser.colors().error)) + .with_message("Unterminated Style") .with_label( Label::new((token.source(), token.range.clone())) .with_order(1) @@ -129,13 +118,13 @@ impl State for StyleState { "Style {} starts here", name.fg(parser.colors().info) )) - .with_color(parser.colors().info), + .with_color(parser.colors().error), ) .with_label( Label::new(paragraph_end) .with_order(1) .with_message(format!("Paragraph ends here")) - .with_color(parser.colors().info), + .with_color(parser.colors().error), ) .with_note("Styles cannot span multiple documents (i.e @import)") .finish(), @@ -199,7 +188,7 @@ impl RegexRule for StyleRule { } }; - if let Some(style_state) = state.borrow_mut().as_any_mut().downcast_mut::() { + if let Some(style_state) = state.borrow_mut().downcast_mut::() { style_state.toggled[index] = style_state.toggled[index] .clone() .map_or(Some(token.clone()), |_| None); diff --git a/src/elements/text.rs b/src/elements/text.rs index 0c768e8..26d7af2 100644 --- a/src/elements/text.rs +++ b/src/elements/text.rs @@ -48,7 +48,7 @@ pub struct TextRule; impl Rule for TextRule { fn name(&self) -> &'static str { "Text" } - fn next_match(&self, _cursor: &Cursor) -> Option<(usize, Box)> { None } + fn next_match(&self, _parser: &dyn Parser, _cursor: &Cursor) -> Option<(usize, Box)> { None } fn on_match( &self, diff --git a/src/lsp/parser.rs b/src/lsp/parser.rs index 82165c7..b222432 100644 --- a/src/lsp/parser.rs +++ b/src/lsp/parser.rs @@ -1,6 +1,6 @@ -use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc}; +use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, rc::Rc}; -use crate::{document::{document::Document, element::Element, layout::{LayoutHolder, LayoutType}, style::{ElementStyle, StyleHolder}}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}}; +use crate::{document::{customstyle::{CustomStyle, CustomStyleHolder}, document::Document, element::Element, layout::{LayoutHolder, LayoutType}, style::{ElementStyle, StyleHolder}}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}}; #[derive(Debug, Clone)] pub struct LineCursor @@ -112,7 +112,7 @@ impl Parser for LsParser fn rules(&self) -> &Vec> { &self.rules } fn rules_mut(&mut self) -> &mut Vec> { &mut self.rules } - fn state(&self) -> std::cell::Ref<'_, StateHolder> { self.state.borrow() } + fn state(&self) -> Ref<'_, StateHolder> { self.state.borrow() } fn state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() } fn has_error(&self) -> bool { true } @@ -148,17 +148,17 @@ impl KernelHolder for LsParser } impl StyleHolder for LsParser { - fn styles(&self) -> std::cell::Ref<'_, HashMap>> { + fn element_styles(&self) -> Ref<'_, HashMap>> { todo!() } - fn styles_mut(&self) -> RefMut<'_, HashMap>> { + fn element_styles_mut(&self) -> RefMut<'_, HashMap>> { todo!() } } impl LayoutHolder for LsParser { - fn layouts(&self) -> std::cell::Ref<'_, HashMap>> { + fn layouts(&self) -> Ref<'_, HashMap>> { todo!() } @@ -166,3 +166,13 @@ impl LayoutHolder for LsParser { todo!() } } + +impl CustomStyleHolder for LsParser { + fn custom_styles(&self) -> Ref<'_, HashMap>> { + todo!() + } + + fn custom_styles_mut(&self) -> RefMut<'_, HashMap>> { + todo!() + } +} diff --git a/src/lua/kernel.rs b/src/lua/kernel.rs index 1a0bd30..8503bfb 100644 --- a/src/lua/kernel.rs +++ b/src/lua/kernel.rs @@ -1,6 +1,10 @@ use std::cell::RefCell; use std::cell::RefMut; +use mlua::Error; +use mlua::FromLuaMulti; +use mlua::Function; +use mlua::IntoLuaMulti; use mlua::Lua; use crate::document::document::Document; @@ -72,3 +76,19 @@ pub trait KernelHolder { fn insert_kernel(&self, name: String, kernel: Kernel) -> RefMut<'_, Kernel>; } + +/// Runs a lua function with a context +/// +/// This is the only way lua functions shoule be ran, because exported +/// functions may require the context in order to operate +pub fn function_with_context<'lua, A, R>(context: KernelContext, fun: &Function<'lua>, args: A) -> Result +where + A: IntoLuaMulti<'lua>, + R: FromLuaMulti<'lua>, +{ + CTX.set(Some(unsafe { std::mem::transmute(context) })); + let ret = fun.call::(args); + CTX.set(None); + + ret +} diff --git a/src/main.rs b/src/main.rs index be36b5c..0679cff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -215,7 +215,13 @@ fn main() -> ExitCode { } } match std::fs::metadata(&output) { - Ok(_) => {} + Ok(output_meta) => { + if !output_meta.is_dir() + { + eprintln!("Input is a directory, but ouput is not a directory, halting"); + return ExitCode::FAILURE; + } + } Err(e) => { eprintln!("Unable to get metadata for output `{output}`: {e}"); return ExitCode::FAILURE; diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs index 968a34b..f0bba36 100644 --- a/src/parser/langparser.rs +++ b/src/parser/langparser.rs @@ -9,6 +9,8 @@ use std::rc::Rc; use ariadne::Label; use ariadne::Report; +use crate::document::customstyle::CustomStyle; +use crate::document::customstyle::CustomStyleHolder; use crate::document::document::Document; use crate::document::document::DocumentAccessors; use crate::document::element::ContainerElement; @@ -29,6 +31,7 @@ use crate::parser::source::SourceFile; use crate::parser::source::VirtualSource; use super::parser::Parser; +use super::parser::ParserStrategy; use super::parser::ReportColors; use super::rule::Rule; use super::source::Cursor; @@ -49,6 +52,7 @@ pub struct LangParser { pub kernels: RefCell>, pub styles: RefCell>>, pub layouts: RefCell>>, + pub custom_styles: RefCell>>, } impl LangParser { @@ -61,6 +65,7 @@ impl LangParser { kernels: RefCell::new(HashMap::new()), styles: RefCell::new(HashMap::new()), layouts: RefCell::new(HashMap::new()), + custom_styles: RefCell::new(HashMap::new()), }; // Register rules register(&mut s); @@ -316,21 +321,29 @@ impl KernelHolder for LangParser { } impl StyleHolder for LangParser { - fn styles(&self) -> Ref<'_, HashMap>> { self.styles.borrow() } + fn element_styles(&self) -> Ref<'_, HashMap>> { + self.styles.borrow() + } - fn styles_mut(&self) -> RefMut<'_, HashMap>> { + fn element_styles_mut(&self) -> RefMut<'_, HashMap>> { self.styles.borrow_mut() } } impl LayoutHolder for LangParser { - fn layouts(&self) -> Ref<'_, HashMap>> { - self.layouts.borrow() - } + fn layouts(&self) -> Ref<'_, HashMap>> { self.layouts.borrow() } - fn layouts_mut( - &self, - ) -> RefMut<'_, HashMap>> { + fn layouts_mut(&self) -> RefMut<'_, HashMap>> { self.layouts.borrow_mut() } } + +impl CustomStyleHolder for LangParser { + fn custom_styles(&self) -> Ref<'_, HashMap>> { + self.custom_styles.borrow() + } + + fn custom_styles_mut(&self) -> RefMut<'_, HashMap>> { + self.custom_styles.borrow_mut() + } +} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index 58969f2..a01ea16 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -8,6 +8,7 @@ use super::rule::Rule; use super::source::Cursor; use super::source::Source; use super::state::StateHolder; +use crate::document::customstyle::CustomStyleHolder; use crate::document::document::Document; use crate::document::element::Element; use crate::document::layout::LayoutHolder; @@ -43,7 +44,7 @@ impl ReportColors { } } -pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { +pub trait Parser: KernelHolder + StyleHolder + LayoutHolder + CustomStyleHolder { /// Gets the colors for formatting errors /// /// When colors are disabled, all colors should resolve to empty string @@ -52,7 +53,37 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { fn rules(&self) -> &Vec>; fn rules_mut(&mut self) -> &mut Vec>; - fn add_rule(&mut self, rule: Box, after: Option<&'static str>) -> Result<(), String> { + fn state(&self) -> Ref<'_, StateHolder>; + fn state_mut(&self) -> RefMut<'_, StateHolder>; + + fn has_error(&self) -> bool; + + /// Add an [`Element`] to the [`Document`] + fn push<'a>(&self, doc: &dyn Document, elem: Box); + + /// Parse [`Source`] into a new [`Document`] + fn parse<'a>( + &self, + source: Rc, + parent: Option<&'a dyn Document<'a>>, + ) -> Box + 'a>; + + /// Parse [`Source`] into an already existing [`Document`] + fn parse_into<'a>(&self, source: Rc, document: &'a dyn Document<'a>); +} + +pub trait ParserStrategy { + fn add_rule(&mut self, rule: Box, after: Option<&'static str>) -> Result<(), String>; + + fn update_matches( + &self, + cursor: &Cursor, + matches: &mut Vec<(usize, Option>)>, + ) -> (Cursor, Option<&Box>, Option>); +} + +impl ParserStrategy for T { + fn add_rule(&mut self, rule: Box, after: Option<&'static str>) -> Result<(), String> { // Error on duplicate rule let rule_name = (*rule).name(); if let Err(e) = self.rules().iter().try_for_each(|rule| { @@ -89,21 +120,13 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { } Ok(()) - } + } - fn state(&self) -> Ref<'_, StateHolder>; - fn state_mut(&self) -> RefMut<'_, StateHolder>; - - fn has_error(&self) -> bool; - - // Update [`matches`] and returns the position of the next matched rule. - // If rule is empty, it means that there are no rules left to parse (i.e - // end of document). - fn update_matches( - &self, - cursor: &Cursor, - matches: &mut Vec<(usize, Option>)>, - ) -> (Cursor, Option<&Box>, Option>) { + fn update_matches( + &self, + cursor: &Cursor, + matches: &mut Vec<(usize, Option>)>, + ) -> (Cursor, Option<&Box>, Option>) { // Update matches // TODO: Trivially parellalizable self.rules() @@ -111,11 +134,12 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { .zip(matches.iter_mut()) .for_each(|(rule, (matched_at, match_data))| { // Don't upate if not stepped over yet - if *matched_at > cursor.pos { + if *matched_at > cursor.pos && rule.downcast_ref::().is_none() { + // TODO: maybe we should expose matches() so it becomes possible to dynamically register a new rule return; } - (*matched_at, *match_data) = match rule.next_match(cursor) { + (*matched_at, *match_data) = match rule.next_match(self, cursor) { None => (usize::MAX, None), Some((mut pos, mut data)) => { // Check if escaped @@ -136,7 +160,7 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { } // Find next potential match - (pos, data) = match rule.next_match(&cursor.at(pos + 1)) { + (pos, data) = match rule.next_match(self, &cursor.at(pos + 1)) { Some((new_pos, new_data)) => (new_pos, new_data), None => (usize::MAX, data), // Stop iterating } @@ -166,18 +190,5 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder { Some(&self.rules()[winner]), std::mem::replace(&mut matches[winner].1, None), ) - } - - /// Add an [`Element`] to the [`Document`] - fn push<'a>(&self, doc: &dyn Document, elem: Box); - - /// Parse [`Source`] into a new [`Document`] - fn parse<'a>( - &self, - source: Rc, - parent: Option<&'a dyn Document<'a>>, - ) -> Box + 'a>; - - /// Parse [`Source`] into an already existing [`Document`] - fn parse_into<'a>(&self, source: Rc, document: &'a dyn Document<'a>); + } } diff --git a/src/parser/rule.rs b/src/parser/rule.rs index 6513518..27277e7 100644 --- a/src/parser/rule.rs +++ b/src/parser/rule.rs @@ -4,6 +4,8 @@ use super::source::Source; use super::source::Token; use crate::document::document::Document; use ariadne::Report; +use downcast_rs::impl_downcast; +use downcast_rs::Downcast; use mlua::Function; use mlua::Lua; @@ -11,11 +13,11 @@ use std::any::Any; use std::ops::Range; use std::rc::Rc; -pub trait Rule { +pub trait Rule: Downcast { /// Returns rule's name fn name(&self) -> &'static str; /// Finds the next match starting from [`cursor`] - fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box)>; + fn next_match(&self, parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)>; /// Callback when rule matches fn on_match<'a>( &self, @@ -33,6 +35,7 @@ pub trait Rule { /// Registers default layouts fn register_layouts(&self, _parser: &dyn Parser) {} } +impl_downcast!(Rule); impl core::fmt::Debug for dyn Rule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -61,13 +64,13 @@ pub trait RegexRule { fn register_layouts(&self, _parser: &dyn Parser) {} } -impl Rule for T { +impl Rule for T { fn name(&self) -> &'static str { RegexRule::name(self) } /// Finds the next match starting from [`cursor`] - fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box)> { + fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box)> { let content = cursor.source.content(); let mut found: Option<(usize, usize)> = None; self.regexes().iter().enumerate().for_each(|(id, re)| {