Experimental custom styles

This commit is contained in:
ef3d0c3e 2024-08-04 19:08:49 +02:00
parent 67ea2f9411
commit b5c8fbbfea
21 changed files with 755 additions and 96 deletions

1
Cargo.lock generated
View file

@ -741,6 +741,7 @@ dependencies = [
"lsp-server", "lsp-server",
"lsp-types 0.97.0", "lsp-types 0.97.0",
"mlua", "mlua",
"rand 0.8.5",
"regex", "regex",
"rusqlite", "rusqlite",
"rust-crypto", "rust-crypto",

View file

@ -37,3 +37,6 @@ tokio = { version = "1.38.1", features = ["macros", "rt-multi-thread", "io-std"]
tower-lsp = "0.20.0" tower-lsp = "0.20.0"
unicode-segmentation = "1.11.0" unicode-segmentation = "1.11.0"
walkdir = "2.5.0" walkdir = "2.5.0"
[dev-dependencies]
rand = "0.8.5"

View file

@ -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<dyn Source>, Range<usize>)>>;
fn on_end<'a>(
&self,
location: Token,
parser: &dyn Parser,
document: &'a (dyn Document<'a> + 'a),
) -> Result<(), Report<(Rc<dyn Source>, Range<usize>)>>;
}
pub trait CustomStyleHolder {
/// gets a reference to all defined custom styles
fn custom_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn CustomStyle>>>;
/// gets a (mutable) reference to all defined custom styles
fn custom_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn CustomStyle>>>;
fn get_custom_style(&self, style_name: &str) -> Option<Rc<dyn CustomStyle>> {
self.custom_styles()
.get(style_name)
.map(|style| style.clone())
}
fn insert_custom_style(&self, style: Rc<dyn CustomStyle>) {
self.custom_styles_mut().insert(style.name().into(), style);
}
}

View file

@ -3,7 +3,6 @@ use std::str::FromStr;
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use crate::elements::reference::Reference; use crate::elements::reference::Reference;
use crate::parser::source::Token; use crate::parser::source::Token;
use crate::parser::util::PropertyParser;
use downcast_rs::impl_downcast; use downcast_rs::impl_downcast;
use downcast_rs::Downcast; use downcast_rs::Downcast;

View file

@ -5,3 +5,4 @@ pub mod element;
pub mod variable; pub mod variable;
pub mod style; pub mod style;
pub mod layout; pub mod layout;
pub mod customstyle;

View file

@ -18,9 +18,6 @@ pub trait ElementStyle: Downcast + core::fmt::Debug {
/// Will fail if deserialization fails /// Will fail if deserialization fails
fn from_json(&self, json: &str) -> Result<Rc<dyn ElementStyle>, String>; fn from_json(&self, json: &str) -> Result<Rc<dyn ElementStyle>, String>;
/// Serializes sytle into json string
fn to_json(&self) -> String;
/// Attempts to deserialize lua table into a new style /// Attempts to deserialize lua table into a new style
fn from_lua( fn from_lua(
&self, &self,
@ -32,24 +29,24 @@ impl_downcast!(ElementStyle);
pub trait StyleHolder { pub trait StyleHolder {
/// gets a reference to all defined styles /// gets a reference to all defined styles
fn styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>>; fn element_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>>;
/// gets a (mutable) reference to all defined styles /// gets a (mutable) reference to all defined styles
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>>; fn element_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>>;
/// Checks if a given style key is registered /// 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 /// Gets the current active style for an element
/// NOTE: Will panic if a style is not defined for a given element /// NOTE: Will panic if a style is not defined for a given element
/// If you need to process user input, use [`is_registered`] /// If you need to process user input, use [`is_registered`]
fn current_style(&self, style_key: &str) -> Rc<dyn ElementStyle> { fn current_style(&self, style_key: &str) -> Rc<dyn ElementStyle> {
self.styles().get(style_key).map(|rc| rc.clone()).unwrap() self.element_styles().get(style_key).map(|rc| rc.clone()).unwrap()
} }
/// Sets the [`style`] /// Sets the [`style`]
fn set_current_style(&self, style: Rc<dyn ElementStyle>) { fn set_current_style(&self, style: Rc<dyn ElementStyle>) {
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<dyn ElementStyle>) .map(|obj| std::rc::Rc::new(obj) as std::rc::Rc<dyn ElementStyle>)
} }
fn to_json(&self) -> String { serde_json::to_string(self).unwrap() }
fn from_lua( fn from_lua(
&self, &self,
lua: &mlua::Lua, lua: &mlua::Lua,

557
src/elements/customstyle.rs Normal file
View file

@ -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<dyn Source>, Range<usize>)>> {
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<dyn Source>, Range<usize>)>> {
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<String, Token>,
}
impl State for CustomStyleState {
fn scope(&self) -> Scope { Scope::PARAGRAPH }
fn on_remove<'a>(
&self,
parser: &dyn Parser,
document: &dyn Document,
) -> Vec<Report<'a, (Rc<dyn Source>, Range<usize>)>> {
let mut reports = vec![];
self.toggled.iter().for_each(|(style, token)| {
let paragraph = document.last_element::<Paragraph>().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<dyn Any>)> {
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<dyn Any>,
))
}
}
fn on_match<'a>(
&self,
parser: &dyn Parser,
document: &'a dyn Document<'a>,
cursor: Cursor,
match_data: Option<Box<dyn Any>>,
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
let (style, end) = match_data
.as_ref()
.unwrap()
.downcast_ref::<(Rc<dyn CustomStyle>, 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::<CustomStyleState>().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::<CustomStyleState>().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<Vec<(String, Function<'lua>)>> {
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", "<a style=\"color:red\">")
end
function red_style_end()
nml.raw.push("inline", "</a>")
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 == "<a style=\"color:red\">" };
Text { content == "Hello" };
Raw { content == "</a>" };
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", "<a style=\"color:red\">")
end
function red_style_end()
nml.raw.push("inline", "</a>")
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 == "<a style=\"color:red\">" };
Text { content == "Hello" };
Raw { content == "</a>" };
Text { content == "." };
};
);
}
}

View file

@ -10,23 +10,15 @@ use ariadne::ReportKind;
use mlua::Error::BadArgument; use mlua::Error::BadArgument;
use mlua::Function; use mlua::Function;
use mlua::Lua; use mlua::Lua;
use mlua::LuaSerdeExt;
use mlua::Table;
use mlua::Value; use mlua::Value;
use regex::Captures;
use regex::Regex; use regex::Regex;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::{self};
use crate::lua::kernel::CTX; use crate::lua::kernel::CTX;
use crate::parser::parser::Parser; use crate::parser::parser::Parser;
use crate::parser::rule::RegexRule;
use crate::parser::rule::Rule; use crate::parser::rule::Rule;
use crate::parser::source::Cursor; use crate::parser::source::Cursor;
use crate::parser::source::Source; use crate::parser::source::Source;
use crate::parser::source::Token;
use super::variable::VariableRule;
pub struct ElemStyleRule { pub struct ElemStyleRule {
start_re: Regex, start_re: Regex,
@ -66,7 +58,7 @@ impl ElemStyleRule {
impl Rule for ElemStyleRule { impl Rule for ElemStyleRule {
fn name(&self) -> &'static str { "Element Style" } fn name(&self) -> &'static str { "Element Style" }
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.start_re self.start_re
.find_at(cursor.source.content(), cursor.pos) .find_at(cursor.source.content(), cursor.pos)
.map_or(None, |m| { .map_or(None, |m| {

View file

@ -252,7 +252,7 @@ impl ListRule {
impl Rule for ListRule { impl Rule for ListRule {
fn name(&self) -> &'static str { "List" } fn name(&self) -> &'static str { "List" }
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.start_re self.start_re
.find_at(cursor.source.content(), cursor.pos) .find_at(cursor.source.content(), cursor.pos)
.map_or(None, |m| { .map_or(None, |m| {
@ -440,7 +440,6 @@ impl Rule for ListRule {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::compiler::compiler::Target;
use crate::elements::paragraph::Paragraph; use crate::elements::paragraph::Paragraph;
use crate::elements::text::Text; use crate::elements::text::Text;
use crate::parser::langparser::LangParser; use crate::parser::langparser::LangParser;

View file

@ -17,3 +17,4 @@ pub mod tex;
pub mod text; pub mod text;
pub mod variable; pub mod variable;
pub mod elemstyle; pub mod elemstyle;
pub mod customstyle;

View file

@ -108,7 +108,7 @@ impl ParagraphRule {
impl Rule for ParagraphRule { impl Rule for ParagraphRule {
fn name(&self) -> &'static str { "Paragraphing" } fn name(&self) -> &'static str { "Paragraphing" }
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
self.re self.re
.find_at(cursor.source.content(), cursor.pos) .find_at(cursor.source.content(), cursor.pos)
.and_then(|m| Some((m.start(), Box::new([false; 0]) as Box<dyn Any>))) .and_then(|m| Some((m.start(), Box::new([false; 0]) as Box<dyn Any>)))

View file

@ -27,10 +27,10 @@ use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
struct Raw { pub struct Raw {
pub(self) location: Token, pub location: Token,
pub(self) kind: ElemKind, pub kind: ElemKind,
pub(self) content: String, pub content: String,
} }
impl Element for Raw { impl Element for Raw {
@ -265,7 +265,6 @@ impl RegexRule for RawRule {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::compiler::compiler::Target;
use crate::elements::paragraph::Paragraph; use crate::elements::paragraph::Paragraph;
use crate::elements::text::Text; use crate::elements::text::Text;
use crate::parser::langparser::LangParser; use crate::parser::langparser::LangParser;

View file

@ -1,4 +1,5 @@
use crate::parser::parser::Parser; use crate::parser::parser::Parser;
use crate::parser::parser::ParserStrategy;
use super::code::CodeRule; use super::code::CodeRule;
use super::comment::CommentRule; use super::comment::CommentRule;
@ -14,6 +15,7 @@ use super::raw::RawRule;
use super::script::ScriptRule; use super::script::ScriptRule;
use super::section::SectionRule; use super::section::SectionRule;
use super::style::StyleRule; use super::style::StyleRule;
use super::customstyle::CustomStyleRule;
use super::tex::TexRule; use super::tex::TexRule;
use super::text::TextRule; use super::text::TextRule;
use super::variable::VariableRule; use super::variable::VariableRule;
@ -37,6 +39,7 @@ pub fn register<P: Parser>(parser: &mut P) {
parser.add_rule(Box::new(LayoutRule::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(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(SectionRule::new()), None).unwrap();
parser.add_rule(Box::new(LinkRule::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(TextRule::default()), None).unwrap();

View file

@ -96,10 +96,6 @@ impl State for StyleState {
} // Style not enabled } // Style not enabled
let token = token.as_ref().unwrap(); 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::<Paragraph>().unwrap(); let paragraph = document.last_element::<Paragraph>().unwrap();
let paragraph_end = paragraph let paragraph_end = paragraph
.content .content
@ -112,16 +108,9 @@ impl State for StyleState {
}) })
.unwrap(); .unwrap();
// TODO: Allow style to span multiple documents if they don't break paragraph.
reports.push( reports.push(
Report::build(ReportKind::Error, token.source(), token.start()) Report::build(ReportKind::Error, token.source(), token.start())
.with_message("Unterminated style") .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_label( .with_label(
Label::new((token.source(), token.range.clone())) Label::new((token.source(), token.range.clone()))
.with_order(1) .with_order(1)
@ -129,13 +118,13 @@ impl State for StyleState {
"Style {} starts here", "Style {} starts here",
name.fg(parser.colors().info) name.fg(parser.colors().info)
)) ))
.with_color(parser.colors().info), .with_color(parser.colors().error),
) )
.with_label( .with_label(
Label::new(paragraph_end) Label::new(paragraph_end)
.with_order(1) .with_order(1)
.with_message(format!("Paragraph ends here")) .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)") .with_note("Styles cannot span multiple documents (i.e @import)")
.finish(), .finish(),
@ -199,7 +188,7 @@ impl RegexRule for StyleRule {
} }
}; };
if let Some(style_state) = state.borrow_mut().as_any_mut().downcast_mut::<StyleState>() { if let Some(style_state) = state.borrow_mut().downcast_mut::<StyleState>() {
style_state.toggled[index] = style_state.toggled[index] style_state.toggled[index] = style_state.toggled[index]
.clone() .clone()
.map_or(Some(token.clone()), |_| None); .map_or(Some(token.clone()), |_| None);

View file

@ -48,7 +48,7 @@ pub struct TextRule;
impl Rule for TextRule { impl Rule for TextRule {
fn name(&self) -> &'static str { "Text" } fn name(&self) -> &'static str { "Text" }
fn next_match(&self, _cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { None } fn next_match(&self, _parser: &dyn Parser, _cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { None }
fn on_match( fn on_match(
&self, &self,

View file

@ -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)] #[derive(Debug, Clone)]
pub struct LineCursor pub struct LineCursor
@ -112,7 +112,7 @@ impl Parser for LsParser
fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules } fn rules(&self) -> &Vec<Box<dyn Rule>> { &self.rules }
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> { &mut self.rules } fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>> { &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 state_mut(&self) -> std::cell::RefMut<'_, StateHolder> { self.state.borrow_mut() }
fn has_error(&self) -> bool { true } fn has_error(&self) -> bool { true }
@ -148,17 +148,17 @@ impl KernelHolder for LsParser
} }
impl StyleHolder for LsParser { impl StyleHolder for LsParser {
fn styles(&self) -> std::cell::Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> { fn element_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> {
todo!() todo!()
} }
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> { fn element_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> {
todo!() todo!()
} }
} }
impl LayoutHolder for LsParser { impl LayoutHolder for LsParser {
fn layouts(&self) -> std::cell::Ref<'_, HashMap<String, Rc<dyn LayoutType>>> { fn layouts(&self) -> Ref<'_, HashMap<String, Rc<dyn LayoutType>>> {
todo!() todo!()
} }
@ -166,3 +166,13 @@ impl LayoutHolder for LsParser {
todo!() todo!()
} }
} }
impl CustomStyleHolder for LsParser {
fn custom_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn CustomStyle>>> {
todo!()
}
fn custom_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn CustomStyle>>> {
todo!()
}
}

View file

@ -1,6 +1,10 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::cell::RefMut; use std::cell::RefMut;
use mlua::Error;
use mlua::FromLuaMulti;
use mlua::Function;
use mlua::IntoLuaMulti;
use mlua::Lua; use mlua::Lua;
use crate::document::document::Document; use crate::document::document::Document;
@ -72,3 +76,19 @@ pub trait KernelHolder {
fn insert_kernel(&self, name: String, kernel: Kernel) -> RefMut<'_, Kernel>; 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<R, Error>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
CTX.set(Some(unsafe { std::mem::transmute(context) }));
let ret = fun.call::<A, R>(args);
CTX.set(None);
ret
}

View file

@ -215,7 +215,13 @@ fn main() -> ExitCode {
} }
} }
match std::fs::metadata(&output) { 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) => { Err(e) => {
eprintln!("Unable to get metadata for output `{output}`: {e}"); eprintln!("Unable to get metadata for output `{output}`: {e}");
return ExitCode::FAILURE; return ExitCode::FAILURE;

View file

@ -9,6 +9,8 @@ use std::rc::Rc;
use ariadne::Label; use ariadne::Label;
use ariadne::Report; use ariadne::Report;
use crate::document::customstyle::CustomStyle;
use crate::document::customstyle::CustomStyleHolder;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::document::DocumentAccessors; use crate::document::document::DocumentAccessors;
use crate::document::element::ContainerElement; use crate::document::element::ContainerElement;
@ -29,6 +31,7 @@ use crate::parser::source::SourceFile;
use crate::parser::source::VirtualSource; use crate::parser::source::VirtualSource;
use super::parser::Parser; use super::parser::Parser;
use super::parser::ParserStrategy;
use super::parser::ReportColors; use super::parser::ReportColors;
use super::rule::Rule; use super::rule::Rule;
use super::source::Cursor; use super::source::Cursor;
@ -49,6 +52,7 @@ pub struct LangParser {
pub kernels: RefCell<HashMap<String, Kernel>>, pub kernels: RefCell<HashMap<String, Kernel>>,
pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>, pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>,
pub layouts: RefCell<HashMap<String, Rc<dyn LayoutType>>>, pub layouts: RefCell<HashMap<String, Rc<dyn LayoutType>>>,
pub custom_styles: RefCell<HashMap<String, Rc<dyn CustomStyle>>>,
} }
impl LangParser { impl LangParser {
@ -61,6 +65,7 @@ impl LangParser {
kernels: RefCell::new(HashMap::new()), kernels: RefCell::new(HashMap::new()),
styles: RefCell::new(HashMap::new()), styles: RefCell::new(HashMap::new()),
layouts: RefCell::new(HashMap::new()), layouts: RefCell::new(HashMap::new()),
custom_styles: RefCell::new(HashMap::new()),
}; };
// Register rules // Register rules
register(&mut s); register(&mut s);
@ -316,21 +321,29 @@ impl KernelHolder for LangParser {
} }
impl StyleHolder for LangParser { impl StyleHolder for LangParser {
fn styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> { self.styles.borrow() } fn element_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> {
self.styles.borrow()
}
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> { fn element_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> {
self.styles.borrow_mut() self.styles.borrow_mut()
} }
} }
impl LayoutHolder for LangParser { impl LayoutHolder for LangParser {
fn layouts(&self) -> Ref<'_, HashMap<String, Rc<dyn crate::document::layout::LayoutType>>> { fn layouts(&self) -> Ref<'_, HashMap<String, Rc<dyn LayoutType>>> { self.layouts.borrow() }
self.layouts.borrow()
}
fn layouts_mut( fn layouts_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn LayoutType>>> {
&self,
) -> RefMut<'_, HashMap<String, Rc<dyn crate::document::layout::LayoutType>>> {
self.layouts.borrow_mut() self.layouts.borrow_mut()
} }
} }
impl CustomStyleHolder for LangParser {
fn custom_styles(&self) -> Ref<'_, HashMap<String, Rc<dyn CustomStyle>>> {
self.custom_styles.borrow()
}
fn custom_styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn CustomStyle>>> {
self.custom_styles.borrow_mut()
}
}

View file

@ -8,6 +8,7 @@ use super::rule::Rule;
use super::source::Cursor; use super::source::Cursor;
use super::source::Source; use super::source::Source;
use super::state::StateHolder; use super::state::StateHolder;
use crate::document::customstyle::CustomStyleHolder;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::element::Element; use crate::document::element::Element;
use crate::document::layout::LayoutHolder; 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 /// Gets the colors for formatting errors
/// ///
/// When colors are disabled, all colors should resolve to empty string /// 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<Box<dyn Rule>>; fn rules(&self) -> &Vec<Box<dyn Rule>>;
fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>>; fn rules_mut(&mut self) -> &mut Vec<Box<dyn Rule>>;
fn add_rule(&mut self, rule: Box<dyn Rule>, 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<dyn Element>);
/// Parse [`Source`] into a new [`Document`]
fn parse<'a>(
&self,
source: Rc<dyn Source>,
parent: Option<&'a dyn Document<'a>>,
) -> Box<dyn Document<'a> + 'a>;
/// Parse [`Source`] into an already existing [`Document`]
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>);
}
pub trait ParserStrategy {
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String>;
fn update_matches(
&self,
cursor: &Cursor,
matches: &mut Vec<(usize, Option<Box<dyn Any>>)>,
) -> (Cursor, Option<&Box<dyn Rule>>, Option<Box<dyn Any>>);
}
impl<T: Parser> ParserStrategy for T {
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String> {
// Error on duplicate rule // Error on duplicate rule
let rule_name = (*rule).name(); let rule_name = (*rule).name();
if let Err(e) = self.rules().iter().try_for_each(|rule| { if let Err(e) = self.rules().iter().try_for_each(|rule| {
@ -89,21 +120,13 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder {
} }
Ok(()) Ok(())
} }
fn state(&self) -> Ref<'_, StateHolder>; fn update_matches(
fn state_mut(&self) -> RefMut<'_, StateHolder>; &self,
cursor: &Cursor,
fn has_error(&self) -> bool; matches: &mut Vec<(usize, Option<Box<dyn Any>>)>,
) -> (Cursor, Option<&Box<dyn Rule>>, Option<Box<dyn Any>>) {
// 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<Box<dyn Any>>)>,
) -> (Cursor, Option<&Box<dyn Rule>>, Option<Box<dyn Any>>) {
// Update matches // Update matches
// TODO: Trivially parellalizable // TODO: Trivially parellalizable
self.rules() self.rules()
@ -111,11 +134,12 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder {
.zip(matches.iter_mut()) .zip(matches.iter_mut())
.for_each(|(rule, (matched_at, match_data))| { .for_each(|(rule, (matched_at, match_data))| {
// Don't upate if not stepped over yet // Don't upate if not stepped over yet
if *matched_at > cursor.pos { if *matched_at > cursor.pos && rule.downcast_ref::<crate::elements::customstyle::CustomStyleRule>().is_none() {
// TODO: maybe we should expose matches() so it becomes possible to dynamically register a new rule
return; return;
} }
(*matched_at, *match_data) = match rule.next_match(cursor) { (*matched_at, *match_data) = match rule.next_match(self, cursor) {
None => (usize::MAX, None), None => (usize::MAX, None),
Some((mut pos, mut data)) => { Some((mut pos, mut data)) => {
// Check if escaped // Check if escaped
@ -136,7 +160,7 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder {
} }
// Find next potential match // 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), Some((new_pos, new_data)) => (new_pos, new_data),
None => (usize::MAX, data), // Stop iterating None => (usize::MAX, data), // Stop iterating
} }
@ -166,18 +190,5 @@ pub trait Parser: KernelHolder + StyleHolder + LayoutHolder {
Some(&self.rules()[winner]), Some(&self.rules()[winner]),
std::mem::replace(&mut matches[winner].1, None), std::mem::replace(&mut matches[winner].1, None),
) )
} }
/// Add an [`Element`] to the [`Document`]
fn push<'a>(&self, doc: &dyn Document, elem: Box<dyn Element>);
/// Parse [`Source`] into a new [`Document`]
fn parse<'a>(
&self,
source: Rc<dyn Source>,
parent: Option<&'a dyn Document<'a>>,
) -> Box<dyn Document<'a> + 'a>;
/// Parse [`Source`] into an already existing [`Document`]
fn parse_into<'a>(&self, source: Rc<dyn Source>, document: &'a dyn Document<'a>);
} }

View file

@ -4,6 +4,8 @@ use super::source::Source;
use super::source::Token; use super::source::Token;
use crate::document::document::Document; use crate::document::document::Document;
use ariadne::Report; use ariadne::Report;
use downcast_rs::impl_downcast;
use downcast_rs::Downcast;
use mlua::Function; use mlua::Function;
use mlua::Lua; use mlua::Lua;
@ -11,11 +13,11 @@ use std::any::Any;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
pub trait Rule { pub trait Rule: Downcast {
/// Returns rule's name /// Returns rule's name
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// Finds the next match starting from [`cursor`] /// Finds the next match starting from [`cursor`]
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)>; fn next_match(&self, parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)>;
/// Callback when rule matches /// Callback when rule matches
fn on_match<'a>( fn on_match<'a>(
&self, &self,
@ -33,6 +35,7 @@ pub trait Rule {
/// Registers default layouts /// Registers default layouts
fn register_layouts(&self, _parser: &dyn Parser) {} fn register_layouts(&self, _parser: &dyn Parser) {}
} }
impl_downcast!(Rule);
impl core::fmt::Debug for dyn Rule { impl core::fmt::Debug for dyn Rule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 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) {} fn register_layouts(&self, _parser: &dyn Parser) {}
} }
impl<T: RegexRule> Rule for T { impl<T: RegexRule + 'static> Rule for T {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
RegexRule::name(self) RegexRule::name(self)
} }
/// Finds the next match starting from [`cursor`] /// Finds the next match starting from [`cursor`]
fn next_match(&self, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> { fn next_match(&self, _parser: &dyn Parser, cursor: &Cursor) -> Option<(usize, Box<dyn Any>)> {
let content = cursor.source.content(); let content = cursor.source.content();
let mut found: Option<(usize, usize)> = None; let mut found: Option<(usize, usize)> = None;
self.regexes().iter().enumerate().for_each(|(id, re)| { self.regexes().iter().enumerate().for_each(|(id, re)| {